diff --git a/lib/models/dynamics/article_content_model.dart b/lib/models/dynamics/article_content_model.dart index 9410f5354..028d3efa1 100644 --- a/lib/models/dynamics/article_content_model.dart +++ b/lib/models/dynamics/article_content_model.dart @@ -11,6 +11,7 @@ class ArticleContentModel { LinkCard? linkCard; Code? code; L1st? list; + Text? heading; ArticleContentModel.fromJson(Map json) { align = json['align']; @@ -24,6 +25,7 @@ class ArticleContentModel { : LinkCard.fromJson(json['link_card']); code = json['code'] == null ? null : Code.fromJson(json['code']); list = json['list'] == null ? null : L1st.fromJson(json['list']); + heading = json['heading'] == null ? null : Text.fromJson(json['heading']); } } diff --git a/lib/pages/article/widgets/opus_content.dart b/lib/pages/article/widgets/opus_content.dart index 3c0aeff2c..d4f9b2a46 100644 --- a/lib/pages/article/widgets/opus_content.dart +++ b/lib/pages/article/widgets/opus_content.dart @@ -6,7 +6,7 @@ import 'package:PiliPlus/http/constants.dart'; import 'package:PiliPlus/models/common/image_preview_type.dart'; import 'package:PiliPlus/models/common/image_type.dart'; import 'package:PiliPlus/models/dynamics/article_content_model.dart' - show ArticleContentModel, Rich, Style, Word; + show ArticleContentModel, Rich, Style, Word, Node; import 'package:PiliPlus/models/dynamics/result.dart'; import 'package:PiliPlus/pages/dynamics/widgets/vote.dart'; import 'package:PiliPlus/utils/app_scheme.dart'; @@ -19,7 +19,7 @@ import 'package:cached_network_svg_image/cached_network_svg_image.dart'; import 'package:flutter/foundation.dart' show kDebugMode; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart' hide ContextExtensionss; +import 'package:get/get.dart' hide ContextExtensionss, Node; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import 'package:re_highlight/languages/all.dart'; import 'package:re_highlight/re_highlight.dart'; @@ -36,6 +36,76 @@ class OpusContent extends StatelessWidget { required this.maxWidth, }); + static InlineSpan _node2Widget({ + required Node item, + required ColorScheme colorScheme, + bool isQuote = false, + }) { + switch (item.type) { + case 'TEXT_NODE_TYPE_RICH' when (item.rich != null): + Rich rich = item.rich!; + switch (rich.type) { + case 'RICH_TEXT_NODE_TYPE_EMOJI': + Emoji emoji = rich.emoji!; + final size = 20.0 * emoji.size; + return WidgetSpan( + child: NetworkImgLayer( + width: size, + height: size, + src: emoji.url, + type: ImageType.emote, + ), + ); + default: + return TextSpan( + text: + '${rich.type == 'RICH_TEXT_NODE_TYPE_WEB' ? '\u{1F517}' : ''}${item.rich!.text}', + style: _getStyle( + rich.style, + rich.type == 'RICH_TEXT_NODE_TYPE_TEXT' + ? null + : colorScheme.primary, + ), + recognizer: TapGestureRecognizer() + ..onTap = () { + switch (rich.type) { + case 'RICH_TEXT_NODE_TYPE_AT': + Get.toNamed('/member?mid=${rich.rid}'); + // case 'RICH_TEXT_NODE_TYPE_TOPIC': + default: + if (rich.jumpUrl != null) { + PiliScheme.routePushFromUrl( + rich.jumpUrl!, + ); + } + } + }, + ); + } + case 'TEXT_NODE_TYPE_FORMULA' when (item.formula != null): + final latex = item.formula!.latexContent!; + return WidgetSpan( + child: CachedNetworkSVGImage( + cacheKey: latex, + semanticsLabel: latex, + height: 65, + '${HttpString.apiBaseUrl}/x/web-frontend/mathjax/tex?formula=${Uri.encodeComponent(latex)}', + colorFilter: ColorFilter.mode( + colorScheme.onSurfaceVariant, + BlendMode.srcIn, + ), + alignment: Alignment.centerLeft, + placeholderBuilder: (_) => Text(latex), + ), + ); + default: + return _getSpan( + item.word, + isQuote ? colorScheme.onSurfaceVariant : null, + ); + } + } + static TextStyle _getStyle(Style? style, [Color? color, double? fontSize]) => TextStyle( decoration: style?.strikethrough == true @@ -79,71 +149,12 @@ class OpusContent extends StatelessWidget { Widget widget = SelectableText.rich( textAlign: element.align == 1 ? TextAlign.center : null, TextSpan( - children: element.text?.nodes?.map((item) { - switch (item.type) { - case 'TEXT_NODE_TYPE_RICH' when (item.rich != null): - Rich rich = item.rich!; - switch (rich.type) { - case 'RICH_TEXT_NODE_TYPE_EMOJI': - Emoji emoji = rich.emoji!; - final size = 20.0 * emoji.size; - return WidgetSpan( - child: NetworkImgLayer( - width: size, - height: size, - src: emoji.url, - type: ImageType.emote, - ), - ); - default: - return TextSpan( - text: - '${rich.type == 'RICH_TEXT_NODE_TYPE_WEB' ? '\u{1F517}' : ''}${item.rich!.text}', - style: _getStyle( - rich.style, - rich.type == 'RICH_TEXT_NODE_TYPE_TEXT' - ? null - : colorScheme.primary, - ), - recognizer: TapGestureRecognizer() - ..onTap = () { - switch (rich.type) { - case 'RICH_TEXT_NODE_TYPE_AT': - Get.toNamed('/member?mid=${rich.rid}'); - // case 'RICH_TEXT_NODE_TYPE_TOPIC': - default: - if (rich.jumpUrl != null) { - PiliScheme.routePushFromUrl( - rich.jumpUrl!, - ); - } - } - }, - ); - } - case 'TEXT_NODE_TYPE_FORMULA' when (item.formula != null): - final latex = item.formula!.latexContent!; - return WidgetSpan( - child: CachedNetworkSVGImage( - cacheKey: latex, - semanticsLabel: latex, - height: 65, - '${HttpString.apiBaseUrl}/x/web-frontend/mathjax/tex?formula=${Uri.encodeComponent(latex)}', - colorFilter: ColorFilter.mode( - colorScheme.onSurfaceVariant, - BlendMode.srcIn, - ), - alignment: Alignment.centerLeft, - placeholderBuilder: (_) => Text(latex), - ), - ); - default: - return _getSpan( - item.word, - isQuote ? colorScheme.onSurfaceVariant : null, - ); - } - }).toList(), + children: element.text?.nodes + ?.map( + (item) => + _node2Widget(item: item, colorScheme: colorScheme), + ) + .toList(), ), ); if (isQuote) { @@ -586,6 +597,16 @@ class OpusContent extends StatelessWidget { width: double.infinity, child: SelectableText.rich(renderer.span!), ); + case 8 when (element.heading?.nodes?.isNotEmpty == true): + return SelectableText.rich( + TextSpan( + children: element.heading!.nodes! + .map( + (e) => _node2Widget(item: e, colorScheme: colorScheme), + ) + .toList(), + ), + ); default: if (kDebugMode) debugPrint('unknown type ${element.paraType}'); if (element.text?.nodes?.isNotEmpty == true) {