diff --git a/lib/models/dynamics/article_content_model.dart b/lib/models/dynamics/article_content_model.dart index eaedb684..9e3445d0 100644 --- a/lib/models/dynamics/article_content_model.dart +++ b/lib/models/dynamics/article_content_model.dart @@ -1,3 +1,4 @@ +import 'package:PiliPlus/models/dynamics/result.dart'; import 'package:PiliPlus/models/dynamics/vote_model.dart'; class ArticleContentModel { @@ -33,6 +34,7 @@ class Pic { num? height; num? size; String? liveUrl; + bool? isLongPic; Pic.fromJson(Map json) { url = json['url']; @@ -42,6 +44,9 @@ class Pic { pics = (json['pics'] as List?)?.map((item) => Pic.fromJson(item)).toList(); style = json['style']; liveUrl = json['live_url']; + if (width != null && height != null) { + isLongPic = (height! / width!) > 22 / 9; + } } } @@ -71,26 +76,28 @@ class Text { Text({ this.nodes, }); - List? nodes; + List? nodes; Text.fromJson(Map json) { nodes = - (json['nodes'] as List?)?.map((item) => Nodes.fromJson(item)).toList(); + (json['nodes'] as List?)?.map((item) => Node.fromJson(item)).toList(); } } -class Nodes { +class Node { int? nodeType; Word? word; Rich? rich; Formula? formula; + String? type; - Nodes.fromJson(Map json) { + Node.fromJson(Map json) { nodeType = json['node_type']; word = json['word'] == null ? null : Word.fromJson(json['word']); rich = json['rich'] == null ? null : Rich.fromJson(json['rich']); formula = json['formula'] == null ? null : Formula.fromJson(json['formula']); + type = json['type']; } } @@ -143,12 +150,18 @@ class Rich { String? jumpUrl; String? origText; String? text; + String? type; + String? rid; + Emoji? emoji; Rich.fromJson(Map json) { style = json['style'] == null ? null : Style.fromJson(json['style']); jumpUrl = json['jump_url']; origText = json['orig_text']; text = json['text']; + type = json['type']; + rid = json['rid']; + emoji = json['emoji'] == null ? null : Emoji.fromJson(json['emoji']); } } @@ -368,12 +381,12 @@ class L1st { class Item { int? level; int? order; - List? nodes; + List? nodes; Item.fromJson(Map json) { level = json['level']; order = json['order']; - nodes = (json['nodes'] as List?)?.map((e) => Nodes.fromJson(e)).toList(); + nodes = (json['nodes'] as List?)?.map((e) => Node.fromJson(e)).toList(); } } diff --git a/lib/models/dynamics/result.dart b/lib/models/dynamics/result.dart index b3ec1808..1b21e813 100644 --- a/lib/models/dynamics/result.dart +++ b/lib/models/dynamics/result.dart @@ -825,24 +825,20 @@ class RichTextNodeItem { } class Emoji { - Emoji({ - this.iconUrl, - this.size, - this.text, - this.type, - }); - - String? iconUrl; - String? webpUrl; - String? gifUrl; - double? size; + // String? iconUrl; + // String? webpUrl; + // String? gifUrl; + String? url; + late num size; String? text; - int? type; + num? type; + Emoji.fromJson(Map json) { - iconUrl = json['icon_url']; - webpUrl = json['webp_url']; - gifUrl = json['gif_url']; - size = json['size'].toDouble(); + // iconUrl = json['icon_url']; + // webpUrl = json['webp_url']; + // gifUrl = json['gif_url']; + url = json['webp_url'] ?? json['gif_url'] ?? json['icon_url']; + size = json['size'] ?? 1; text = json['text']; type = json['type']; } diff --git a/lib/pages/article/view.dart b/lib/pages/article/view.dart index 0d5a9cc4..f8c3f6f7 100644 --- a/lib/pages/article/view.dart +++ b/lib/pages/article/view.dart @@ -444,8 +444,9 @@ class _ArticlePageState extends State }, itemCount: length, itemBuilder: (context, index) { - final url = pics[0].url!; + final pic = pics[index]; return GestureDetector( + behavior: HitTestBehavior.opaque, onTap: () { context.imageView( imgList: pics @@ -456,10 +457,28 @@ class _ArticlePageState extends State ); }, child: Hero( - tag: url, - child: CachedNetworkImage( - imageUrl: - Utils.thumbnailImgUrl(url, 60), + tag: pic.url!, + child: Stack( + clipBehavior: Clip.none, + alignment: Alignment.center, + children: [ + Positioned.fill( + child: CachedNetworkImage( + fit: pic.isLongPic == true + ? BoxFit.cover + : null, + imageUrl: Utils.thumbnailImgUrl( + pic.url, 60), + ), + ), + if (pic.isLongPic == true) + PBadge( + text: '长图', + type: PBadgeType.primary, + right: paddingRight, + bottom: 12, + ), + ], ), ), ); diff --git a/lib/pages/article/widgets/opus_content.dart b/lib/pages/article/widgets/opus_content.dart index f903aa68..2d1ab36e 100644 --- a/lib/pages/article/widgets/opus_content.dart +++ b/lib/pages/article/widgets/opus_content.dart @@ -4,8 +4,9 @@ import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/image/image_view.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.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, Style, Word; + show ArticleContentModel, Rich, Style, Word; import 'package:PiliPlus/models/dynamics/result.dart'; import 'package:PiliPlus/pages/dynamics/widgets/vote.dart'; import 'package:PiliPlus/utils/app_scheme.dart'; @@ -68,46 +69,78 @@ class OpusContent extends StatelessWidget { switch (element.paraType) { case 1 || 4: final isQuote = element.paraType == 4; - Widget widget = SelectableText.rich( - textAlign: element.align == 1 ? TextAlign.center : null, - TextSpan( - children: element.text?.nodes?.map((item) { - if (item.rich != null) { - return TextSpan( - text: '\u{1F517}${item.rich!.text}', - style: _getStyle(item.rich!.style, colorScheme.primary), - recognizer: item.rich!.jumpUrl == null - ? null - : (TapGestureRecognizer() - ..onTap = () { - PiliScheme.routePushFromUrl(item.rich!.jumpUrl!); - }), - ); - } else if (item.formula != null) { - // TEXT_NODE_TYPE_FORMULA - return TextSpan( - children: [ - WidgetSpan( - child: SizedBox( - height: 65, - child: CachedNetworkSVGImage( - 'https://api.bilibili.com/x/web-frontend/mathjax/tex?formula=${Uri.encodeComponent(item.formula!.latexContent!)}', - colorFilter: ColorFilter.mode( - colorScheme.onSurfaceVariant, - BlendMode.srcIn, + Widget widget = SelectionArea( + child: Text.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): + return TextSpan( + children: [ + WidgetSpan( + child: CachedNetworkSVGImage( + height: 65, + 'https://api.bilibili.com/x/web-frontend/mathjax/tex?formula=${Uri.encodeComponent(item.formula!.latexContent!)}', + colorFilter: ColorFilter.mode( + colorScheme.onSurfaceVariant, + BlendMode.srcIn, + ), + alignment: Alignment.centerLeft, + placeholderBuilder: (_) => + const SizedBox.shrink(), ), - alignment: Alignment.centerLeft, - placeholderBuilder: (context) => - const SizedBox.shrink(), ), - ), - ), - ], - ); - } - return _getSpan( - item.word, isQuote ? colorScheme.onSurfaceVariant : null); - }).toList()), + ], + ); + default: + return _getSpan( + item.word, + isQuote ? colorScheme.onSurfaceVariant : null, + ); + } + }).toList()), + ), ); if (isQuote) { widget = Container( @@ -175,23 +208,25 @@ class OpusContent extends StatelessWidget { imageUrl: Utils.thumbnailImgUrl(element.line!.pic!.url!), ); case 5 when (element.list != null): - return SelectableText.rich( - TextSpan( - children: element.list!.items?.indexed.map((entry) { - return TextSpan( - children: [ - WidgetSpan( - child: Icon(MdiIcons.circleMedium), - alignment: PlaceholderAlignment.middle, - ), - ...entry.$2.nodes!.map((item) { - return _getSpan(item.word); - }), - if (entry.$1 < element.list!.items!.length - 1) - const TextSpan(text: '\n'), - ], - ); - }).toList(), + return SelectionArea( + child: Text.rich( + TextSpan( + children: element.list!.items?.indexed.map((entry) { + return TextSpan( + children: [ + WidgetSpan( + child: Icon(MdiIcons.circleMedium), + alignment: PlaceholderAlignment.middle, + ), + ...entry.$2.nodes!.map((item) { + return _getSpan(item.word); + }), + if (entry.$1 < element.list!.items!.length - 1) + const TextSpan(text: '\n'), + ], + ); + }).toList(), + ), ), ); case 6: @@ -513,32 +548,42 @@ class OpusContent extends StatelessWidget { color: colorScheme.onInverseSurface, ), width: double.infinity, - child: SelectableText.rich(renderer.span!), + child: SelectionArea(child: Text.rich(renderer.span!)), ); default: debugPrint('unknown type ${element.paraType}'); if (element.text?.nodes?.isNotEmpty == true) { - return SelectableText.rich( - textAlign: element.align == 1 ? TextAlign.center : null, - TextSpan( - children: element.text!.nodes! - .map((item) => _getSpan(item.word)) - .toList()), + return SelectionArea( + child: Text.rich( + textAlign: element.align == 1 ? TextAlign.center : null, + TextSpan( + children: element.text!.nodes! + .map((item) => _getSpan(item.word)) + .toList()), + ), ); } - return SelectableText('不支持的类型 (${element.paraType})', + return SelectionArea( + child: Text( + '不支持的类型 (${element.paraType})', style: const TextStyle( fontWeight: FontWeight.bold, color: Colors.red, - )); + ), + ), + ); } } catch (e) { - return SelectableText('错误的类型 $e', + return SelectionArea( + child: Text( + '错误的类型 $e', style: const TextStyle( fontWeight: FontWeight.bold, color: Colors.red, - )); + ), + ), + ); } }, separatorBuilder: (context, index) => const SizedBox(height: 10), diff --git a/lib/pages/dynamics/widgets/rich_node_panel.dart b/lib/pages/dynamics/widgets/rich_node_panel.dart index 4297203d..bf165cd8 100644 --- a/lib/pages/dynamics/widgets/rich_node_panel.dart +++ b/lib/pages/dynamics/widgets/rich_node_panel.dart @@ -130,13 +130,14 @@ TextSpan? richNode( break; // 表情 case 'RICH_TEXT_NODE_TYPE_EMOJI' when (i.emoji != null): + final size = i.emoji!.size * 20.0; spanChildren.add( WidgetSpan( child: NetworkImgLayer( - src: i.emoji!.webpUrl ?? i.emoji!.gifUrl ?? i.emoji!.iconUrl, + src: i.emoji!.url, type: ImageType.emote, - width: (i.emoji!.size ?? 1) * 20, - height: (i.emoji!.size ?? 1) * 20, + width: size, + height: size, ), ), ); diff --git a/lib/pages/dynamics/widgets/up_panel.dart b/lib/pages/dynamics/widgets/up_panel.dart index 373a388a..357b829d 100644 --- a/lib/pages/dynamics/widgets/up_panel.dart +++ b/lib/pages/dynamics/widgets/up_panel.dart @@ -59,7 +59,7 @@ class _UpPanelState extends State { children: [ TextSpan( text: - 'Live(${widget.dynamicsController.upData.value.liveUsers?.count ?? "0"})', + 'Live(${widget.dynamicsController.upData.value.liveUsers?.count})', // checked ), if (!isTop) ...[ const TextSpan(text: '\n'), diff --git a/lib/pages/member_home/view.dart b/lib/pages/member_home/view.dart index ab8f6b8d..780b01c4 100644 --- a/lib/pages/member_home/view.dart +++ b/lib/pages/member_home/view.dart @@ -158,7 +158,7 @@ class _MemberHomeState extends State ], if (res.article?.item?.isNotEmpty == true) ...[ _videoHeader( - title: '专栏', + title: '图文', param: 'contribute', param1: 'opus', count: res.article!.count!,