Commit 992c2f82 authored by 关振斌's avatar 关振斌

update

parent 5fcf9f32
// import 'dart:ffi';
import 'package:chart/pages/frame/notfound/index.dart';
import 'package:chart/pages/frame/product/index.dart';
import 'package:flutter/material.dart';
import 'package:flutter_client_sse/flutter_client_sse.dart';
import 'package:get/get.dart';
import 'routes.dart';
......@@ -22,6 +24,17 @@ class RouteObservers<R extends Route<dynamic>> extends RouteObserver<R> {
String? name = route.settings.name;
if (ChatNewController.to.state.isLoading) {
ChatNewController.to.sse?.cancel();
ChatNewController.to.state.isLoading = false;
SSEClient.unsubscribeFromSSE();
}
if (ProductController.to.state.isLoading) {
ProductController.to.sse?.cancel();
ProductController.to.state.isLoading = false;
SSEClient.unsubscribeFromSSE();
}
if (name != null) {
bool isProduct = name.contains('product?title');
......
// baidu yapi
// const SERVER_API_URL = 'https://yapi.baidu.com/mock/41008';
// const SERVER_API_URL = 'http://192.168.110.127:8083/api';
const SERVER_API_URL = 'http://101.34.153.228:8083/api';//线上
// const SERVER_API_URL = 'http://101.34.153.228:8083/api';//线上
const SERVER_API_URL = "http://192.168.120.108:8083/api";
// http://192.168.110.127:8083/api/doc.html
// http://192.168.110.66:8083/api/doc.html
// const SERVER_API_URL = 'http://192.168.110.66:8083/api';
......
......@@ -2,16 +2,21 @@ library dash_chat_2;
import 'dart:math';
import 'package:highlight/highlight.dart' show highlight, Node;
import 'package:animated_text_kit/animated_text_kit.dart';
import 'package:chart/package/markdown/flutter_markdown.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:flutter_parsed_text/flutter_parsed_text.dart';
import 'package:get/get_connect/http/src/utils/utils.dart';
import 'package:gradient_borders/input_borders/gradient_outline_input_border.dart';
import 'package:highlight/highlight.dart';
import 'package:intl/intl.dart' as intl;
import 'package:url_launcher/url_launcher.dart';
import 'package:video_player/video_player.dart' as vp;
import '../markdown/src/style_sheet.dart';
import 'src/widgets/image_provider/image_provider.dart';
export 'package:flutter_parsed_text/flutter_parsed_text.dart';
......
......@@ -42,9 +42,7 @@ class DefaultAvatar extends StatelessWidget {
onLongPress:
onLongPressAvatar != null ? () => onLongPressAvatar!(user) : null,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 10,
),
padding: const EdgeInsets.symmetric(horizontal: 10),
child: SizedBox(
height: size,
width: size,
......
part of dash_chat_2;
// ignore: non_constant_identifier_names
// part of animated_text_kit;
/// {@category Default widgets}
......@@ -113,11 +114,27 @@ class DefaultMessageText extends StatelessWidget {
// child: Markdown(data: text, selectable: true),
// );
return RichText(
text: TextSpan(text: text, style: TextStyle(color: Colors.black)),
// selectionRegistrar: SelectionContainer.maybeOf(context),
selectionColor: const Color(0xAF6694e8),
return MyMarkdown(
// syntaxHighlighter: SyntaxHighlighter(),
// styleConfig: StyleConfig(),
data: text,
// styleSheet: MarkdownStyleSheet(
// // code: TextStyle(color: Colors.red),
// ),
);
// Column(
// children: [
// Expanded(
// child: Markdown(data: text),
// )
// ],
// );
// RichText(
// text: TextSpan(text: text, style: TextStyle(color: Colors.black)),
// // selectionRegistrar: SelectionContainer.maybeOf(context),
// selectionColor: const Color(0xAF6694e8),
// );
// Markdown(data: text, selectable: true);
}
......@@ -162,3 +179,50 @@ class DefaultMessageText extends StatelessWidget {
);
}
}
class MyStyleSheet extends MarkdownStyleSheet {
@override
// TextStyle codeblockStyle = TextStyle(fontFamily: 'monospace', fontSize: 12.0);
//
@override
// BoxDecoration codeblockDecoration = BoxDecoration(color: Colors.red);
@override
TextStyle codeStyle = TextStyle(fontFamily: 'monospace', color: Colors.red);
@override
Widget codeBlock({
required String code,
required String language,
// required SyntaxHighlighterStyle syntaxHighlighterStyle
}) {
final nodes = highlight.parse(code, language: language);
return Container(
padding: EdgeInsets.all(16.0),
decoration: codeblockDecoration,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: RichText(
text: TextSpan(
style: codeStyle,
children: _toTextSpan(nodes.nodes!),
),
)));
}
List<TextSpan> _toTextSpan(List<Node> nodes) {
final result = <TextSpan>[];
for (final node in nodes) {
if (node.value != null) {
result.add(TextSpan(text: node.value!, style: TextStyle()));
} else if (node.children != null) {
result.add(TextSpan(
children: _toTextSpan(node.children!),
style: TextStyle(color: node.className == null ? null : Colors.blue),
));
}
}
return result;
}
}
......@@ -71,7 +71,7 @@ class MessageRow extends StatelessWidget {
return Padding(
padding: EdgeInsets.only(top: isPreviousSameAuthor ? 2 : 15),
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment:
isOwnMessage ? MainAxisAlignment.end : MainAxisAlignment.start,
children: <Widget>[
......
......@@ -83,9 +83,7 @@ class TextContainer extends StatelessWidget {
: 18.0,
),
padding: messageOptions.messagePadding ?? const EdgeInsets.all(11),
child: messageTextBuilder != null
? messageTextBuilder!(message, previousMessage, nextMessage)
: DefaultMessageText(
child: DefaultMessageText(
index: index,
messageLength: messageLength,
message: message,
......@@ -95,3 +93,6 @@ class TextContainer extends StatelessWidget {
);
}
}
// messageTextBuilder != null
// ? messageTextBuilder!(message, previousMessage, nextMessage)
// :
\ No newline at end of file
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/// A library to render markdown formatted text.
library flutter_markdown;
export 'src/builder.dart';
export 'src/style_sheet.dart';
export 'src/widget.dart';
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:io';
import 'package:flutter/cupertino.dart' show CupertinoTheme;
import 'package:flutter/material.dart' show Theme;
import 'package:flutter/widgets.dart';
import 'style_sheet.dart';
import 'widget.dart';
/// Type for a function that creates image widgets.
typedef ImageBuilder = Widget Function(
Uri uri, String? imageDirectory, double? width, double? height);
/// A default image builder handling http/https, resource, and file URLs.
// ignore: prefer_function_declarations_over_variables
final ImageBuilder kDefaultImageBuilder = (
Uri uri,
String? imageDirectory,
double? width,
double? height,
) {
if (uri.scheme == 'http' || uri.scheme == 'https') {
return Image.network(uri.toString(), width: width, height: height);
} else if (uri.scheme == 'data') {
return _handleDataSchemeUri(uri, width, height);
} else if (uri.scheme == 'resource') {
return Image.asset(uri.path, width: width, height: height);
} else {
final Uri fileUri = imageDirectory != null
? Uri.parse(imageDirectory + uri.toString())
: uri;
if (fileUri.scheme == 'http' || fileUri.scheme == 'https') {
return Image.network(fileUri.toString(), width: width, height: height);
} else {
return Image.file(File.fromUri(fileUri), width: width, height: height);
}
}
};
/// A default style sheet generator.
final MarkdownStyleSheet Function(BuildContext, MarkdownStyleSheetBaseTheme?)
// ignore: prefer_function_declarations_over_variables
kFallbackStyle = (
BuildContext context,
MarkdownStyleSheetBaseTheme? baseTheme,
) {
MarkdownStyleSheet result;
switch (baseTheme) {
case MarkdownStyleSheetBaseTheme.platform:
result = (Platform.isIOS || Platform.isMacOS)
? MarkdownStyleSheet.fromCupertinoTheme(CupertinoTheme.of(context))
: MarkdownStyleSheet.fromTheme(Theme.of(context));
break;
case MarkdownStyleSheetBaseTheme.cupertino:
result =
MarkdownStyleSheet.fromCupertinoTheme(CupertinoTheme.of(context));
break;
case MarkdownStyleSheetBaseTheme.material:
// ignore: no_default_cases
default:
result = MarkdownStyleSheet.fromTheme(Theme.of(context));
}
return result.copyWith(
textScaleFactor: MediaQuery.textScaleFactorOf(context),
);
};
Widget _handleDataSchemeUri(
Uri uri, final double? width, final double? height) {
final String mimeType = uri.data!.mimeType;
if (mimeType.startsWith('image/')) {
return Image.memory(
uri.data!.contentAsBytes(),
width: width,
height: height,
);
} else if (mimeType.startsWith('text/')) {
return Text(uri.data!.contentAsString());
}
return const SizedBox();
}
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:html'; // ignore: avoid_web_libraries_in_flutter
import 'package:flutter/cupertino.dart' show CupertinoTheme;
import 'package:flutter/material.dart' show Theme;
import 'package:flutter/widgets.dart';
import 'package:path/path.dart' as p;
import 'style_sheet.dart';
import 'widget.dart';
/// Type for a function that creates image widgets.
typedef ImageBuilder = Widget Function(
Uri uri, String? imageDirectory, double? width, double? height);
/// A default image builder handling http/https, resource, data, and file URLs.
// ignore: prefer_function_declarations_over_variables
final ImageBuilder kDefaultImageBuilder = (
Uri uri,
String? imageDirectory,
double? width,
double? height,
) {
if (uri.scheme == 'http' || uri.scheme == 'https') {
return Image.network(uri.toString(), width: width, height: height);
} else if (uri.scheme == 'data') {
return _handleDataSchemeUri(uri, width, height);
} else if (uri.scheme == 'resource') {
return Image.asset(uri.path, width: width, height: height);
} else {
final Uri fileUri = imageDirectory != null
? Uri.parse(p.join(imageDirectory, uri.toString()))
: uri;
if (fileUri.scheme == 'http' || fileUri.scheme == 'https') {
return Image.network(fileUri.toString(), width: width, height: height);
} else {
final String src = p.join(p.current, fileUri.toString());
return Image.network(src, width: width, height: height);
}
}
};
/// A default style sheet generator.
final MarkdownStyleSheet Function(BuildContext, MarkdownStyleSheetBaseTheme?)
// ignore: prefer_function_declarations_over_variables
kFallbackStyle = (
BuildContext context,
MarkdownStyleSheetBaseTheme? baseTheme,
) {
MarkdownStyleSheet result;
switch (baseTheme) {
case MarkdownStyleSheetBaseTheme.platform:
final String userAgent = window.navigator.userAgent;
result = userAgent.contains('Mac OS X')
? MarkdownStyleSheet.fromCupertinoTheme(CupertinoTheme.of(context))
: MarkdownStyleSheet.fromTheme(Theme.of(context));
break;
case MarkdownStyleSheetBaseTheme.cupertino:
result =
MarkdownStyleSheet.fromCupertinoTheme(CupertinoTheme.of(context));
break;
case MarkdownStyleSheetBaseTheme.material:
default: // ignore: no_default_cases
result = MarkdownStyleSheet.fromTheme(Theme.of(context));
}
return result.copyWith(
textScaleFactor: MediaQuery.textScaleFactorOf(context),
);
};
Widget _handleDataSchemeUri(
Uri uri, final double? width, final double? height) {
final String mimeType = uri.data!.mimeType;
if (mimeType.startsWith('image/')) {
return Image.memory(
uri.data!.contentAsBytes(),
width: width,
height: height,
);
} else if (mimeType.startsWith('text/')) {
return Text(uri.data!.contentAsString());
}
return const SizedBox();
}
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:markdown/markdown.dart' as md;
import '_functions_io.dart' if (dart.library.html) '_functions_web.dart';
import 'style_sheet.dart';
import 'widget.dart';
const List<String> _kBlockTags = <String>[
'p',
'h1',
'h2',
'h3',
'h4',
'h5',
'h6',
'li',
'blockquote',
'pre',
'ol',
'ul',
'hr',
'table',
'thead',
'tbody',
'tr'
];
const List<String> _kListTags = <String>['ul', 'ol'];
bool _isBlockTag(String? tag) => _kBlockTags.contains(tag);
bool _isListTag(String tag) => _kListTags.contains(tag);
class _BlockElement {
_BlockElement(this.tag);
final String? tag;
final List<Widget> children = <Widget>[];
int nextListIndex = 0;
}
class _TableElement {
final List<TableRow> rows = <TableRow>[];
}
/// A collection of widgets that should be placed adjacent to (inline with)
/// other inline elements in the same parent block.
///
/// Inline elements can be textual (a/em/strong) represented by [RichText]
/// widgets or images (img) represented by [Image.network] widgets.
///
/// Inline elements can be nested within other inline elements, inheriting their
/// parent's style along with the style of the block they are in.
///
/// When laying out inline widgets, first, any adjacent RichText widgets are
/// merged, then, all inline widgets are enclosed in a parent [Wrap] widget.
class _InlineElement {
_InlineElement(this.tag, {this.style});
final String? tag;
/// Created by merging the style defined for this element's [tag] in the
/// delegate's [MarkdownStyleSheet] with the style of its parent.
final TextStyle? style;
final List<Widget> children = <Widget>[];
}
/// A delegate used by [MarkdownBuilder] to control the widgets it creates.
abstract class MarkdownBuilderDelegate {
/// Returns a gesture recognizer to use for an `a` element with the given
/// text, `href` attribute, and title.
GestureRecognizer createLink(String text, String? href, String title);
/// Returns formatted text to use to display the given contents of a `pre`
/// element.
///
/// The `styleSheet` is the value of [MarkdownBuilder.styleSheet].
TextSpan formatText(MarkdownStyleSheet styleSheet, String code);
}
/// Builds a [Widget] tree from parsed Markdown.
///
/// See also:
///
/// * [Markdown], which is a widget that parses and displays Markdown.
class MarkdownBuilder implements md.NodeVisitor {
/// Creates an object that builds a [Widget] tree from parsed Markdown.
MarkdownBuilder({
required this.delegate,
required this.selectable,
required this.styleSheet,
required this.imageDirectory,
required this.imageBuilder,
required this.checkboxBuilder,
required this.bulletBuilder,
required this.builders,
required this.paddingBuilders,
required this.listItemCrossAxisAlignment,
this.fitContent = false,
this.onTapText,
this.softLineBreak = false,
});
/// A delegate that controls how link and `pre` elements behave.
final MarkdownBuilderDelegate delegate;
/// If true, the text is selectable.
///
/// Defaults to false.
final bool selectable;
/// Defines which [TextStyle] objects to use for each type of element.
final MarkdownStyleSheet styleSheet;
/// The base directory holding images referenced by Img tags with local or network file paths.
final String? imageDirectory;
/// Call when build an image widget.
final MarkdownImageBuilder? imageBuilder;
/// Call when build a checkbox widget.
final MarkdownCheckboxBuilder? checkboxBuilder;
/// Called when building a custom bullet.
final MarkdownBulletBuilder? bulletBuilder;
/// Call when build a custom widget.
final Map<String, MarkdownElementBuilder> builders;
/// Call when build a padding for widget.
final Map<String, MarkdownPaddingBuilder> paddingBuilders;
/// Whether to allow the widget to fit the child content.
final bool fitContent;
/// Controls the cross axis alignment for the bullet and list item content
/// in lists.
///
/// Defaults to [MarkdownListItemCrossAxisAlignment.baseline], which
/// does not allow for intrinsic height measurements.
final MarkdownListItemCrossAxisAlignment listItemCrossAxisAlignment;
/// Default tap handler used when [selectable] is set to true
final VoidCallback? onTapText;
/// The soft line break is used to identify the spaces at the end of aline of
/// text and the leading spaces in the immediately following the line of text.
///
/// Default these spaces are removed in accordance with the Markdown
/// specification on soft line breaks when lines of text are joined.
final bool softLineBreak;
final List<String> _listIndents = <String>[];
final List<_BlockElement> _blocks = <_BlockElement>[];
final List<_TableElement> _tables = <_TableElement>[];
final List<_InlineElement> _inlines = <_InlineElement>[];
final List<GestureRecognizer> _linkHandlers = <GestureRecognizer>[];
String? _currentBlockTag;
String? _lastVisitedTag;
bool _isInBlockquote = false;
/// Returns widgets that display the given Markdown nodes.
///
/// The returned widgets are typically used as children in a [ListView].
List<Widget> build(List<md.Node> nodes) {
_listIndents.clear();
_blocks.clear();
_tables.clear();
_inlines.clear();
_linkHandlers.clear();
_isInBlockquote = false;
_blocks.add(_BlockElement(null));
for (final md.Node node in nodes) {
assert(_blocks.length == 1);
node.accept(this);
}
assert(_tables.isEmpty);
assert(_inlines.isEmpty);
assert(!_isInBlockquote);
return _blocks.single.children;
}
@override
bool visitElementBefore(md.Element element) {
final String tag = element.tag;
_currentBlockTag ??= tag;
_lastVisitedTag = tag;
if (builders.containsKey(tag)) {
builders[tag]!.visitElementBefore(element);
}
if (paddingBuilders.containsKey(tag)) {
paddingBuilders[tag]!.visitElementBefore(element);
}
int? start;
if (_isBlockTag(tag)) {
_addAnonymousBlockIfNeeded();
if (_isListTag(tag)) {
_listIndents.add(tag);
if (element.attributes['start'] != null) {
start = int.parse(element.attributes['start']!) - 1;
}
} else if (tag == 'blockquote') {
_isInBlockquote = true;
} else if (tag == 'table') {
_tables.add(_TableElement());
} else if (tag == 'tr') {
final int length = _tables.single.rows.length;
BoxDecoration? decoration =
styleSheet.tableCellsDecoration as BoxDecoration?;
if (length == 0 || length.isOdd) {
decoration = null;
}
_tables.single.rows.add(TableRow(
decoration: decoration,
// TODO(stuartmorgan): This should be fixed, not suppressed; enabling
// this lint warning exposed that the builder is modifying the
// children of TableRows, even though they are @immutable.
// ignore: prefer_const_literals_to_create_immutables
children: <Widget>[],
));
}
final _BlockElement bElement = _BlockElement(tag);
if (start != null) {
bElement.nextListIndex = start;
}
_blocks.add(bElement);
} else {
if (tag == 'a') {
final String? text = extractTextFromElement(element);
// Don't add empty links
if (text == null) {
return false;
}
final String? destination = element.attributes['href'];
final String title = element.attributes['title'] ?? '';
_linkHandlers.add(
delegate.createLink(text, destination, title),
);
}
_addParentInlineIfNeeded(_blocks.last.tag);
// The Markdown parser passes empty table data tags for blank
// table cells. Insert a text node with an empty string in this
// case for the table cell to get properly created.
if (element.tag == 'td' &&
element.children != null &&
element.children!.isEmpty) {
element.children!.add(md.Text(''));
}
final TextStyle parentStyle = _inlines.last.style!;
_inlines.add(_InlineElement(
tag,
style: parentStyle.merge(styleSheet.styles[tag]),
));
}
return true;
}
/// Returns the text, if any, from [element] and its descendants.
String? extractTextFromElement(md.Node element) {
return element is md.Element && (element.children?.isNotEmpty ?? false)
? element.children!
.map((md.Node e) =>
e is md.Text ? e.text : extractTextFromElement(e))
.join()
: (element is md.Element && (element.attributes.isNotEmpty)
? element.attributes['alt']
: '');
}
@override
void visitText(md.Text text) {
// Don't allow text directly under the root.
if (_blocks.last.tag == null) {
return;
}
_addParentInlineIfNeeded(_blocks.last.tag);
// Define trim text function to remove spaces from text elements in
// accordance with Markdown specifications.
String trimText(String text) {
// The leading spaces pattern is used to identify spaces
// at the beginning of a line of text.
final RegExp leadingSpacesPattern = RegExp(r'^ *');
// The soft line break is used to identify the spaces at the end of a line
// of text and the leading spaces in the immediately following the line
// of text. These spaces are removed in accordance with the Markdown
// specification on soft line breaks when lines of text are joined.
final RegExp softLineBreakPattern = RegExp(r' ?\n *');
// Leading spaces following a hard line break are ignored.
// https://github.github.com/gfm/#example-657
// Leading spaces in paragraph or list item are ignored
// https://github.github.com/gfm/#example-192
// https://github.github.com/gfm/#example-236
if (const <String>['ul', 'ol', 'p', 'br'].contains(_lastVisitedTag)) {
text = text.replaceAll(leadingSpacesPattern, '');
}
if (softLineBreak) {
return text;
}
return text.replaceAll(softLineBreakPattern, ' ');
}
Widget? child;
if (_blocks.isNotEmpty && builders.containsKey(_blocks.last.tag)) {
child = builders[_blocks.last.tag!]!
.visitText(text, styleSheet.styles[_blocks.last.tag!]);
} else if (_blocks.last.tag == 'pre') {
child = Scrollbar(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
padding: styleSheet.codeblockPadding,
child: _buildRichText(delegate.formatText(styleSheet, text.text)),
),
);
} else {
child = _buildRichText(
TextSpan(
style: _isInBlockquote
? styleSheet.blockquote!.merge(_inlines.last.style)
: _inlines.last.style,
text: _isInBlockquote ? text.text : trimText(text.text),
recognizer: _linkHandlers.isNotEmpty ? _linkHandlers.last : null,
),
textAlign: _textAlignForBlockTag(_currentBlockTag),
);
}
if (child != null) {
_inlines.last.children.add(child);
}
_lastVisitedTag = null;
}
@override
void visitElementAfter(md.Element element) {
final String tag = element.tag;
if (_isBlockTag(tag)) {
_addAnonymousBlockIfNeeded();
final _BlockElement current = _blocks.removeLast();
Widget child;
if (current.children.isNotEmpty) {
child = Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: fitContent
? CrossAxisAlignment.start
: CrossAxisAlignment.stretch,
children: current.children,
);
} else {
child = const SizedBox();
}
if (_isListTag(tag)) {
assert(_listIndents.isNotEmpty);
_listIndents.removeLast();
} else if (tag == 'li') {
if (_listIndents.isNotEmpty) {
if (element.children!.isEmpty) {
element.children!.add(md.Text(''));
}
Widget bullet;
final dynamic el = element.children![0];
if (el is md.Element && el.attributes['type'] == 'checkbox') {
final bool val = el.attributes.containsKey('checked');
bullet = _buildCheckbox(val);
} else {
bullet = _buildBullet(_listIndents.last);
}
child = Row(
mainAxisSize: fitContent ? MainAxisSize.min : MainAxisSize.max,
textBaseline: listItemCrossAxisAlignment ==
MarkdownListItemCrossAxisAlignment.start
? null
: TextBaseline.alphabetic,
crossAxisAlignment: listItemCrossAxisAlignment ==
MarkdownListItemCrossAxisAlignment.start
? CrossAxisAlignment.start
: CrossAxisAlignment.baseline,
children: <Widget>[
SizedBox(
width: styleSheet.listIndent! +
styleSheet.listBulletPadding!.left +
styleSheet.listBulletPadding!.right,
child: bullet,
),
Flexible(
fit: fitContent ? FlexFit.loose : FlexFit.tight,
child: child,
)
],
);
}
} else if (tag == 'table') {
child = Table(
defaultColumnWidth: styleSheet.tableColumnWidth!,
defaultVerticalAlignment: TableCellVerticalAlignment.middle,
border: styleSheet.tableBorder,
children: _tables.removeLast().rows,
);
} else if (tag == 'blockquote') {
_isInBlockquote = false;
child = DecoratedBox(
decoration: styleSheet.blockquoteDecoration!,
child: Padding(
padding: styleSheet.blockquotePadding!,
child: child,
),
);
} else if (tag == 'pre') {
child = DecoratedBox(
decoration: styleSheet.codeblockDecoration!,
child: child,
);
} else if (tag == 'hr') {
child = Container(decoration: styleSheet.horizontalRuleDecoration);
}
_addBlockChild(child);
} else {
final _InlineElement current = _inlines.removeLast();
final _InlineElement parent = _inlines.last;
EdgeInsets padding = EdgeInsets.zero;
if (paddingBuilders.containsKey(tag)) {
padding = paddingBuilders[tag]!.getPadding();
}
if (builders.containsKey(tag)) {
final Widget? child =
builders[tag]!.visitElementAfter(element, styleSheet.styles[tag]);
if (child != null) {
current.children[0] = child;
}
} else if (tag == 'img') {
// create an image widget for this image
current.children.add(_buildPadding(
padding,
_buildImage(
element.attributes['src']!,
element.attributes['title'],
element.attributes['alt'],
),
));
} else if (tag == 'br') {
current.children.add(_buildRichText(const TextSpan(text: '\n')));
} else if (tag == 'th' || tag == 'td') {
TextAlign? align;
final String? alignAttribute = element.attributes['align'];
if (alignAttribute == null) {
align = tag == 'th' ? styleSheet.tableHeadAlign : TextAlign.left;
} else {
switch (alignAttribute) {
case 'left':
align = TextAlign.left;
break;
case 'center':
align = TextAlign.center;
break;
case 'right':
align = TextAlign.right;
break;
}
}
final Widget child = _buildTableCell(
_mergeInlineChildren(current.children, align),
textAlign: align,
);
_ambiguate(_tables.single.rows.last.children)!.add(child);
} else if (tag == 'a') {
_linkHandlers.removeLast();
}
if (current.children.isNotEmpty) {
parent.children.addAll(current.children);
}
}
if (_currentBlockTag == tag) {
_currentBlockTag = null;
}
_lastVisitedTag = tag;
}
Widget _buildImage(String src, String? title, String? alt) {
final List<String> parts = src.split('#');
if (parts.isEmpty) {
return const SizedBox();
}
final String path = parts.first;
double? width;
double? height;
if (parts.length == 2) {
final List<String> dimensions = parts.last.split('x');
if (dimensions.length == 2) {
width = double.parse(dimensions[0]);
height = double.parse(dimensions[1]);
}
}
final Uri uri = Uri.parse(path);
Widget child;
if (imageBuilder != null) {
child = imageBuilder!(uri, title, alt);
} else {
child = kDefaultImageBuilder(uri, imageDirectory, width, height);
}
if (_linkHandlers.isNotEmpty) {
final TapGestureRecognizer recognizer =
_linkHandlers.last as TapGestureRecognizer;
return GestureDetector(onTap: recognizer.onTap, child: child);
} else {
return child;
}
}
Widget _buildCheckbox(bool checked) {
if (checkboxBuilder != null) {
return checkboxBuilder!(checked);
}
return Padding(
padding: styleSheet.listBulletPadding!,
child: Icon(
checked ? Icons.check_box : Icons.check_box_outline_blank,
size: styleSheet.checkbox!.fontSize,
color: styleSheet.checkbox!.color,
),
);
}
Widget _buildBullet(String listTag) {
final int index = _blocks.last.nextListIndex;
final bool isUnordered = listTag == 'ul';
if (bulletBuilder != null) {
return Padding(
padding: styleSheet.listBulletPadding!,
child: bulletBuilder!(index,
isUnordered ? BulletStyle.unorderedList : BulletStyle.orderedList),
);
}
if (isUnordered) {
return Padding(
padding: styleSheet.listBulletPadding!,
child: Text(
'•',
textAlign: TextAlign.center,
style: styleSheet.listBullet,
),
);
}
return Padding(
padding: styleSheet.listBulletPadding!,
child: Text(
'${index + 1}.',
textAlign: TextAlign.right,
style: styleSheet.listBullet,
),
);
}
Widget _buildTableCell(List<Widget?> children, {TextAlign? textAlign}) {
return TableCell(
child: Padding(
padding: styleSheet.tableCellsPadding!,
child: DefaultTextStyle(
style: styleSheet.tableBody!,
textAlign: textAlign,
child: Wrap(children: children as List<Widget>),
),
),
);
}
Widget _buildPadding(EdgeInsets padding, Widget child) {
if (padding == EdgeInsets.zero) {
return child;
}
return Padding(padding: padding, child: child);
}
void _addParentInlineIfNeeded(String? tag) {
if (_inlines.isEmpty) {
_inlines.add(_InlineElement(
tag,
style: styleSheet.styles[tag!],
));
}
}
void _addBlockChild(Widget child) {
final _BlockElement parent = _blocks.last;
if (parent.children.isNotEmpty) {
parent.children.add(SizedBox(height: styleSheet.blockSpacing));
}
parent.children.add(child);
parent.nextListIndex += 1;
}
void _addAnonymousBlockIfNeeded() {
if (_inlines.isEmpty) {
return;
}
WrapAlignment blockAlignment = WrapAlignment.start;
TextAlign textAlign = TextAlign.start;
EdgeInsets textPadding = EdgeInsets.zero;
if (_isBlockTag(_currentBlockTag)) {
blockAlignment = _wrapAlignmentForBlockTag(_currentBlockTag);
textAlign = _textAlignForBlockTag(_currentBlockTag);
textPadding = _textPaddingForBlockTag(_currentBlockTag);
if (paddingBuilders.containsKey(_currentBlockTag)) {
textPadding = paddingBuilders[_currentBlockTag]!.getPadding();
}
}
final _InlineElement inline = _inlines.single;
if (inline.children.isNotEmpty) {
final List<Widget> mergedInlines = _mergeInlineChildren(
inline.children,
textAlign,
);
final Wrap wrap = Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
alignment: blockAlignment,
children: mergedInlines,
);
if (textPadding == EdgeInsets.zero) {
_addBlockChild(wrap);
} else {
final Padding padding = Padding(padding: textPadding, child: wrap);
_addBlockChild(padding);
}
_inlines.clear();
}
}
/// Merges adjacent [TextSpan] children
List<Widget> _mergeInlineChildren(
List<Widget> children,
TextAlign? textAlign,
) {
final List<Widget> mergedTexts = <Widget>[];
for (final Widget child in children) {
if (mergedTexts.isNotEmpty &&
mergedTexts.last is RichText &&
child is RichText) {
final RichText previous = mergedTexts.removeLast() as RichText;
final TextSpan previousTextSpan = previous.text as TextSpan;
final List<TextSpan> children = previousTextSpan.children != null
? previousTextSpan.children!
.map((InlineSpan span) => span is! TextSpan
? TextSpan(children: <InlineSpan>[span])
: span)
.toList()
: <TextSpan>[previousTextSpan];
children.add(child.text as TextSpan);
final TextSpan? mergedSpan = _mergeSimilarTextSpans(children);
mergedTexts.add(_buildRichText(
mergedSpan,
textAlign: textAlign,
));
} else if (mergedTexts.isNotEmpty &&
mergedTexts.last is SelectableText &&
child is SelectableText) {
final SelectableText previous =
mergedTexts.removeLast() as SelectableText;
final TextSpan previousTextSpan = previous.textSpan!;
final List<TextSpan> children = previousTextSpan.children != null
? List<TextSpan>.from(previousTextSpan.children!)
: <TextSpan>[previousTextSpan];
if (child.textSpan != null) {
children.add(child.textSpan!);
}
final TextSpan? mergedSpan = _mergeSimilarTextSpans(children);
mergedTexts.add(
_buildRichText(
mergedSpan,
textAlign: textAlign,
),
);
} else {
mergedTexts.add(child);
}
}
return mergedTexts;
}
TextAlign _textAlignForBlockTag(String? blockTag) {
final WrapAlignment wrapAlignment = _wrapAlignmentForBlockTag(blockTag);
switch (wrapAlignment) {
case WrapAlignment.start:
return TextAlign.start;
case WrapAlignment.center:
return TextAlign.center;
case WrapAlignment.end:
return TextAlign.end;
case WrapAlignment.spaceAround:
return TextAlign.justify;
case WrapAlignment.spaceBetween:
return TextAlign.justify;
case WrapAlignment.spaceEvenly:
return TextAlign.justify;
}
}
WrapAlignment _wrapAlignmentForBlockTag(String? blockTag) {
switch (blockTag) {
case 'p':
return styleSheet.textAlign;
case 'h1':
return styleSheet.h1Align;
case 'h2':
return styleSheet.h2Align;
case 'h3':
return styleSheet.h3Align;
case 'h4':
return styleSheet.h4Align;
case 'h5':
return styleSheet.h5Align;
case 'h6':
return styleSheet.h6Align;
case 'ul':
return styleSheet.unorderedListAlign;
case 'ol':
return styleSheet.orderedListAlign;
case 'blockquote':
return styleSheet.blockquoteAlign;
case 'pre':
return styleSheet.codeblockAlign;
case 'hr':
break;
case 'li':
break;
}
return WrapAlignment.start;
}
EdgeInsets _textPaddingForBlockTag(String? blockTag) {
switch (blockTag) {
case 'p':
return styleSheet.pPadding!;
case 'h1':
return styleSheet.h1Padding!;
case 'h2':
return styleSheet.h2Padding!;
case 'h3':
return styleSheet.h3Padding!;
case 'h4':
return styleSheet.h4Padding!;
case 'h5':
return styleSheet.h5Padding!;
case 'h6':
return styleSheet.h6Padding!;
}
return EdgeInsets.zero;
}
/// Combine text spans with equivalent properties into a single span.
TextSpan? _mergeSimilarTextSpans(List<TextSpan>? textSpans) {
if (textSpans == null || textSpans.length < 2) {
return TextSpan(children: textSpans);
}
final List<TextSpan> mergedSpans = <TextSpan>[textSpans.first];
for (int index = 1; index < textSpans.length; index++) {
final TextSpan nextChild = textSpans[index];
if (nextChild.recognizer == mergedSpans.last.recognizer &&
nextChild.semanticsLabel == mergedSpans.last.semanticsLabel &&
nextChild.style == mergedSpans.last.style) {
final TextSpan previous = mergedSpans.removeLast();
mergedSpans.add(TextSpan(
text: previous.toPlainText() + nextChild.toPlainText(),
recognizer: previous.recognizer,
semanticsLabel: previous.semanticsLabel,
style: previous.style,
));
} else {
mergedSpans.add(nextChild);
}
}
// When the mergered spans compress into a single TextSpan return just that
// TextSpan, otherwise bundle the set of TextSpans under a single parent.
return mergedSpans.length == 1
? mergedSpans.first
: TextSpan(children: mergedSpans);
}
Widget _buildRichText(TextSpan? text, {TextAlign? textAlign, String? key}) {
//Adding a unique key prevents the problem of using the same link handler for text spans with the same text
final Key k = key == null ? UniqueKey() : Key(key);
if (selectable) {
return SelectableText.rich(
text!,
textScaleFactor: styleSheet.textScaleFactor,
textAlign: textAlign ?? TextAlign.start,
onTap: onTapText,
key: k,
);
} else {
return RichText(
text: text!,
textScaleFactor: styleSheet.textScaleFactor!,
textAlign: textAlign ?? TextAlign.start,
key: k,
);
}
}
/// This allows a value of type T or T? to be treated as a value of type T?.
///
/// We use this so that APIs that have become non-nullable can still be used
/// with `!` and `?` on the stable branch.
T? _ambiguate<T>(T? value) => value;
}
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
/// Defines which [TextStyle] objects to use for which Markdown elements.
class MarkdownStyleSheet {
/// Creates an explicit mapping of [TextStyle] objects to Markdown elements.
MarkdownStyleSheet({
this.a,
this.p,
this.pPadding,
this.code,
this.h1,
this.h1Padding,
this.h2,
this.h2Padding,
this.h3,
this.h3Padding,
this.h4,
this.h4Padding,
this.h5,
this.h5Padding,
this.h6,
this.h6Padding,
this.em,
this.strong,
this.del,
this.blockquote,
this.img,
this.checkbox,
this.blockSpacing,
this.listIndent,
this.listBullet,
this.listBulletPadding,
this.tableHead,
this.tableBody,
this.tableHeadAlign,
this.tableBorder,
this.tableColumnWidth,
this.tableCellsPadding,
this.tableCellsDecoration,
this.blockquotePadding,
this.blockquoteDecoration,
this.codeblockPadding,
this.codeblockDecoration,
this.horizontalRuleDecoration,
this.textAlign = WrapAlignment.start,
this.h1Align = WrapAlignment.start,
this.h2Align = WrapAlignment.start,
this.h3Align = WrapAlignment.start,
this.h4Align = WrapAlignment.start,
this.h5Align = WrapAlignment.start,
this.h6Align = WrapAlignment.start,
this.unorderedListAlign = WrapAlignment.start,
this.orderedListAlign = WrapAlignment.start,
this.blockquoteAlign = WrapAlignment.start,
this.codeblockAlign = WrapAlignment.start,
this.textScaleFactor,
}) : _styles = <String, TextStyle?>{
'a': a,
'p': p,
'li': p,
'code': code,
'pre': p,
'h1': h1,
'h2': h2,
'h3': h3,
'h4': h4,
'h5': h5,
'h6': h6,
'em': em,
'strong': strong,
'del': del,
'blockquote': blockquote,
'img': img,
'table': p,
'th': tableHead,
'tr': tableBody,
'td': tableBody,
};
/// Creates a [MarkdownStyleSheet] from the [TextStyle]s in the provided [ThemeData].
factory MarkdownStyleSheet.fromTheme(ThemeData theme) {
assert(theme.textTheme.bodyMedium?.fontSize != null);
return MarkdownStyleSheet(
a: const TextStyle(color: Colors.blue),
p: theme.textTheme.bodyMedium,
pPadding: EdgeInsets.zero,
code: theme.textTheme.bodyMedium!.copyWith(
backgroundColor: theme.cardTheme.color ?? theme.cardColor,
fontFamily: 'monospace',
fontSize: theme.textTheme.bodyMedium!.fontSize! * 0.85,
),
h1: theme.textTheme.headlineSmall,
h1Padding: EdgeInsets.zero,
h2: theme.textTheme.titleLarge,
h2Padding: EdgeInsets.zero,
h3: theme.textTheme.titleMedium,
h3Padding: EdgeInsets.zero,
h4: theme.textTheme.bodyLarge,
h4Padding: EdgeInsets.zero,
h5: theme.textTheme.bodyLarge,
h5Padding: EdgeInsets.zero,
h6: theme.textTheme.bodyLarge,
h6Padding: EdgeInsets.zero,
em: const TextStyle(fontStyle: FontStyle.italic),
strong: const TextStyle(fontWeight: FontWeight.bold),
del: const TextStyle(decoration: TextDecoration.lineThrough),
blockquote: theme.textTheme.bodyMedium,
img: theme.textTheme.bodyMedium,
checkbox: theme.textTheme.bodyMedium!.copyWith(
color: theme.primaryColor,
),
blockSpacing: 8.0,
listIndent: 24.0,
listBullet: theme.textTheme.bodyMedium,
listBulletPadding: const EdgeInsets.only(right: 4),
tableHead: const TextStyle(fontWeight: FontWeight.w600),
tableBody: theme.textTheme.bodyMedium,
tableHeadAlign: TextAlign.center,
tableBorder: TableBorder.all(
color: theme.dividerColor,
),
tableColumnWidth: const FlexColumnWidth(),
tableCellsPadding: const EdgeInsets.fromLTRB(16, 8, 16, 8),
tableCellsDecoration: const BoxDecoration(),
blockquotePadding: const EdgeInsets.all(8.0),
blockquoteDecoration: BoxDecoration(
color: Colors.blue.shade100,
borderRadius: BorderRadius.circular(2.0),
),
codeblockPadding: const EdgeInsets.all(8.0),
codeblockDecoration: BoxDecoration(
color: theme.cardTheme.color ?? theme.cardColor,
borderRadius: BorderRadius.circular(2.0),
),
horizontalRuleDecoration: BoxDecoration(
border: Border(
top: BorderSide(
width: 5.0,
color: theme.dividerColor,
),
),
),
);
}
/// Creates a [MarkdownStyleSheet] from the [TextStyle]s in the provided [CupertinoThemeData].
factory MarkdownStyleSheet.fromCupertinoTheme(CupertinoThemeData theme) {
assert(theme.textTheme.textStyle.fontSize != null);
return MarkdownStyleSheet(
a: theme.textTheme.textStyle.copyWith(
color: theme.brightness == Brightness.dark
? CupertinoColors.link.darkColor
: CupertinoColors.link.color,
),
p: theme.textTheme.textStyle,
pPadding: EdgeInsets.zero,
code: theme.textTheme.textStyle.copyWith(
backgroundColor: theme.brightness == Brightness.dark
? CupertinoColors.systemGrey6.darkColor
: CupertinoColors.systemGrey6.color,
fontFamily: 'monospace',
fontSize: theme.textTheme.textStyle.fontSize! * 0.85,
),
h1: theme.textTheme.textStyle.copyWith(
fontWeight: FontWeight.w500,
fontSize: theme.textTheme.textStyle.fontSize! + 10,
),
h1Padding: EdgeInsets.zero,
h2: theme.textTheme.textStyle.copyWith(
fontWeight: FontWeight.w500,
fontSize: theme.textTheme.textStyle.fontSize! + 8,
),
h2Padding: EdgeInsets.zero,
h3: theme.textTheme.textStyle.copyWith(
fontWeight: FontWeight.w500,
fontSize: theme.textTheme.textStyle.fontSize! + 6,
),
h3Padding: EdgeInsets.zero,
h4: theme.textTheme.textStyle.copyWith(
fontWeight: FontWeight.w500,
fontSize: theme.textTheme.textStyle.fontSize! + 4,
),
h4Padding: EdgeInsets.zero,
h5: theme.textTheme.textStyle.copyWith(
fontWeight: FontWeight.w500,
fontSize: theme.textTheme.textStyle.fontSize! + 2,
),
h5Padding: EdgeInsets.zero,
h6: theme.textTheme.textStyle.copyWith(
fontWeight: FontWeight.w500,
),
h6Padding: EdgeInsets.zero,
em: theme.textTheme.textStyle.copyWith(
fontStyle: FontStyle.italic,
),
strong: theme.textTheme.textStyle.copyWith(
fontWeight: FontWeight.bold,
),
del: theme.textTheme.textStyle.copyWith(
decoration: TextDecoration.lineThrough,
),
blockquote: theme.textTheme.textStyle,
img: theme.textTheme.textStyle,
checkbox: theme.textTheme.textStyle.copyWith(
color: theme.primaryColor,
),
blockSpacing: 8,
listIndent: 24,
listBullet: theme.textTheme.textStyle,
listBulletPadding: const EdgeInsets.only(right: 4),
tableHead: theme.textTheme.textStyle.copyWith(
fontWeight: FontWeight.w600,
),
tableBody: theme.textTheme.textStyle,
tableHeadAlign: TextAlign.center,
tableBorder: TableBorder.all(color: CupertinoColors.separator, width: 0),
tableColumnWidth: const FlexColumnWidth(),
tableCellsPadding: const EdgeInsets.fromLTRB(16, 8, 16, 8),
tableCellsDecoration: BoxDecoration(
color: theme.brightness == Brightness.dark
? CupertinoColors.systemGrey6.darkColor
: CupertinoColors.systemGrey6.color,
),
blockquotePadding: const EdgeInsets.all(16),
blockquoteDecoration: BoxDecoration(
color: theme.brightness == Brightness.dark
? CupertinoColors.systemGrey6.darkColor
: CupertinoColors.systemGrey6.color,
border: Border(
left: BorderSide(
color: theme.brightness == Brightness.dark
? CupertinoColors.systemGrey4.darkColor
: CupertinoColors.systemGrey4.color,
width: 4,
),
),
),
codeblockPadding: const EdgeInsets.all(8),
codeblockDecoration: BoxDecoration(
color: theme.brightness == Brightness.dark
? CupertinoColors.systemGrey6.darkColor
: CupertinoColors.systemGrey6.color,
),
horizontalRuleDecoration: BoxDecoration(
border: Border(
top: BorderSide(
color: theme.brightness == Brightness.dark
? CupertinoColors.systemGrey4.darkColor
: CupertinoColors.systemGrey4.color,
),
),
),
);
}
/// Creates a [MarkdownStyle] from the [TextStyle]s in the provided [ThemeData].
///
/// This constructor uses larger fonts for the headings than in
/// [MarkdownStyle.fromTheme].
factory MarkdownStyleSheet.largeFromTheme(ThemeData theme) {
return MarkdownStyleSheet(
a: const TextStyle(color: Colors.blue),
p: theme.textTheme.bodyMedium,
pPadding: EdgeInsets.zero,
code: theme.textTheme.bodyMedium!.copyWith(
backgroundColor: theme.cardTheme.color ?? theme.cardColor,
fontFamily: 'monospace',
fontSize: theme.textTheme.bodyMedium!.fontSize! * 0.85,
),
h1: theme.textTheme.displayMedium,
h1Padding: EdgeInsets.zero,
h2: theme.textTheme.displaySmall,
h2Padding: EdgeInsets.zero,
h3: theme.textTheme.headlineMedium,
h3Padding: EdgeInsets.zero,
h4: theme.textTheme.headlineSmall,
h4Padding: EdgeInsets.zero,
h5: theme.textTheme.titleLarge,
h5Padding: EdgeInsets.zero,
h6: theme.textTheme.titleMedium,
h6Padding: EdgeInsets.zero,
em: const TextStyle(fontStyle: FontStyle.italic),
strong: const TextStyle(fontWeight: FontWeight.bold),
del: const TextStyle(decoration: TextDecoration.lineThrough),
blockquote: theme.textTheme.bodyMedium,
img: theme.textTheme.bodyMedium,
checkbox: theme.textTheme.bodyMedium!.copyWith(
color: theme.primaryColor,
),
blockSpacing: 8.0,
listIndent: 24.0,
listBullet: theme.textTheme.bodyMedium,
listBulletPadding: const EdgeInsets.only(right: 4),
tableHead: const TextStyle(fontWeight: FontWeight.w600),
tableBody: theme.textTheme.bodyMedium,
tableHeadAlign: TextAlign.center,
tableBorder: TableBorder.all(
color: theme.dividerColor,
),
tableColumnWidth: const FlexColumnWidth(),
tableCellsPadding: const EdgeInsets.fromLTRB(16, 8, 16, 8),
tableCellsDecoration: const BoxDecoration(),
blockquotePadding: const EdgeInsets.all(8.0),
blockquoteDecoration: BoxDecoration(
color: Colors.blue.shade100,
borderRadius: BorderRadius.circular(2.0),
),
codeblockPadding: const EdgeInsets.all(8.0),
codeblockDecoration: BoxDecoration(
color: theme.cardTheme.color ?? theme.cardColor,
borderRadius: BorderRadius.circular(2.0),
),
horizontalRuleDecoration: BoxDecoration(
border: Border(
top: BorderSide(
width: 5.0,
color: theme.dividerColor,
),
),
),
);
}
/// Creates a [MarkdownStyleSheet] based on the current style, with the
/// provided parameters overridden.
MarkdownStyleSheet copyWith({
TextStyle? a,
TextStyle? p,
EdgeInsets? pPadding,
TextStyle? code,
TextStyle? h1,
EdgeInsets? h1Padding,
TextStyle? h2,
EdgeInsets? h2Padding,
TextStyle? h3,
EdgeInsets? h3Padding,
TextStyle? h4,
EdgeInsets? h4Padding,
TextStyle? h5,
EdgeInsets? h5Padding,
TextStyle? h6,
EdgeInsets? h6Padding,
TextStyle? em,
TextStyle? strong,
TextStyle? del,
TextStyle? blockquote,
TextStyle? img,
TextStyle? checkbox,
double? blockSpacing,
double? listIndent,
TextStyle? listBullet,
EdgeInsets? listBulletPadding,
TextStyle? tableHead,
TextStyle? tableBody,
TextAlign? tableHeadAlign,
TableBorder? tableBorder,
TableColumnWidth? tableColumnWidth,
EdgeInsets? tableCellsPadding,
Decoration? tableCellsDecoration,
EdgeInsets? blockquotePadding,
Decoration? blockquoteDecoration,
EdgeInsets? codeblockPadding,
Decoration? codeblockDecoration,
Decoration? horizontalRuleDecoration,
WrapAlignment? textAlign,
WrapAlignment? h1Align,
WrapAlignment? h2Align,
WrapAlignment? h3Align,
WrapAlignment? h4Align,
WrapAlignment? h5Align,
WrapAlignment? h6Align,
WrapAlignment? unorderedListAlign,
WrapAlignment? orderedListAlign,
WrapAlignment? blockquoteAlign,
WrapAlignment? codeblockAlign,
double? textScaleFactor,
}) {
return MarkdownStyleSheet(
a: a ?? this.a,
p: p ?? this.p,
pPadding: pPadding ?? this.pPadding,
code: code ?? this.code,
h1: h1 ?? this.h1,
h1Padding: h1Padding ?? this.h1Padding,
h2: h2 ?? this.h2,
h2Padding: h2Padding ?? this.h2Padding,
h3: h3 ?? this.h3,
h3Padding: h3Padding ?? this.h3Padding,
h4: h4 ?? this.h4,
h4Padding: h4Padding ?? this.h4Padding,
h5: h5 ?? this.h5,
h5Padding: h5Padding ?? this.h5Padding,
h6: h6 ?? this.h6,
h6Padding: h6Padding ?? this.h6Padding,
em: em ?? this.em,
strong: strong ?? this.strong,
del: del ?? this.del,
blockquote: blockquote ?? this.blockquote,
img: img ?? this.img,
checkbox: checkbox ?? this.checkbox,
blockSpacing: blockSpacing ?? this.blockSpacing,
listIndent: listIndent ?? this.listIndent,
listBullet: listBullet ?? this.listBullet,
listBulletPadding: listBulletPadding ?? this.listBulletPadding,
tableHead: tableHead ?? this.tableHead,
tableBody: tableBody ?? this.tableBody,
tableHeadAlign: tableHeadAlign ?? this.tableHeadAlign,
tableBorder: tableBorder ?? this.tableBorder,
tableColumnWidth: tableColumnWidth ?? this.tableColumnWidth,
tableCellsPadding: tableCellsPadding ?? this.tableCellsPadding,
tableCellsDecoration: tableCellsDecoration ?? this.tableCellsDecoration,
blockquotePadding: blockquotePadding ?? this.blockquotePadding,
blockquoteDecoration: blockquoteDecoration ?? this.blockquoteDecoration,
codeblockPadding: codeblockPadding ?? this.codeblockPadding,
codeblockDecoration: codeblockDecoration ?? this.codeblockDecoration,
horizontalRuleDecoration:
horizontalRuleDecoration ?? this.horizontalRuleDecoration,
textAlign: textAlign ?? this.textAlign,
h1Align: h1Align ?? this.h1Align,
h2Align: h2Align ?? this.h2Align,
h3Align: h3Align ?? this.h3Align,
h4Align: h4Align ?? this.h4Align,
h5Align: h5Align ?? this.h5Align,
h6Align: h6Align ?? this.h6Align,
unorderedListAlign: unorderedListAlign ?? this.unorderedListAlign,
orderedListAlign: orderedListAlign ?? this.orderedListAlign,
blockquoteAlign: blockquoteAlign ?? this.blockquoteAlign,
codeblockAlign: codeblockAlign ?? this.codeblockAlign,
textScaleFactor: textScaleFactor ?? this.textScaleFactor,
);
}
/// Returns a new text style that is a combination of this style and the given
/// [other] style.
MarkdownStyleSheet merge(MarkdownStyleSheet? other) {
if (other == null) {
return this;
}
return copyWith(
a: a!.merge(other.a),
p: p!.merge(other.p),
pPadding: other.pPadding,
code: code!.merge(other.code),
h1: h1!.merge(other.h1),
h1Padding: other.h1Padding,
h2: h2!.merge(other.h2),
h2Padding: other.h2Padding,
h3: h3!.merge(other.h3),
h3Padding: other.h3Padding,
h4: h4!.merge(other.h4),
h4Padding: other.h4Padding,
h5: h5!.merge(other.h5),
h5Padding: other.h5Padding,
h6: h6!.merge(other.h6),
h6Padding: other.h6Padding,
em: em!.merge(other.em),
strong: strong!.merge(other.strong),
del: del!.merge(other.del),
blockquote: blockquote!.merge(other.blockquote),
img: img!.merge(other.img),
checkbox: checkbox!.merge(other.checkbox),
blockSpacing: other.blockSpacing,
listIndent: other.listIndent,
listBullet: listBullet!.merge(other.listBullet),
listBulletPadding: other.listBulletPadding,
tableHead: tableHead!.merge(other.tableHead),
tableBody: tableBody!.merge(other.tableBody),
tableHeadAlign: other.tableHeadAlign,
tableBorder: other.tableBorder,
tableColumnWidth: other.tableColumnWidth,
tableCellsPadding: other.tableCellsPadding,
tableCellsDecoration: other.tableCellsDecoration,
blockquotePadding: other.blockquotePadding,
blockquoteDecoration: other.blockquoteDecoration,
codeblockPadding: other.codeblockPadding,
codeblockDecoration: other.codeblockDecoration,
horizontalRuleDecoration: other.horizontalRuleDecoration,
textAlign: other.textAlign,
h1Align: other.h1Align,
h2Align: other.h2Align,
h3Align: other.h3Align,
h4Align: other.h4Align,
h5Align: other.h5Align,
h6Align: other.h6Align,
unorderedListAlign: other.unorderedListAlign,
orderedListAlign: other.orderedListAlign,
blockquoteAlign: other.blockquoteAlign,
codeblockAlign: other.codeblockAlign,
textScaleFactor: other.textScaleFactor,
);
}
/// The [TextStyle] to use for `a` elements.
final TextStyle? a;
/// The [TextStyle] to use for `p` elements.
final TextStyle? p;
/// The padding to use for `p` elements.
final EdgeInsets? pPadding;
/// The [TextStyle] to use for `code` elements.
final TextStyle? code;
/// The [TextStyle] to use for `h1` elements.
final TextStyle? h1;
/// The padding to use for `h1` elements.
final EdgeInsets? h1Padding;
/// The [TextStyle] to use for `h2` elements.
final TextStyle? h2;
/// The padding to use for `h2` elements.
final EdgeInsets? h2Padding;
/// The [TextStyle] to use for `h3` elements.
final TextStyle? h3;
/// The padding to use for `h3` elements.
final EdgeInsets? h3Padding;
/// The [TextStyle] to use for `h4` elements.
final TextStyle? h4;
/// The padding to use for `h4` elements.
final EdgeInsets? h4Padding;
/// The [TextStyle] to use for `h5` elements.
final TextStyle? h5;
/// The padding to use for `h5` elements.
final EdgeInsets? h5Padding;
/// The [TextStyle] to use for `h6` elements.
final TextStyle? h6;
/// The padding to use for `h6` elements.
final EdgeInsets? h6Padding;
/// The [TextStyle] to use for `em` elements.
final TextStyle? em;
/// The [TextStyle] to use for `strong` elements.
final TextStyle? strong;
/// The [TextStyle] to use for `del` elements.
final TextStyle? del;
/// The [TextStyle] to use for `blockquote` elements.
final TextStyle? blockquote;
/// The [TextStyle] to use for `img` elements.
final TextStyle? img;
/// The [TextStyle] to use for `input` elements.
final TextStyle? checkbox;
/// The amount of vertical space to use between block-level elements.
final double? blockSpacing;
/// The amount of horizontal space to indent list items.
final double? listIndent;
/// The [TextStyle] to use for bullets.
final TextStyle? listBullet;
/// The padding to use for bullets.
final EdgeInsets? listBulletPadding;
/// The [TextStyle] to use for `th` elements.
final TextStyle? tableHead;
/// The [TextStyle] to use for `td` elements.
final TextStyle? tableBody;
/// The [TextAlign] to use for `th` elements.
final TextAlign? tableHeadAlign;
/// The [TableBorder] to use for `table` elements.
final TableBorder? tableBorder;
/// The [TableColumnWidth] to use for `th` and `td` elements.
final TableColumnWidth? tableColumnWidth;
/// The padding to use for `th` and `td` elements.
final EdgeInsets? tableCellsPadding;
/// The decoration to use for `th` and `td` elements.
final Decoration? tableCellsDecoration;
/// The padding to use for `blockquote` elements.
final EdgeInsets? blockquotePadding;
/// The decoration to use behind `blockquote` elements.
final Decoration? blockquoteDecoration;
/// The padding to use for `pre` elements.
final EdgeInsets? codeblockPadding;
/// The decoration to use behind for `pre` elements.
final Decoration? codeblockDecoration;
/// The decoration to use for `hr` elements.
final Decoration? horizontalRuleDecoration;
/// The [WrapAlignment] to use for normal text. Defaults to start.
final WrapAlignment textAlign;
/// The [WrapAlignment] to use for h1 text. Defaults to start.
final WrapAlignment h1Align;
/// The [WrapAlignment] to use for h2 text. Defaults to start.
final WrapAlignment h2Align;
/// The [WrapAlignment] to use for h3 text. Defaults to start.
final WrapAlignment h3Align;
/// The [WrapAlignment] to use for h4 text. Defaults to start.
final WrapAlignment h4Align;
/// The [WrapAlignment] to use for h5 text. Defaults to start.
final WrapAlignment h5Align;
/// The [WrapAlignment] to use for h6 text. Defaults to start.
final WrapAlignment h6Align;
/// The [WrapAlignment] to use for an unordered list. Defaults to start.
final WrapAlignment unorderedListAlign;
/// The [WrapAlignment] to use for an ordered list. Defaults to start.
final WrapAlignment orderedListAlign;
/// The [WrapAlignment] to use for a blockquote. Defaults to start.
final WrapAlignment blockquoteAlign;
/// The [WrapAlignment] to use for a code block. Defaults to start.
final WrapAlignment codeblockAlign;
/// The text scale factor to use in textual elements
final double? textScaleFactor;
/// A [Map] from element name to the corresponding [TextStyle] object.
Map<String, TextStyle?> get styles => _styles;
Map<String, TextStyle?> _styles;
@override
// ignore: avoid_equals_and_hash_code_on_mutable_classes
bool operator ==(dynamic other) {
if (identical(this, other)) {
return true;
}
if (other.runtimeType != MarkdownStyleSheet) {
return false;
}
return other is MarkdownStyleSheet &&
other.a == a &&
other.p == p &&
other.pPadding == pPadding &&
other.code == code &&
other.h1 == h1 &&
other.h1Padding == h1Padding &&
other.h2 == h2 &&
other.h2Padding == h2Padding &&
other.h3 == h3 &&
other.h3Padding == h3Padding &&
other.h4 == h4 &&
other.h4Padding == h4Padding &&
other.h5 == h5 &&
other.h5Padding == h5Padding &&
other.h6 == h6 &&
other.h6Padding == h6Padding &&
other.em == em &&
other.strong == strong &&
other.del == del &&
other.blockquote == blockquote &&
other.img == img &&
other.checkbox == checkbox &&
other.blockSpacing == blockSpacing &&
other.listIndent == listIndent &&
other.listBullet == listBullet &&
other.listBulletPadding == listBulletPadding &&
other.tableHead == tableHead &&
other.tableBody == tableBody &&
other.tableHeadAlign == tableHeadAlign &&
other.tableBorder == tableBorder &&
other.tableColumnWidth == tableColumnWidth &&
other.tableCellsPadding == tableCellsPadding &&
other.tableCellsDecoration == tableCellsDecoration &&
other.blockquotePadding == blockquotePadding &&
other.blockquoteDecoration == blockquoteDecoration &&
other.codeblockPadding == codeblockPadding &&
other.codeblockDecoration == codeblockDecoration &&
other.horizontalRuleDecoration == horizontalRuleDecoration &&
other.textAlign == textAlign &&
other.h1Align == h1Align &&
other.h2Align == h2Align &&
other.h3Align == h3Align &&
other.h4Align == h4Align &&
other.h5Align == h5Align &&
other.h6Align == h6Align &&
other.unorderedListAlign == unorderedListAlign &&
other.orderedListAlign == orderedListAlign &&
other.blockquoteAlign == blockquoteAlign &&
other.codeblockAlign == codeblockAlign &&
other.textScaleFactor == textScaleFactor;
}
@override
// ignore: avoid_equals_and_hash_code_on_mutable_classes
int get hashCode {
return Object.hashAll(<Object?>[
a,
p,
pPadding,
code,
h1,
h1Padding,
h2,
h2Padding,
h3,
h3Padding,
h4,
h4Padding,
h5,
h5Padding,
h6,
h6Padding,
em,
strong,
del,
blockquote,
img,
checkbox,
blockSpacing,
listIndent,
listBullet,
listBulletPadding,
tableHead,
tableBody,
tableHeadAlign,
tableBorder,
tableColumnWidth,
tableCellsPadding,
tableCellsDecoration,
blockquotePadding,
blockquoteDecoration,
codeblockPadding,
codeblockDecoration,
horizontalRuleDecoration,
textAlign,
h1Align,
h2Align,
h3Align,
h4Align,
h5Align,
h6Align,
unorderedListAlign,
orderedListAlign,
blockquoteAlign,
codeblockAlign,
textScaleFactor,
]);
}
}
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:convert';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:markdown/markdown.dart' as md;
import '../flutter_markdown.dart';
import '_functions_io.dart' if (dart.library.html) '_functions_web.dart';
/// Signature for callbacks used by [MarkdownWidget] when the user taps a link.
/// The callback will return the link text, destination, and title from the
/// Markdown link tag in the document.
///
/// Used by [MarkdownWidget.onTapLink].
typedef MarkdownTapLinkCallback = void Function(
String text, String? href, String title);
/// Signature for custom image widget.
///
/// Used by [MarkdownWidget.imageBuilder]
typedef MarkdownImageBuilder = Widget Function(
Uri uri, String? title, String? alt);
/// Signature for custom checkbox widget.
///
/// Used by [MarkdownWidget.checkboxBuilder]
typedef MarkdownCheckboxBuilder = Widget Function(bool value);
/// Signature for custom bullet widget.
///
/// Used by [MarkdownWidget.bulletBuilder]
typedef MarkdownBulletBuilder = Widget Function(int index, BulletStyle style);
/// Enumeration sent to the user when calling [MarkdownBulletBuilder]
///
/// Use this to differentiate the bullet styling when building your own.
enum BulletStyle {
/// An ordered list.
orderedList,
/// An unordered list.
unorderedList,
}
/// Creates a format [TextSpan] given a string.
///
/// Used by [MarkdownWidget] to highlight the contents of `pre` elements.
abstract class SyntaxHighlighter {
// ignore: one_member_abstracts
/// Returns the formatted [TextSpan] for the given string.
TextSpan format(String source);
}
/// An interface for an element builder.
abstract class MarkdownElementBuilder {
/// Called when an Element has been reached, before its children have been
/// visited.
void visitElementBefore(md.Element element) {}
/// Called when a text node has been reached.
///
/// If [MarkdownWidget.styleSheet] has a style of this tag, will passing
/// to [preferredStyle].
///
/// If you needn't build a widget, return null.
Widget? visitText(md.Text text, TextStyle? preferredStyle) => null;
/// Called when an Element has been reached, after its children have been
/// visited.
///
/// If [MarkdownWidget.styleSheet] has a style of this tag, will passing
/// to [preferredStyle].
///
/// If you needn't build a widget, return null.
Widget? visitElementAfter(md.Element element, TextStyle? preferredStyle) =>
null;
}
/// Enum to specify which theme being used when creating [MarkdownStyleSheet]
///
/// [material] - create MarkdownStyleSheet based on MaterialTheme
/// [cupertino] - create MarkdownStyleSheet based on CupertinoTheme
/// [platform] - create MarkdownStyleSheet based on the Platform where the
/// is running on. Material on Android and Cupertino on iOS
enum MarkdownStyleSheetBaseTheme {
/// Creates a MarkdownStyleSheet based on MaterialTheme.
material,
/// Creates a MarkdownStyleSheet based on CupertinoTheme.
cupertino,
/// Creates a MarkdownStyleSheet whose theme is based on the current platform.
platform,
}
/// Enumeration of alignment strategies for the cross axis of list items.
enum MarkdownListItemCrossAxisAlignment {
/// Uses [CrossAxisAlignment.baseline] for the row the bullet and the list
/// item are placed in.
///
/// This alignment will ensure that the bullet always lines up with
/// the list text on the baseline.
///
/// However, note that this alignment does not support intrinsic height
/// measurements because [RenderFlex] does not support it for
/// [CrossAxisAlignment.baseline].
/// See https://github.com/flutter/flutter_markdown/issues/311 for cases,
/// where this might be a problem for you.
///
/// See also:
/// * [start], which allows for intrinsic height measurements.
baseline,
/// Uses [CrossAxisAlignment.start] for the row the bullet and the list item
/// are placed in.
///
/// This alignment will ensure that intrinsic height measurements work.
///
/// However, note that this alignment might not line up the bullet with the
/// list text in the way you would expect in certain scenarios.
/// See https://github.com/flutter/flutter_markdown/issues/169 for example
/// cases that do not produce expected results.
///
/// See also:
/// * [baseline], which will position the bullet and list item on the
/// baseline.
start,
}
/// A base class for widgets that parse and display Markdown.
///
/// Supports all standard Markdown from the original
/// [Markdown specification](https://github.github.com/gfm/).
///
/// See also:
///
/// * [Markdown], which is a scrolling container of Markdown.
/// * [MarkdownBody], which is a non-scrolling container of Markdown.
/// * <https://github.github.com/gfm/>
abstract class MarkdownWidget extends StatefulWidget {
/// Creates a widget that parses and displays Markdown.
///
/// The [data] argument must not be null.
const MarkdownWidget({
super.key,
required this.data,
this.selectable = false,
this.styleSheet,
this.styleSheetTheme = MarkdownStyleSheetBaseTheme.material,
this.syntaxHighlighter,
this.onTapLink,
this.onTapText,
this.imageDirectory,
this.blockSyntaxes,
this.inlineSyntaxes,
this.extensionSet,
this.imageBuilder,
this.checkboxBuilder,
this.bulletBuilder,
this.builders = const <String, MarkdownElementBuilder>{},
this.paddingBuilders = const <String, MarkdownPaddingBuilder>{},
this.fitContent = false,
this.listItemCrossAxisAlignment =
MarkdownListItemCrossAxisAlignment.baseline,
this.softLineBreak = false,
});
/// The Markdown to display.
final String data;
/// If true, the text is selectable.
///
/// Defaults to false.
final bool selectable;
/// The styles to use when displaying the Markdown.
///
/// If null, the styles are inferred from the current [Theme].
final MarkdownStyleSheet? styleSheet;
/// Setting to specify base theme for MarkdownStyleSheet
///
/// Default to [MarkdownStyleSheetBaseTheme.material]
final MarkdownStyleSheetBaseTheme? styleSheetTheme;
/// The syntax highlighter used to color text in `pre` elements.
///
/// If null, the [MarkdownStyleSheet.code] style is used for `pre` elements.
final SyntaxHighlighter? syntaxHighlighter;
/// Called when the user taps a link.
final MarkdownTapLinkCallback? onTapLink;
/// Default tap handler used when [selectable] is set to true
final VoidCallback? onTapText;
/// The base directory holding images referenced by Img tags with local or network file paths.
final String? imageDirectory;
/// Collection of custom block syntax types to be used parsing the Markdown data.
final List<md.BlockSyntax>? blockSyntaxes;
/// Collection of custom inline syntax types to be used parsing the Markdown data.
final List<md.InlineSyntax>? inlineSyntaxes;
/// Markdown syntax extension set
///
/// Defaults to [md.ExtensionSet.gitHubFlavored]
final md.ExtensionSet? extensionSet;
/// Call when build an image widget.
final MarkdownImageBuilder? imageBuilder;
/// Call when build a checkbox widget.
final MarkdownCheckboxBuilder? checkboxBuilder;
/// Called when building a bullet
final MarkdownBulletBuilder? bulletBuilder;
/// Render certain tags, usually used with [extensionSet]
///
/// For example, we will add support for `sub` tag:
///
/// ```dart
/// builders: {
/// 'sub': SubscriptBuilder(),
/// }
/// ```
///
/// The `SubscriptBuilder` is a subclass of [MarkdownElementBuilder].
final Map<String, MarkdownElementBuilder> builders;
/// Add padding for different tags (use only for block elements and img)
///
/// For example, we will add padding for `img` tag:
///
/// ```dart
/// paddingBuilders: {
/// 'img': ImgPaddingBuilder(),
/// }
/// ```
///
/// The `ImgPaddingBuilder` is a subclass of [MarkdownPaddingBuilder].
final Map<String, MarkdownPaddingBuilder> paddingBuilders;
/// Whether to allow the widget to fit the child content.
final bool fitContent;
/// Controls the cross axis alignment for the bullet and list item content
/// in lists.
///
/// Defaults to [MarkdownListItemCrossAxisAlignment.baseline], which
/// does not allow for intrinsic height measurements.
final MarkdownListItemCrossAxisAlignment listItemCrossAxisAlignment;
/// The soft line break is used to identify the spaces at the end of aline of
/// text and the leading spaces in the immediately following the line of text.
///
/// Default these spaces are removed in accordance with the Markdown
/// specification on soft line breaks when lines of text are joined.
final bool softLineBreak;
/// Subclasses should override this function to display the given children,
/// which are the parsed representation of [data].
@protected
Widget build(BuildContext context, List<Widget>? children);
@override
State<MarkdownWidget> createState() => _MarkdownWidgetState();
}
class _MarkdownWidgetState extends State<MarkdownWidget>
implements MarkdownBuilderDelegate {
List<Widget>? _children;
final List<GestureRecognizer> _recognizers = <GestureRecognizer>[];
@override
void didChangeDependencies() {
_parseMarkdown();
super.didChangeDependencies();
}
@override
void didUpdateWidget(MarkdownWidget oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.data != oldWidget.data ||
widget.styleSheet != oldWidget.styleSheet) {
_parseMarkdown();
}
}
@override
void dispose() {
_disposeRecognizers();
super.dispose();
}
void _parseMarkdown() {
final MarkdownStyleSheet fallbackStyleSheet =
kFallbackStyle(context, widget.styleSheetTheme);
final MarkdownStyleSheet styleSheet =
fallbackStyleSheet.merge(widget.styleSheet);
_disposeRecognizers();
final md.Document document = md.Document(
blockSyntaxes: widget.blockSyntaxes,
inlineSyntaxes: widget.inlineSyntaxes,
extensionSet: widget.extensionSet ?? md.ExtensionSet.gitHubFlavored,
encodeHtml: false,
);
// Parse the source Markdown data into nodes of an Abstract Syntax Tree.
final List<String> lines = const LineSplitter().convert(widget.data);
final List<md.Node> astNodes = document.parseLines(lines);
// Configure a Markdown widget builder to traverse the AST nodes and
// create a widget tree based on the elements.
final MarkdownBuilder builder = MarkdownBuilder(
delegate: this,
selectable: widget.selectable,
styleSheet: styleSheet,
imageDirectory: widget.imageDirectory,
imageBuilder: widget.imageBuilder,
checkboxBuilder: widget.checkboxBuilder,
bulletBuilder: widget.bulletBuilder,
builders: widget.builders,
paddingBuilders: widget.paddingBuilders,
fitContent: widget.fitContent,
listItemCrossAxisAlignment: widget.listItemCrossAxisAlignment,
onTapText: widget.onTapText,
softLineBreak: widget.softLineBreak,
);
_children = builder.build(astNodes);
}
void _disposeRecognizers() {
if (_recognizers.isEmpty) {
return;
}
final List<GestureRecognizer> localRecognizers =
List<GestureRecognizer>.from(_recognizers);
_recognizers.clear();
for (final GestureRecognizer recognizer in localRecognizers) {
recognizer.dispose();
}
}
@override
GestureRecognizer createLink(String text, String? href, String title) {
final TapGestureRecognizer recognizer = TapGestureRecognizer()
..onTap = () {
if (widget.onTapLink != null) {
widget.onTapLink!(text, href, title);
}
};
_recognizers.add(recognizer);
return recognizer;
}
@override
TextSpan formatText(MarkdownStyleSheet styleSheet, String code) {
code = code.replaceAll(RegExp(r'\n$'), '');
if (widget.syntaxHighlighter != null) {
return widget.syntaxHighlighter!.format(code);
}
return TextSpan(style: styleSheet.code, text: code);
}
@override
Widget build(BuildContext context) => widget.build(context, _children);
}
/// A non-scrolling widget that parses and displays Markdown.
///
/// Supports all GitHub Flavored Markdown from the
/// [specification](https://github.github.com/gfm/).
///
/// See also:
///
/// * [Markdown], which is a scrolling container of Markdown.
/// * <https://github.github.com/gfm/>
class MarkdownBody extends MarkdownWidget {
/// Creates a non-scrolling widget that parses and displays Markdown.
const MarkdownBody({
super.key,
required super.data,
super.selectable,
super.styleSheet,
// TODO(stuartmorgan): Remove this once 3.0 is no longer part of the
// legacy analysis matrix; it's a false positive there.
// ignore: avoid_init_to_null
super.styleSheetTheme = null,
super.syntaxHighlighter,
super.onTapLink,
super.onTapText,
super.imageDirectory,
super.blockSyntaxes,
super.inlineSyntaxes,
super.extensionSet,
super.imageBuilder,
super.checkboxBuilder,
super.bulletBuilder,
super.builders,
super.paddingBuilders,
super.listItemCrossAxisAlignment,
this.shrinkWrap = true,
super.fitContent = true,
super.softLineBreak,
});
/// If [shrinkWrap] is `true`, [MarkdownBody] will take the minimum height
/// that wraps its content. Otherwise, [MarkdownBody] will expand to the
/// maximum allowed height.
final bool shrinkWrap;
@override
Widget build(BuildContext context, List<Widget>? children) {
if (children!.length == 1 && shrinkWrap) {
return children.single;
}
return Column(
mainAxisSize: shrinkWrap ? MainAxisSize.min : MainAxisSize.max,
crossAxisAlignment:
fitContent ? CrossAxisAlignment.start : CrossAxisAlignment.stretch,
children: children,
);
}
}
/// A scrolling widget that parses and displays Markdown.
///
/// Supports all GitHub Flavored Markdown from the
/// [specification](https://github.github.com/gfm/).
///
/// See also:
///
/// * [MarkdownBody], which is a non-scrolling container of Markdown.
/// * <https://github.github.com/gfm/>
class MyMarkdown extends MarkdownWidget {
/// Creates a scrolling widget that parses and displays Markdown.
const MyMarkdown({
super.key,
required super.data,
super.selectable,
super.styleSheet,
// TODO(stuartmorgan): Remove this once 3.0 is no longer part of the
// legacy analysis matrix; it's a false positive there.
// ignore: avoid_init_to_null
super.styleSheetTheme = null,
super.syntaxHighlighter,
super.onTapLink,
super.onTapText,
super.imageDirectory,
super.blockSyntaxes,
super.inlineSyntaxes,
super.extensionSet,
super.imageBuilder,
super.checkboxBuilder,
super.bulletBuilder,
super.builders,
super.paddingBuilders,
super.listItemCrossAxisAlignment,
this.padding = const EdgeInsets.all(16.0),
this.controller,
this.physics,
this.shrinkWrap = false,
super.softLineBreak,
});
/// The amount of space by which to inset the children.
final EdgeInsets padding;
/// An object that can be used to control the position to which this scroll view is scrolled.
///
/// See also: [ScrollView.controller]
final ScrollController? controller;
/// How the scroll view should respond to user input.
///
/// See also: [ScrollView.physics]
final ScrollPhysics? physics;
/// Whether the extent of the scroll view in the scroll direction should be
/// determined by the contents being viewed.
///
/// See also: [ScrollView.shrinkWrap]
final bool shrinkWrap;
@override
Widget build(BuildContext context, List<Widget>? children) {
return Column(children: children!);
// return ListView(
// padding: padding,
// controller: controller,
// physics: physics,
// shrinkWrap: shrinkWrap,
// children: children!,
// );
}
}
/// Parse [task list items](https://github.github.com/gfm/#task-list-items-extension-).
///
/// This class is no longer used as Markdown now supports checkbox syntax natively.
@Deprecated(
'Use [OrderedListWithCheckBoxSyntax] or [UnorderedListWithCheckBoxSyntax]')
class TaskListSyntax extends md.InlineSyntax {
/// Creates a new instance.
@Deprecated(
'Use [OrderedListWithCheckBoxSyntax] or [UnorderedListWithCheckBoxSyntax]')
TaskListSyntax() : super(_pattern);
static const String _pattern = r'^ *\[([ xX])\] +';
@override
bool onMatch(md.InlineParser parser, Match match) {
final md.Element el = md.Element.withTag('input');
el.attributes['type'] = 'checkbox';
el.attributes['disabled'] = 'true';
el.attributes['checked'] = '${match[1]!.trim().isNotEmpty}';
parser.addNode(el);
return true;
}
}
/// An interface for an padding builder for element.
abstract class MarkdownPaddingBuilder {
/// Called when an Element has been reached, before its children have been
/// visited.
void visitElementBefore(md.Element element) {}
/// Called when a widget node has been rendering and need tag padding.
EdgeInsets getPadding() => EdgeInsets.zero;
}
......@@ -20,11 +20,13 @@ import 'index.dart';
import 'package:eventsource/eventsource.dart';
import 'dart:convert';
import 'package:flutter/services.dart';
import 'package:flutter_client_sse/flutter_client_sse.dart';
// EventSource eventSource = EventSource(Uri.parse('http://example.com/events'));
//
class ChatNewController extends GetxController {
ChatNewController();
static ChatNewController get to => Get.put(ChatNewController());
/// 响应式成员变量
......@@ -32,6 +34,9 @@ class ChatNewController extends GetxController {
late StreamSubscription<Event> eventSource;
// ignore: prefer_typing_uninitialized_variables
late var sse;
/// 成员变量
/// 事件
......@@ -93,6 +98,7 @@ class ChatNewController extends GetxController {
// );
_addMessage(textMessage);
// _addMessage(loadingMessage);
// ("正在思考中...");
......@@ -100,10 +106,13 @@ class ChatNewController extends GetxController {
try {
// print("1321312321${UserStore.to.profile.id}");
if (UserStore.to.profile.id != '') {
await initEventSource();
state.isLoading = true;
int? result =
await NewsAPI.aiAnswerWithStream({"question": message.text});
// _cancelLoading();
// print(
// "eventSource.isPausedeventSource.isPausedeventSource.isPaused---------------${eventSource.isPaused}");
if (result == 200) {
EasyLoading.dismiss();
......@@ -143,7 +152,7 @@ class ChatNewController extends GetxController {
Vibrate.feedback(FeedbackType.error);
}
} catch (e) {
// print("eeeeeeee$e");
print("eeeeeeee$e");
state.isLoading = false;
// _cancelLoading();
final receiveErrorMessage = Chat.ChatMessage(
......@@ -200,24 +209,20 @@ class ChatNewController extends GetxController {
}
initEventSource() async {
final eventSourceUrl = await EventSource.connect(
"$SERVER_API_URL/openAi/connect/${UserStore.to.profile.id}")
as EventSource;
eventSource = eventSourceUrl.listen(
(Event event) {
// print("New event:");
// print(" event: ${event.event}");
// print(" data: ${event.data}");
// final a = json['event.data'];
if (event.data == "[DONE]") {
// if (UserStore.to.isLogin) {
sse = SSEClient.subscribeToSSE(
url: "$SERVER_API_URL/openAi/connect/${UserStore.to.profile.id}",
header: {}).listen((event) {
if (event.id == "[DONE]") {
state.isLoading = false;
// SSEClient.unsubscribeFromSSE();
// eventSource.cancel();
return;
}
print('Id: ' + event.id!);
print('Event: ' + event.event!);
print('Data: ' + event.data!);
Map<String, dynamic> jsonMap = jsonDecode("${event.data}");
// if()
if (jsonMap['askType'] == 1) {
_updateMessage("${jsonMap['content']}" as String);
} else {
......@@ -227,20 +232,7 @@ class ChatNewController extends GetxController {
ProductController.to.state.inderText + jsonMap['content'];
// state.genText
}
// list.setRange(1, 4, [9, 9, 9]);
// final receiveMessage = Chat.ChatMessage(
// user: receiveUser,
// createdAt: DateTime.now(),
// // id: const Uuid().v4(),
// text: "${event.data}",
// );
// _addMessage(receiveMessage);
},
);
// eventSource.onOpen.listen();
// eventSource
});
}
_updateMessage(String text) {
......@@ -286,12 +278,25 @@ class ChatNewController extends GetxController {
Get.snackbar("复制成功", "", colorText: Colors.white);
}
@override
void onReady() async {
print("1____________________onReadyonReadyonReadyonReadyonReady");
super.onReady();
}
@override
void onClose() {
print("222____________________onCloseonCloseonCloseonCloseonCloseonClose");
super.onClose();
}
/// 生命周期
@override
void onInit() async {
// await initEventSource();
// connect("http://example.org/events");
// http://192.168.110.127:8083/api/openAi/connect
await initEventSource();
// await initEventSource();
super.onInit();
// ever(Get.parameters['question'].obs, (value) {
......@@ -302,6 +307,7 @@ class ChatNewController extends GetxController {
///dispose 释放内存
@override
void dispose() {
print("disposedisposedisposedisposedispose");
super.dispose();
// dispose 释放对象
// refreshController.dispose();
......
......@@ -31,7 +31,17 @@ class ChatNewPage extends GetView<ChatNewController> {
// ),
// )
// final c = Get.put(ChatNewController());
return Obx(() => Scaffold(
// return WillPopScope(
// onWillPop: () async {
// 禁止返回
// return false;
// },
return WillPopScope(
onWillPop: () async {
return false;
},
child: Obx(() => Scaffold(
appBar: transparentAppBar(
actions: [
IconButton(
......@@ -208,7 +218,7 @@ class ChatNewPage extends GetView<ChatNewController> {
)
// Obx(() => )),
));
)));
}
}
......
......@@ -8,6 +8,7 @@ import 'package:eventsource/eventsource.dart';
// import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_client_sse/flutter_client_sse.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:get/get_connect/http/src/utils/utils.dart';
import 'package:share_plus/share_plus.dart';
......@@ -18,12 +19,13 @@ import 'index.dart';
class ProductController extends GetxController {
static ProductController get to => Get.put(ProductController());
// ProductController();
// late var sse;
/// 响应式成员变量
final state = ProductState();
late EventSource eventSourceConnect;
late final List<String> initDataList;
late var sse;
TextEditingController controller1 = TextEditingController();
TextEditingController controller2 = TextEditingController();
......@@ -63,6 +65,25 @@ class ProductController extends GetxController {
// 初始静态数据
}
initEventSource() async {
// if (UserStore.to.isLogin) {
sse = SSEClient.subscribeToSSE(
url: "$SERVER_API_URL/openAi/connect/${UserStore.to.profile.id}",
header: {}).listen((event) {
if (event.id == "[DONE]") {
state.isLoading = false;
return;
}
Map<String, dynamic> jsonMap = jsonDecode("${event.data}");
if (jsonMap['askType'] == 1) {
// _updateMessage("${jsonMap['content']}" as String);
} else {
state.inderText = state.inderText + jsonMap['content'];
// state.genText
}
});
}
share() async {
if (state.inderText?.isNotEmpty) {
await Share.share(
......@@ -119,6 +140,8 @@ class ProductController extends GetxController {
// json.encode(map);
if (UserStore.to.isLogin) {
state.isLoading = true;
await initEventSource();
int result = await NewsAPI.sendMessageByDetailId([
{
"label": params['firstLabel'],
......@@ -146,6 +169,7 @@ class ProductController extends GetxController {
});
} else {
EasyLoading.dismiss();
state.isLoading = false;
}
// state.genText = result;
......
......@@ -28,6 +28,10 @@ class ProductState {
set inderText(value) => _inderText.value = value;
get inderText => _inderText.value;
final _isLoading = false.obs;
set isLoading(value) => _isLoading.value = value;
get isLoading => _isLoading.value;
RxList<MessageQueenItem> messageQueenItemQueen = <MessageQueenItem>[].obs;
}
......
......@@ -95,7 +95,8 @@ class NewsCategoriesWidget extends GetView<MainController> {
if (GetPlatform.isAndroid) {
Get.toNamed(AppRoutes.AN_PAY_LIST);
} else {
Get.toNamed(AppRoutes.PAY_LIST);
Get.toNamed(AppRoutes.AN_PAY_LIST);
// Get.toNamed(AppRoutes.PAY_LIST);
}
// AN_PAY_LIST
......
......@@ -386,7 +386,8 @@ class UserDetailPage extends GetView<UserDetailController> {
if (GetPlatform.isAndroid) {
Get.toNamed(AppRoutes.AN_PAY_LIST);
} else {
Get.toNamed(AppRoutes.PAY_LIST);
Get.toNamed(AppRoutes.AN_PAY_LIST);
// Get.toNamed(AppRoutes.PAY_LIST);
}
} else {
Get.toNamed(AppRoutes.SIGN_IN);
......
......@@ -398,6 +398,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.6.6"
flutter_client_sse:
dependency: "direct main"
description:
name: flutter_client_sse
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
flutter_easyloading:
dependency: "direct main"
description:
......@@ -623,6 +630,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.6.0"
highlight:
dependency: "direct main"
description:
name: highlight
url: "https://pub.dartlang.org"
source: hosted
version: "0.7.0"
html:
dependency: transitive
description:
......
......@@ -115,6 +115,8 @@ dependencies:
alipay_kit_ios: 5.0.0
pointycastle: ^3.1.1
eventsource: ^0.4.0
flutter_client_sse: ^1.0.0
highlight: ^0.7.0
# package:bubble/bubble.dart
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment