mod: opus

Closes #802

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-05-03 12:29:45 +08:00
parent 478b71d6b3
commit 974a74a3c7
4 changed files with 447 additions and 9 deletions

View File

@@ -89,11 +89,22 @@ class Nodes {
int? nodeType;
Word? word;
Rich? rich;
Formula? formula;
Nodes.fromJson(Map<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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<GoodsItem>? items;
Goods.fromJson(Map<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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'];
}
}

View File

@@ -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<TextSpan>((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}',
),