Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
C
ChatGPT
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Labels
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Jobs
Commits
Open sidebar
关振斌
ChatGPT
Commits
992c2f82
Commit
992c2f82
authored
Mar 10, 2023
by
关振斌
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
update
parent
5fcf9f32
Changes
21
Show whitespace changes
Inline
Side-by-side
Showing
21 changed files
with
2721 additions
and
233 deletions
+2721
-233
lib/common/routers/observers.dart
lib/common/routers/observers.dart
+13
-0
lib/common/values/server.dart
lib/common/values/server.dart
+3
-1
lib/package/chat_dash/dash_chat_2.dart
lib/package/chat_dash/dash_chat_2.dart
+6
-1
lib/package/chat_dash/src/widgets/message_row/default_avatar.dart
...age/chat_dash/src/widgets/message_row/default_avatar.dart
+1
-3
lib/package/chat_dash/src/widgets/message_row/default_message_text.dart
...at_dash/src/widgets/message_row/default_message_text.dart
+68
-4
lib/package/chat_dash/src/widgets/message_row/message_row.dart
...ackage/chat_dash/src/widgets/message_row/message_row.dart
+1
-1
lib/package/chat_dash/src/widgets/message_row/text_container.dart
...age/chat_dash/src/widgets/message_row/text_container.dart
+10
-9
lib/package/markdown/flutter_markdown.dart
lib/package/markdown/flutter_markdown.dart
+10
-0
lib/package/markdown/src/_functions_io.dart
lib/package/markdown/src/_functions_io.dart
+86
-0
lib/package/markdown/src/_functions_web.dart
lib/package/markdown/src/_functions_web.dart
+88
-0
lib/package/markdown/src/builder.dart
lib/package/markdown/src/builder.dart
+848
-0
lib/package/markdown/src/style_sheet.dart
lib/package/markdown/src/style_sheet.dart
+770
-0
lib/package/markdown/src/widget.dart
lib/package/markdown/src/widget.dart
+541
-0
lib/pages/frame/notfound/controller.dart
lib/pages/frame/notfound/controller.dart
+49
-43
lib/pages/frame/notfound/view.dart
lib/pages/frame/notfound/view.dart
+178
-168
lib/pages/frame/product/controller.dart
lib/pages/frame/product/controller.dart
+25
-1
lib/pages/frame/product/state.dart
lib/pages/frame/product/state.dart
+4
-0
lib/pages/main/widgets/categories.dart
lib/pages/main/widgets/categories.dart
+2
-1
lib/pages/user/view.dart
lib/pages/user/view.dart
+2
-1
pubspec.lock
pubspec.lock
+14
-0
pubspec.yaml
pubspec.yaml
+2
-0
No files found.
lib/common/routers/observers.dart
View file @
992c2f82
// 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'
);
...
...
lib/common/values/server.dart
View file @
992c2f82
// 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';
...
...
lib/package/chat_dash/dash_chat_2.dart
View file @
992c2f82
...
...
@@ -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'
;
...
...
lib/package/chat_dash/src/widgets/message_row/default_avatar.dart
View file @
992c2f82
...
...
@@ -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
,
...
...
lib/package/chat_dash/src/widgets/message_row/default_message_text.dart
View file @
992c2f82
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
;
}
}
lib/package/chat_dash/src/widgets/message_row/message_row.dart
View file @
992c2f82
...
...
@@ -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
>[
...
...
lib/package/chat_dash/src/widgets/message_row/text_container.dart
View file @
992c2f82
...
...
@@ -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
lib/package/markdown/flutter_markdown.dart
0 → 100644
View file @
992c2f82
// 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'
;
lib/package/markdown/src/_functions_io.dart
0 → 100644
View file @
992c2f82
// 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
();
}
lib/package/markdown/src/_functions_web.dart
0 → 100644
View file @
992c2f82
// 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
();
}
lib/package/markdown/src/builder.dart
0 → 100644
View file @
992c2f82
// 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
;
}
lib/package/markdown/src/style_sheet.dart
0 → 100644
View file @
992c2f82
// 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
,
]);
}
}
lib/package/markdown/src/widget.dart
0 → 100644
View file @
992c2f82
// 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
;
}
lib/pages/frame/notfound/controller.dart
View file @
992c2f82
...
...
@@ -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();
...
...
lib/pages/frame/notfound/view.dart
View file @
992c2f82
...
...
@@ -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(() => )),
));
)
));
}
}
...
...
lib/pages/frame/product/controller.dart
View file @
992c2f82
...
...
@@ -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;
...
...
lib/pages/frame/product/state.dart
View file @
992c2f82
...
...
@@ -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
;
}
...
...
lib/pages/main/widgets/categories.dart
View file @
992c2f82
...
...
@@ -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
...
...
lib/pages/user/view.dart
View file @
992c2f82
...
...
@@ -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
);
...
...
pubspec.lock
View file @
992c2f82
...
...
@@ -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:
...
...
pubspec.yaml
View file @
992c2f82
...
...
@@ -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
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment