diff --git a/lib/models/dynamics/article_content_model.dart b/lib/models/dynamics/article_content_model.dart index b48111f47..feb7c7b86 100644 --- a/lib/models/dynamics/article_content_model.dart +++ b/lib/models/dynamics/article_content_model.dart @@ -89,11 +89,22 @@ class Nodes { int? nodeType; Word? word; Rich? rich; + Formula? formula; Nodes.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']); + } +} + +class Formula { + String? latexContent; + + Formula.fromJson(Map json) { + latexContent = json['latex_content']; } } @@ -174,6 +185,12 @@ class Card { String? type; Ugc? ugc; ItemNull? itemNull; + Common? common; + Live? live; + Opus? opus; + Vote? vote; + Music? music; + Goods? goods; Card.fromJson(Map json) { oid = json['oid']; @@ -181,6 +198,163 @@ class Card { ugc = json['ugc'] == null ? null : Ugc.fromJson(json['ugc']); itemNull = json['item_null'] == null ? null : ItemNull.fromJson(json['item_null']); + common = json['common'] == null ? null : Common.fromJson(json['common']); + live = json['live'] == null ? null : Live.fromJson(json['live']); + opus = json['opus'] == null ? null : Opus.fromJson(json['opus']); + vote = json['vote'] == null ? null : Vote.fromJson(json['vote']); + music = json['music'] == null ? null : Music.fromJson(json['music']); + goods = json['goods'] == null ? null : Goods.fromJson(json['goods']); + } +} + +class Goods { + String? headIcon; + String? headText; + String? jumpUrl; + List? items; + + Goods.fromJson(Map json) { + headIcon = json['head_icon']; + headText = json['head_text']; + jumpUrl = json['jump_url']; + items = (json['items'] as List?) + ?.map((item) => GoodsItem.fromJson(item)) + .toList(); + } +} + +class GoodsItem { + String? brief; + String? cover; + int? id; + String? jumpDesc; + String? jumpUrl; + String? name; + String? price; + + GoodsItem.fromJson(Map json) { + brief = json['brief']; + cover = json['cover']; + id = json['id']; + jumpDesc = json['jump_desc']; + jumpUrl = json['jump_url']; + name = json['name']; + price = json['price']; + } +} + +class Music { + String? cover; + int? id; + String? jumpUrl; + String? label; + String? title; + + Music.fromJson(Map json) { + cover = json['cover']; + id = json['id']; + jumpUrl = json['jump_url']; + label = json['label']; + title = json['title']; + } +} + +class Vote { + int? choiceCnt; + int? defaultShare; + String? desc; + int? endTime; + int? status; + int? uid; + int? voteId; + late int joinNum; + + Vote.fromJson(Map json) { + choiceCnt = json['choice_cnt']; + defaultShare = json['default_share']; + desc = json['desc']; + endTime = json['end_time']; + status = json['status']; + uid = json['uid']; + voteId = json['vote_id']; + joinNum = json['join_num'] ?? 0; + } +} + +class Opus { + int? authorMid; + String? authorName; + String? cover; + String? jumpUrl; + String? title; + int? statView; + + Opus.fromJson(Map json) { + authorMid = json['author']?['mid']; + authorName = json['author']?['name']; + cover = json['cover']; + jumpUrl = json['jump_url']; + title = json['title']; + statView = json['stat']?['view']; + } +} + +class Live { + String? cover; + String? descFirst; + String? descSecond; + String? title; + String? jumpUrl; + int? id; + int? liveState; + int? reserveType; + String? badgeText; + + Live.fromJson(Map json) { + cover = json['cover']; + descFirst = json['desc_first']; + descSecond = json['desc_second']; + title = json['title']; + jumpUrl = json['jump_url']; + id = json['id']; + liveState = json['live_state']; + reserveType = json['reserve_type']; + badgeText = json['badge']?['text']; + } +} + +class Common { + Common({ + this.cover, + this.desc1, + this.desc2, + this.headText, + this.idStr, + this.jumpUrl, + this.style, + this.subType, + this.title, + }); + String? cover; + String? desc1; + String? desc2; + String? headText; + String? idStr; + String? jumpUrl; + int? style; + String? subType; + String? title; + + Common.fromJson(Map json) { + cover = json['cover']; + desc1 = json['desc1']; + desc2 = json['desc2']; + headText = json['head_text']; + idStr = json['id_str']; + jumpUrl = json['jump_url']; + style = json['style']; + subType = json['sub_type']; + title = json['title']; } } diff --git a/lib/pages/article/widgets/opus_content.dart b/lib/pages/article/widgets/opus_content.dart index 3e0c8c687..719fce73c 100644 --- a/lib/pages/article/widgets/opus_content.dart +++ b/lib/pages/article/widgets/opus_content.dart @@ -11,6 +11,7 @@ import 'package:PiliPlus/utils/app_scheme.dart'; import 'package:PiliPlus/utils/extension.dart'; import 'package:PiliPlus/utils/utils.dart'; import 'package:cached_network_image/cached_network_image.dart'; +import 'package:cached_network_svg_image/cached_network_svg_image.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; @@ -70,28 +71,51 @@ class OpusContent extends StatelessWidget { 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 + 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, + ), + alignment: Alignment.centerLeft, + placeholderBuilder: (context) => + const SizedBox.shrink(), + ), + ), + ), + ], + ); } return _getSpan(item.word); }).toList()), ); case 4: return Container( - padding: const EdgeInsets.only(left: 8), + padding: + const EdgeInsets.only(left: 8, top: 4, right: 4, bottom: 4), decoration: BoxDecoration( border: Border( left: BorderSide(color: colorScheme.outlineVariant, width: 4), ), - color: colorScheme.surfaceContainer, + borderRadius: const BorderRadius.all(Radius.circular(6)), + color: colorScheme.onInverseSurface, ), child: SelectableText.rich( textAlign: element.align == 1 ? TextAlign.center : null, @@ -183,10 +207,35 @@ class OpusContent extends StatelessWidget { child: InkWell( onTap: () { try { - PiliScheme.videoPush( - int.parse(element.linkCard!.card!.oid!), - null, - ); + if (element.linkCard!.card!.type == + 'LINK_CARD_TYPE_VOTE') { + Get.toNamed( + '/webview', + parameters: { + 'url': + 'https://t.bilibili.com/vote/h5/index/#/result?vote_id=${element.linkCard!.card!.oid}', + }, + ); + return; + } + String? url = switch (element.linkCard!.card!.type) { + 'LINK_CARD_TYPE_UGC' => + element.linkCard!.card!.ugc!.jumpUrl, + 'LINK_CARD_TYPE_COMMON' => + element.linkCard!.card!.common!.jumpUrl, + 'LINK_CARD_TYPE_LIVE' => + element.linkCard!.card!.live!.jumpUrl, + 'LINK_CARD_TYPE_OPUS' => + element.linkCard!.card!.opus!.jumpUrl, + 'LINK_CARD_TYPE_MUSIC' => + element.linkCard!.card!.music!.jumpUrl, + 'LINK_CARD_TYPE_GOODS' => + element.linkCard!.card!.goods!.jumpUrl, + _ => null, + }; + if (url != null) { + PiliScheme.routePushFromUrl(url); + } } catch (_) {} }, borderRadius: const BorderRadius.all(Radius.circular(8)), @@ -228,6 +277,212 @@ class OpusContent extends StatelessWidget { Text(' ${element.linkCard?.card?.itemNull?.text}'), ], ), + 'LINK_CARD_TYPE_COMMON' => Row( + children: [ + NetworkImgLayer( + radius: 6, + width: 65 * StyleString.aspectRatio, + height: 65, + src: element.linkCard!.card!.common!.cover, + ), + const SizedBox(width: 10), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(element.linkCard!.card!.common!.title!), + if (element.linkCard!.card!.common!.desc1 != + null) + Text( + element.linkCard!.card!.common!.desc1!, + style: TextStyle( + fontSize: 13, + color: colorScheme.outline, + ), + ), + if (element.linkCard!.card!.common!.desc2 != + null) + Text( + element.linkCard!.card!.common!.desc2!, + style: TextStyle( + fontSize: 13, + color: colorScheme.outline, + ), + ), + ], + ), + ), + ], + ), + 'LINK_CARD_TYPE_LIVE' => Row( + children: [ + NetworkImgLayer( + radius: 6, + width: 65 * StyleString.aspectRatio, + height: 65, + src: element.linkCard!.card!.live!.cover, + ), + const SizedBox(width: 10), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(element.linkCard!.card!.live!.title!), + if (element.linkCard!.card!.live!.descFirst != + null) + Text( + element.linkCard!.card!.live!.descFirst!, + style: TextStyle( + fontSize: 13, + color: colorScheme.outline, + ), + ), + if (element + .linkCard!.card!.live!.descSecond != + null) + Text( + element.linkCard!.card!.live!.descSecond!, + style: TextStyle( + fontSize: 13, + color: colorScheme.outline, + ), + ), + ], + ), + ), + ], + ), + 'LINK_CARD_TYPE_OPUS' => Row( + children: [ + NetworkImgLayer( + radius: 6, + width: 65 * StyleString.aspectRatio, + height: 65, + src: element.linkCard!.card!.opus!.cover, + ), + const SizedBox(width: 10), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(element.linkCard!.card!.opus!.title!), + Text( + '${element.linkCard!.card!.opus!.authorName} · ${element.linkCard!.card!.opus!.statView ?? 0}阅读', + style: TextStyle( + fontSize: 13, + color: colorScheme.outline, + ), + ), + ], + ), + ), + ], + ), + 'LINK_CARD_TYPE_VOTE' => Row( + children: [ + Container( + decoration: BoxDecoration( + borderRadius: const BorderRadius.all( + Radius.circular(6), + ), + color: colorScheme.secondaryContainer, + ), + width: 75, + height: 50, + alignment: Alignment.center, + child: Icon( + Icons.bar_chart_rounded, + color: colorScheme.onSecondaryContainer, + ), + ), + const SizedBox(width: 10), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(element.linkCard!.card!.vote!.desc!), + Text( + '${element.linkCard!.card!.vote!.joinNum}人参与', + style: TextStyle( + fontSize: 13, + color: colorScheme.outline, + ), + ), + ], + ), + ), + ], + ), + 'LINK_CARD_TYPE_MUSIC' => Row( + children: [ + NetworkImgLayer( + radius: 6, + width: 65 * StyleString.aspectRatio, + height: 65, + src: element.linkCard!.card!.music!.cover, + ), + const SizedBox(width: 10), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(element.linkCard!.card!.music!.title!), + if (element.linkCard!.card!.music!.label != + null) + Text( + element.linkCard!.card!.music!.label!, + style: TextStyle( + fontSize: 13, + color: colorScheme.outline, + ), + ), + ], + ), + ), + ], + ), + 'LINK_CARD_TYPE_GOODS' => Row( + children: [ + NetworkImgLayer( + radius: 6, + width: 65 * StyleString.aspectRatio, + height: 65, + src: element + .linkCard!.card!.goods!.items!.first.cover, + ), + const SizedBox(width: 10), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(element.linkCard!.card!.goods!.items! + .first.name!), + if (element.linkCard!.card!.goods!.items! + .first.brief != + null) + Text( + element.linkCard!.card!.goods!.items! + .first.brief!, + style: TextStyle( + fontSize: 13, + color: colorScheme.outline, + ), + ), + if (element.linkCard!.card!.goods!.items! + .first.price != + null) + Text( + '${element.linkCard!.card!.goods!.items!.first.price!}起', + style: TextStyle( + fontSize: 13, + color: colorScheme.outline, + ), + ), + ], + ), + ), + ], + ), _ => throw UnimplementedError( '\nparaType: ${element.paraType},\ncard type: ${element.linkCard?.card?.type}', ), diff --git a/pubspec.lock b/pubspec.lock index 47c46ebe1..0ae33bee9 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -247,6 +247,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" + cached_network_svg_image: + dependency: "direct main" + description: + name: cached_network_svg_image + sha256: fe9df0217c12e3903558dad14e1bb938c51296a1d96faa080415c6146bbd7a7d + url: "https://pub.dev" + source: hosted + version: "1.2.0" canvas_danmaku: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 834347653..4ba181cb6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -191,6 +191,7 @@ dependencies: document_file_save_plus: ^2.0.0 webdav_client: ^1.2.2 re_highlight: ^0.0.3 + cached_network_svg_image: ^1.2.0 dependency_overrides: screen_brightness: ^2.0.1