refactor: Use own highlight rendering with working scrollbar and text selection
This commit is contained in:
parent
8e4c61f03b
commit
d8d0abf27c
5 changed files with 117 additions and 45 deletions
|
|
@ -2,13 +2,13 @@ import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter_highlighter/flutter_highlighter.dart';
|
|
||||||
import 'package:flutter_highlighter/themes/shades-of-purple.dart';
|
|
||||||
import 'package:flutter_linkify/flutter_linkify.dart';
|
import 'package:flutter_linkify/flutter_linkify.dart';
|
||||||
|
import 'package:highlight/highlight.dart' show highlight;
|
||||||
import 'package:html/dom.dart' as dom;
|
import 'package:html/dom.dart' as dom;
|
||||||
import 'package:html/parser.dart' as parser;
|
import 'package:html/parser.dart' as parser;
|
||||||
import 'package:matrix/matrix.dart';
|
import 'package:matrix/matrix.dart';
|
||||||
|
|
||||||
|
import 'package:fluffychat/utils/code_highlight_theme.dart';
|
||||||
import 'package:fluffychat/utils/event_checkbox_extension.dart';
|
import 'package:fluffychat/utils/event_checkbox_extension.dart';
|
||||||
import 'package:fluffychat/widgets/avatar.dart';
|
import 'package:fluffychat/widgets/avatar.dart';
|
||||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||||
|
|
@ -137,6 +137,19 @@ class HtmlMessage extends StatelessWidget {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
InlineSpan _renderCodeBlockNode(dom.Node node) {
|
||||||
|
if (node is! dom.Element) {
|
||||||
|
return TextSpan(text: node.text);
|
||||||
|
}
|
||||||
|
final style = atomOneDarkTheme[node.className.split('-').last] ??
|
||||||
|
atomOneDarkTheme['root'];
|
||||||
|
|
||||||
|
return TextSpan(
|
||||||
|
children: node.nodes.map(_renderCodeBlockNode).toList(),
|
||||||
|
style: style,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Transforms a Node to an InlineSpan.
|
/// Transforms a Node to an InlineSpan.
|
||||||
InlineSpan _renderHtml(
|
InlineSpan _renderHtml(
|
||||||
dom.Node node,
|
dom.Node node,
|
||||||
|
|
@ -334,33 +347,60 @@ class HtmlMessage extends StatelessWidget {
|
||||||
);
|
);
|
||||||
case 'code':
|
case 'code':
|
||||||
final isInline = node.parent?.localName != 'pre';
|
final isInline = node.parent?.localName != 'pre';
|
||||||
|
final lang = node.className
|
||||||
|
.split(' ')
|
||||||
|
.singleWhereOrNull(
|
||||||
|
(className) => className.startsWith('language-'),
|
||||||
|
)
|
||||||
|
?.split('language-')
|
||||||
|
.last ??
|
||||||
|
'md';
|
||||||
|
final highlightedHtml =
|
||||||
|
highlight.parse(node.text, language: lang).toHtml();
|
||||||
|
final element = parser.parse(highlightedHtml).body;
|
||||||
|
if (element == null) {
|
||||||
|
return const TextSpan(text: 'Unable to render code block!');
|
||||||
|
}
|
||||||
|
final controller = isInline ? null : ScrollController();
|
||||||
|
|
||||||
return WidgetSpan(
|
return WidgetSpan(
|
||||||
child: Material(
|
child: Material(
|
||||||
clipBehavior: Clip.hardEdge,
|
color: atomOneBackgroundColor,
|
||||||
borderRadius: BorderRadius.circular(4),
|
shape: RoundedRectangleBorder(
|
||||||
child: SingleChildScrollView(
|
side: const BorderSide(color: hightlightTextColor),
|
||||||
scrollDirection: Axis.horizontal,
|
borderRadius: BorderRadius.circular(4),
|
||||||
child: HighlightView(
|
|
||||||
node.text,
|
|
||||||
language: node.className
|
|
||||||
.split(' ')
|
|
||||||
.singleWhereOrNull(
|
|
||||||
(className) => className.startsWith('language-'),
|
|
||||||
)
|
|
||||||
?.split('language-')
|
|
||||||
.last ??
|
|
||||||
'md',
|
|
||||||
theme: shadesOfPurpleTheme,
|
|
||||||
padding: EdgeInsets.symmetric(
|
|
||||||
horizontal: 8,
|
|
||||||
vertical: isInline ? 0 : 8,
|
|
||||||
),
|
|
||||||
textStyle: TextStyle(
|
|
||||||
fontSize: fontSize,
|
|
||||||
fontFamily: 'RobotoMono',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
child: isInline
|
||||||
|
? Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||||
|
child: Text.rich(
|
||||||
|
TextSpan(
|
||||||
|
children: [_renderCodeBlockNode(element)],
|
||||||
|
),
|
||||||
|
selectionColor: hightlightTextColor.withAlpha(128),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: RawScrollbar(
|
||||||
|
thumbVisibility: true,
|
||||||
|
trackVisibility: true,
|
||||||
|
controller: controller,
|
||||||
|
thumbColor: hightlightTextColor,
|
||||||
|
trackColor: hightlightTextColor.withAlpha(128),
|
||||||
|
thickness: 8,
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
controller: controller,
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(4.0),
|
||||||
|
child: Text.rich(
|
||||||
|
TextSpan(
|
||||||
|
children: [_renderCodeBlockNode(element)],
|
||||||
|
),
|
||||||
|
selectionColor: hightlightTextColor.withAlpha(212),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
case 'img':
|
case 'img':
|
||||||
|
|
|
||||||
40
lib/utils/code_highlight_theme.dart
Normal file
40
lib/utils/code_highlight_theme.dart
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
const hightlightTextColor = Color(0xffabb2bf);
|
||||||
|
const atomOneBackgroundColor = Color(0xff282c34);
|
||||||
|
const atomOneDarkTheme = {
|
||||||
|
'root': TextStyle(color: hightlightTextColor),
|
||||||
|
'comment': TextStyle(color: Color(0xff5c6370), fontStyle: FontStyle.italic),
|
||||||
|
'quote': TextStyle(color: Color(0xff5c6370), fontStyle: FontStyle.italic),
|
||||||
|
'doctag': TextStyle(color: Color(0xffc678dd)),
|
||||||
|
'keyword': TextStyle(color: Color(0xffc678dd)),
|
||||||
|
'formula': TextStyle(color: Color(0xffc678dd)),
|
||||||
|
'section': TextStyle(color: Color(0xffe06c75)),
|
||||||
|
'name': TextStyle(color: Color(0xffe06c75)),
|
||||||
|
'selector-tag': TextStyle(color: Color(0xffe06c75)),
|
||||||
|
'deletion': TextStyle(color: Color(0xffe06c75)),
|
||||||
|
'subst': TextStyle(color: Color(0xffe06c75)),
|
||||||
|
'literal': TextStyle(color: Color(0xff56b6c2)),
|
||||||
|
'string': TextStyle(color: Color(0xff98c379)),
|
||||||
|
'regexp': TextStyle(color: Color(0xff98c379)),
|
||||||
|
'addition': TextStyle(color: Color(0xff98c379)),
|
||||||
|
'attribute': TextStyle(color: Color(0xff98c379)),
|
||||||
|
'meta-string': TextStyle(color: Color(0xff98c379)),
|
||||||
|
'built_in': TextStyle(color: Color(0xffe6c07b)),
|
||||||
|
'attr': TextStyle(color: Color(0xffd19a66)),
|
||||||
|
'variable': TextStyle(color: Color(0xffd19a66)),
|
||||||
|
'template-variable': TextStyle(color: Color(0xffd19a66)),
|
||||||
|
'type': TextStyle(color: Color(0xffd19a66)),
|
||||||
|
'selector-class': TextStyle(color: Color(0xffd19a66)),
|
||||||
|
'selector-attr': TextStyle(color: Color(0xffd19a66)),
|
||||||
|
'selector-pseudo': TextStyle(color: Color(0xffd19a66)),
|
||||||
|
'number': TextStyle(color: Color(0xffd19a66)),
|
||||||
|
'symbol': TextStyle(color: Color(0xff61aeee)),
|
||||||
|
'bullet': TextStyle(color: Color(0xff61aeee)),
|
||||||
|
'link': TextStyle(color: Color(0xff61aeee)),
|
||||||
|
'meta': TextStyle(color: Color(0xff61aeee)),
|
||||||
|
'selector-id': TextStyle(color: Color(0xff61aeee)),
|
||||||
|
'title': TextStyle(color: Color(0xff61aeee)),
|
||||||
|
'emphasis': TextStyle(fontStyle: FontStyle.italic),
|
||||||
|
'strong': TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
};
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
import 'package:flutter_highlighter/flutter_highlighter.dart';
|
|
||||||
import 'package:flutter_highlighter/themes/shades-of-purple.dart';
|
|
||||||
import 'package:matrix/matrix.dart';
|
import 'package:matrix/matrix.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
|
|
@ -40,10 +38,12 @@ class ErrorReporter {
|
||||||
height: 256,
|
height: 256,
|
||||||
width: 256,
|
width: 256,
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
child: HighlightView(
|
child: Text(
|
||||||
text,
|
text,
|
||||||
language: 'sh',
|
style: const TextStyle(
|
||||||
theme: shadesOfPurpleTheme,
|
fontSize: 14,
|
||||||
|
fontFamily: 'RobotoMono',
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
18
pubspec.lock
18
pubspec.lock
|
|
@ -483,14 +483,6 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "9.1.0"
|
version: "9.1.0"
|
||||||
flutter_highlighter:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: flutter_highlighter
|
|
||||||
sha256: "93173afd47a9ada53f3176371755e7ea4a1065362763976d06d6adfb4d946e10"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.1.1"
|
|
||||||
flutter_linkify:
|
flutter_linkify:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -799,14 +791,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.4.0"
|
version: "0.4.0"
|
||||||
highlighter:
|
highlight:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: highlighter
|
name: highlight
|
||||||
sha256: "92180c72b9da8758e1acf39a45aa305a97dcfe2fdc8f3d1d2947c23f2772bfbc"
|
sha256: "5353a83ffe3e3eca7df0abfb72dcf3fa66cc56b953728e7113ad4ad88497cf21"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.1"
|
version: "0.7.0"
|
||||||
html:
|
html:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,6 @@ dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_foreground_task: ^9.1.0
|
flutter_foreground_task: ^9.1.0
|
||||||
flutter_highlighter: ^0.1.1
|
|
||||||
flutter_linkify: ^6.0.0
|
flutter_linkify: ^6.0.0
|
||||||
flutter_local_notifications: ^19.5.0
|
flutter_local_notifications: ^19.5.0
|
||||||
flutter_localizations:
|
flutter_localizations:
|
||||||
|
|
@ -45,6 +44,7 @@ dependencies:
|
||||||
geolocator: ^14.0.2
|
geolocator: ^14.0.2
|
||||||
go_router: ^17.0.0
|
go_router: ^17.0.0
|
||||||
handy_window: ^0.4.0
|
handy_window: ^0.4.0
|
||||||
|
highlight: ^0.7.0
|
||||||
html: ^0.15.4
|
html: ^0.15.4
|
||||||
http: ^1.6.0
|
http: ^1.6.0
|
||||||
image: ^4.1.7
|
image: ^4.1.7
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue