diff --git a/assets/highlighting/dart.json b/assets/highlighting/dart.json new file mode 100644 index 0000000..c7ddf4c --- /dev/null +++ b/assets/highlighting/dart.json @@ -0,0 +1,531 @@ +{ + "name": "Dart", + "version": "1.2.3", + "fileTypes": ["dart"], + "scopeName": "source.dart", + + "foldingStartMarker": "\\{\\s*$", + "foldingStopMarker": "^\\s*\\}", + + "patterns": [ + { + "name": "meta.preprocessor.script.dart", + "match": "^(#!.*)$" + }, + { + "name": "meta.declaration.dart", + "begin": "^\\w*\\b(library|import|part of|part|export)\\b", + "beginCaptures": { + "0": { + "name": "keyword.other.import.dart" + } + }, + "end": ";", + "endCaptures": { + "0": { + "name": "punctuation.terminator.dart" + } + }, + "patterns": [ + { + "include": "#strings" + }, + { + "include": "#comments" + }, + { + "name": "keyword.other.import.dart", + "match": "\\b(as|show|hide)\\b" + }, + { + "name": "keyword.control.dart", + "match": "\\b(if)\\b" + } + ] + }, + { + "include": "#comments" + }, + { + "include": "#punctuation" + }, + { + "include": "#annotations" + }, + { + "include": "#keywords" + }, + { + "include": "#constants-and-special-vars" + }, + { + "include": "#operators" + }, + { + "include": "#strings" + } + ], + + "repository": { + "dartdoc": { + "patterns": [ + { + "match": "(\\[.*?\\])", + "captures": { + "0": { + "name": "variable.name.source.dart" + } + } + }, + { + "match": "^ {4,}(?![ \\*]).*", + "captures": { + "0": { + "name": "variable.name.source.dart" + } + } + }, + { + "contentName": "variable.other.source.dart", + "begin": "```.*?$", + "end": "```" + }, + { + "match": "(`.*?`)", + "captures": { + "0": { + "name": "variable.other.source.dart" + } + } + }, + { + "match": "(`.*?`)", + "captures": { + "0": { + "name": "variable.other.source.dart" + } + } + }, + { + "match": "(\\* (( ).*))$", + "captures": { + "2": { + "name": "variable.other.source.dart" + } + } + } + ] + }, + "comments": { + "patterns": [ + { + "name": "comment.block.empty.dart", + "match": "/\\*\\*/", + "captures": { + "0": { + "name": "punctuation.definition.comment.dart" + } + } + }, + { + "include": "#comments-doc-oldschool" + }, + { + "include": "#comments-doc" + }, + { + "include": "#comments-inline" + } + ] + }, + "comments-doc-oldschool": { + "patterns": [ + { + "name": "comment.block.documentation.dart", + "begin": "/\\*\\*", + "end": "\\*/", + "patterns": [ + { + "include": "#comments-doc-oldschool" + }, + { + "include": "#comments-block" + }, + { + "include": "#dartdoc" + } + ] + } + ] + }, + "comments-doc": { + "patterns": [ + { + "name": "comment.block.documentation.dart", + "begin": "///", + "while": "^\\s*///", + "patterns": [ + { + "include": "#dartdoc" + } + ] + } + ] + }, + "comments-inline": { + "patterns": [ + { + "include": "#comments-block" + }, + { + "match": "((//).*)$", + "captures": { + "1": { + "name": "comment.line.double-slash.dart" + } + } + } + ] + }, + "comments-block": { + "patterns": [ + { + "name": "comment.block.dart", + "begin": "/\\*", + "end": "\\*/", + "patterns": [ + { + "include": "#comments-block" + } + ] + } + ] + }, + "annotations": { + "patterns": [ + { + "name": "storage.type.annotation.dart", + "match": "@[a-zA-Z]+" + } + ] + }, + "constants-and-special-vars": { + "patterns": [ + { + "name": "constant.language.dart", + "match": "(??]|,\\s*|\\s+extends\\s+)+>)?[!?]?\\(", + "captures": { + "1": { + "name": "entity.name.function.dart" + }, + "2": { + "patterns": [ + { + "include": "#type-args" + } + ] + } + } + } + ] + }, + "type-args": { + "begin": "(<)", + "end": "(>)", + "beginCaptures": { + "1": { + "name": "other.source.dart" + } + }, + "endCaptures": { + "1": { + "name": "other.source.dart" + } + }, + "patterns": [ + { + "include": "#class-identifier" + }, + { + "match": "," + }, + { + "name": "keyword.declaration.dart", + "match": "extends" + }, + { + "include": "#comments" + } + ] + }, + "keywords": { + "patterns": [ + { + "name": "keyword.cast.dart", + "match": "(?>>?|~|\\^|\\||&)" + }, + { + "name": "keyword.operator.assignment.bitwise.dart", + "match": "((&|\\^|\\||<<|>>>?)=)" + }, + { + "name": "keyword.operator.closure.dart", + "match": "(=>)" + }, + { + "name": "keyword.operator.comparison.dart", + "match": "(==|!=|<=?|>=?)" + }, + { + "name": "keyword.operator.assignment.arithmetic.dart", + "match": "(([+*/%-]|\\~)=)" + }, + { + "name": "keyword.operator.assignment.dart", + "match": "(=)" + }, + { + "name": "keyword.operator.increment-decrement.dart", + "match": "(\\-\\-|\\+\\+)" + }, + { + "name": "keyword.operator.arithmetic.dart", + "match": "(\\-|\\+|\\*|\\/|\\~\\/|%)" + }, + { + "name": "keyword.operator.logical.dart", + "match": "(!|&&|\\|\\|)" + } + ] + }, + "string-interp": { + "patterns": [ + { + "match": "\\$([a-zA-Z0-9_]+)", + "captures": { + "1": { + "name": "variable.parameter.dart" + } + } + }, + { + "name": "string.interpolated.expression.dart", + "begin": "\\$\\{", + "end": "\\}", + "patterns": [ + { + "include": "#constants-and-special-vars", + "name": "variable.parameter.dart" + }, + { + "include": "#strings" + }, + { + "name": "variable.parameter.dart", + "match": "[a-zA-Z0-9_]+" + } + ] + }, + { + "name": "constant.character.escape.dart", + "match": "\\\\." + } + ] + }, + "strings": { + "patterns": [ + { + "name": "string.interpolated.triple.double.dart", + "begin": "(?|>=)\\b" + }, + { + "name": "keyword.operator.logical.python", + "match": "\\b(?:and|or|not)\\b" + } + ] + }, + "strings": { + "patterns": [ + { + "name": "string.quoted.triple.double.python", + "begin": "\"\"\"", + "end": "\"\"\"" + }, + { + "name": "string.quoted.triple.single.python", + "begin": "'''", + "end": "'''" + }, + { + "name": "string.quoted.double.python", + "begin": "\"", + "end": "\"", + "patterns": [{ "include": "#string-escape" }] + }, + { + "name": "string.quoted.single.python", + "begin": "'", + "end": "'", + "patterns": [{ "include": "#string-escape" }] + } + ] + }, + "string-escape": { + "patterns": [ + { "name": "constant.character.escape.python", "match": "\\\\[\"']" } + ] + } + } +} diff --git a/assets/highlighting/sql.json b/assets/highlighting/sql.json new file mode 100644 index 0000000..d1cf718 --- /dev/null +++ b/assets/highlighting/sql.json @@ -0,0 +1,145 @@ +{ + "fileTypes": ["sql", "ddl", "dml"], + "foldingStartMarker": "(?i)^\\s*(begin|if|loop)\\b", + "foldingStopMarker": "(?i)^\\s*(end)\\b", + "keyEquivalent": "^~S", + "name": "PL/pgSQL (Postgres)", + "patterns": [ + { + "begin": "/\\*", + "end": "\\*/", + "name": "comment.block.postgres" + }, + { + "match": "--.*$", + "name": "comment.line.double-dash.postgres" + }, + { + "captures": { + "1": { + "name": "keyword.other.postgres" + }, + "2": { + "name": "keyword.other.postgres" + } + }, + "match": "(?i)^\\s*(create)(\\s+or\\s+replace)?\\s+", + "name": "meta.create.postgres" + }, + { + "captures": { + "1": { + "name": "keyword.other.postgres" + }, + "2": { + "name": "keyword.other.postgres" + }, + "3": { + "name": "entity.name.type.postgres" + } + }, + "match": "(?i)\\b(package)(\\s+body)?\\s+(\\S+)", + "name": "meta.package.postgres" + }, + { + "captures": { + "1": { + "name": "keyword.other.postgres" + }, + "2": { + "name": "entity.name.type.postgres" + } + }, + "match": "(?i)\\b(type)\\s+\"([^\"]+)\"", + "name": "meta.type.postgres" + }, + { + "captures": { + "1": { + "name": "keyword.other.postgres" + }, + "2": { + "name": "entity.name.function.postgres" + } + }, + "match": "(?i)\\s*(function|procedure)\\s+([-a-z0-9_.]+)", + "name": "meta.procedure.postgres" + }, + { + "match": "[!<>:]?=|<>|<|>|\\+|(? { try { _signRecord = await _dailySign.getToday(); _dailySign.listLastRecord(14).then((value) { - setState(() => _signRecordHistory = value); + if (mounted) { + setState(() => _signRecordHistory = value); + } }); } catch (e) { context.showErrorDialog(e); } - setState(() => _signingDaily = false); + if (mounted) { + setState(() => _signingDaily = false); + } } Future _signDaily() async { diff --git a/lib/widgets/markdown_text_content.dart b/lib/widgets/markdown_text_content.dart index c0dfeac..86fa8f9 100644 --- a/lib/widgets/markdown_text_content.dart +++ b/lib/widgets/markdown_text_content.dart @@ -1,12 +1,14 @@ import 'package:flutter/material.dart'; -import 'package:flutter_markdown_selectionarea/flutter_markdown.dart'; +import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:gap/gap.dart'; import 'package:get/get.dart'; +import 'package:google_fonts/google_fonts.dart'; import 'package:markdown/markdown.dart' as markdown; -import 'package:markdown/markdown.dart'; +import 'package:path/path.dart'; import 'package:solian/providers/stickers.dart'; import 'package:solian/widgets/attachments/attachment_list.dart'; import 'package:solian/widgets/auto_cache_image.dart'; +import 'package:syntax_highlight/syntax_highlight.dart'; import 'package:url_launcher/url_launcher_string.dart'; import 'account/account_profile_popup.dart'; @@ -39,15 +41,6 @@ class MarkdownTextContent extends StatelessWidget { // Getting paragraph var paragraph = paragraphs[idx]; - // Auto adding new-lines - if (isAutoWarp) { - paragraph = paragraph.replaceAll('\n', '\\\n'); - } - const charactersToTrim = '\\\n\t\r '; - final trimPattern = - RegExp('^[$charactersToTrim]+|[$charactersToTrim]+\$'); - paragraph = paragraph.trim().replaceAll(trimPattern, ''); - // Matching stickers final stickerMatch = stickerRegex.allMatches(paragraph); final isOnlySticker = @@ -62,31 +55,48 @@ class MarkdownTextContent extends StatelessWidget { styleSheet: MarkdownStyleSheet.fromTheme( Theme.of(context), ).copyWith( - textScaleFactor: isLargeText ? 1.1 : 1, - blockquote: TextStyle( - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - blockquoteDecoration: BoxDecoration( - color: Theme.of(context).colorScheme.surfaceContainerHigh, - borderRadius: const BorderRadius.all(Radius.circular(4)), - ), - horizontalRuleDecoration: BoxDecoration( - border: Border( - top: BorderSide( - width: 1.0, - color: Theme.of(context).dividerColor, + textScaler: TextScaler.linear(isLargeText ? 1.1 : 1), + blockquote: TextStyle( + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + blockquoteDecoration: BoxDecoration( + color: Theme.of(context).colorScheme.surfaceContainerHigh, + borderRadius: const BorderRadius.all(Radius.circular(4)), + ), + horizontalRuleDecoration: BoxDecoration( + border: Border( + top: BorderSide( + width: 1.0, + color: Theme.of(context).dividerColor, + ), ), ), - ), - ), + codeblockDecoration: BoxDecoration( + border: Border.all( + color: Theme.of(context).dividerColor, + width: 0.3, + ), + borderRadius: const BorderRadius.all(Radius.circular(4)), + color: Theme.of(context).colorScheme.surface.withOpacity(0.5), + )), + builders: { + 'code': _MarkdownTextCodeElement(), + }, + softLineBreak: true, extensionSet: markdown.ExtensionSet( - markdown.ExtensionSet.gitHubFlavored.blockSyntaxes, + [ + markdown.CodeBlockSyntax(), + ...markdown.ExtensionSet.commonMark.blockSyntaxes, + ...markdown.ExtensionSet.gitHubFlavored.blockSyntaxes, + ], [ + if (isAutoWarp) markdown.LineBreakSyntax(), _UserNameCardInlineSyntax(), _CustomEmoteInlineSyntax(), - markdown.EmojiSyntax(), markdown.AutolinkSyntax(), markdown.AutolinkExtensionSyntax(), + markdown.CodeSyntax(), + ...markdown.ExtensionSet.commonMark.inlineSyntaxes, ...markdown.ExtensionSet.gitHubFlavored.inlineSyntaxes ], ), @@ -209,7 +219,7 @@ class MarkdownTextContent extends StatelessWidget { } } -class _UserNameCardInlineSyntax extends InlineSyntax { +class _UserNameCardInlineSyntax extends markdown.InlineSyntax { _UserNameCardInlineSyntax() : super(r'@[a-zA-Z0-9_]+'); @override @@ -225,7 +235,7 @@ class _UserNameCardInlineSyntax extends InlineSyntax { } } -class _CustomEmoteInlineSyntax extends InlineSyntax { +class _CustomEmoteInlineSyntax extends markdown.InlineSyntax { _CustomEmoteInlineSyntax() : super(r':([-\w]+):'); @override @@ -245,3 +255,45 @@ class _CustomEmoteInlineSyntax extends InlineSyntax { return true; } } + +class _MarkdownTextCodeElement extends MarkdownElementBuilder { + @override + Widget? visitElementAfter( + markdown.Element element, + TextStyle? preferredStyle, + ) { + var language = ''; + + if (element.attributes['class'] != null) { + String lg = element.attributes['class'] as String; + language = lg.substring(9).trim(); + } + return SizedBox( + child: FutureBuilder( + future: (() async { + final docPath = '../../../'; + final highlightingPath = + join(docPath, 'assets/highlighting', language); + await Highlighter.initialize([highlightingPath]); + return Highlighter( + language: highlightingPath, + theme: await HighlighterTheme.loadLightTheme(), + ); + })(), + builder: (context, snapshot) { + if (snapshot.hasData) { + final highlighter = snapshot.data!; + return Text.rich( + highlighter.highlight(element.textContent.trim()), + style: GoogleFonts.robotoMono(), + ); + } + return Text( + element.textContent.trim(), + style: GoogleFonts.robotoMono(), + ); + }, + ), + ).paddingAll(8); + } +} diff --git a/pubspec.lock b/pubspec.lock index ed23db9..a3b72e3 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -795,14 +795,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.3+2" - flutter_markdown_selectionarea: - dependency: "direct main" - description: - name: flutter_markdown_selectionarea - sha256: d4bc27e70a5c40ebdab23a4b81f75d53696a214d4d1f13c12045b38a0ddc58a2 - url: "https://pub.dev" - source: hosted - version: "0.6.17+1" flutter_native_splash: dependency: "direct dev" description: @@ -1986,6 +1978,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.3.0+3" + syntax_highlight: + dependency: "direct main" + description: + name: syntax_highlight + sha256: ee33b6aa82cc722bb9b40152a792181dee222353b486c0255fde666a3e3a4997 + url: "https://pub.dev" + source: hosted + version: "0.4.0" term_glyph: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 3d1472a..1b10a04 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -49,7 +49,6 @@ dependencies: dismissible_page: ^1.0.2 share_plus: ^10.0.0 flutter_cache_manager: ^3.3.3 - flutter_markdown_selectionarea: ^0.6.17+1 shared_preferences: ^2.2.3 provider: ^6.1.2 gal: ^2.3.0 @@ -84,6 +83,7 @@ dependencies: version: ^3.0.2 action_slider: ^0.7.0 in_app_review: ^2.0.9 + syntax_highlight: ^0.4.0 dev_dependencies: flutter_test: @@ -103,6 +103,7 @@ flutter: assets: - assets/logo.png - assets/locales/ + - assets/highlighting/ fonts: - family: NotoSansEmoji