mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-06-01 00:28:18 +08:00
opt opus rich text
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
@@ -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<String, dynamic> 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>? nodes;
|
||||
List<Node>? nodes;
|
||||
|
||||
Text.fromJson(Map<String, dynamic> 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<String, dynamic> json) {
|
||||
Node.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']);
|
||||
type = json['type'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,12 +150,18 @@ class Rich {
|
||||
String? jumpUrl;
|
||||
String? origText;
|
||||
String? text;
|
||||
String? type;
|
||||
String? rid;
|
||||
Emoji? emoji;
|
||||
|
||||
Rich.fromJson(Map<String, dynamic> 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>? nodes;
|
||||
List<Node>? nodes;
|
||||
|
||||
Item.fromJson(Map<String, dynamic> 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<String, dynamic> 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'];
|
||||
}
|
||||
|
||||
@@ -444,8 +444,9 @@ class _ArticlePageState extends State<ArticlePage>
|
||||
},
|
||||
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<ArticlePage>
|
||||
);
|
||||
},
|
||||
child: Hero(
|
||||
tag: url,
|
||||
tag: pic.url!,
|
||||
child: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: CachedNetworkImage(
|
||||
imageUrl:
|
||||
Utils.thumbnailImgUrl(url, 60),
|
||||
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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -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(
|
||||
Widget widget = SelectionArea(
|
||||
child: Text.rich(
|
||||
textAlign: element.align == 1 ? TextAlign.center : null,
|
||||
TextSpan(
|
||||
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
|
||||
? null
|
||||
: (TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
PiliScheme.routePushFromUrl(item.rich!.jumpUrl!);
|
||||
}),
|
||||
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,
|
||||
),
|
||||
);
|
||||
} else if (item.formula != null) {
|
||||
// TEXT_NODE_TYPE_FORMULA
|
||||
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: SizedBox(
|
||||
height: 65,
|
||||
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: (context) =>
|
||||
placeholderBuilder: (_) =>
|
||||
const SizedBox.shrink(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
default:
|
||||
return _getSpan(
|
||||
item.word, isQuote ? colorScheme.onSurfaceVariant : null);
|
||||
item.word,
|
||||
isQuote ? colorScheme.onSurfaceVariant : null,
|
||||
);
|
||||
}
|
||||
}).toList()),
|
||||
),
|
||||
);
|
||||
if (isQuote) {
|
||||
widget = Container(
|
||||
@@ -175,7 +208,8 @@ class OpusContent extends StatelessWidget {
|
||||
imageUrl: Utils.thumbnailImgUrl(element.line!.pic!.url!),
|
||||
);
|
||||
case 5 when (element.list != null):
|
||||
return SelectableText.rich(
|
||||
return SelectionArea(
|
||||
child: Text.rich(
|
||||
TextSpan(
|
||||
children: element.list!.items?.indexed.map((entry) {
|
||||
return TextSpan(
|
||||
@@ -193,6 +227,7 @@ class OpusContent extends StatelessWidget {
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
);
|
||||
case 6:
|
||||
return Material(
|
||||
@@ -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(
|
||||
return SelectionArea(
|
||||
child: Text.rich(
|
||||
textAlign: element.align == 1 ? TextAlign.center : null,
|
||||
TextSpan(
|
||||
children: element.text!.nodes!
|
||||
.map<TextSpan>((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),
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -59,7 +59,7 @@ class _UpPanelState extends State<UpPanel> {
|
||||
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'),
|
||||
|
||||
@@ -158,7 +158,7 @@ class _MemberHomeState extends State<MemberHome>
|
||||
],
|
||||
if (res.article?.item?.isNotEmpty == true) ...[
|
||||
_videoHeader(
|
||||
title: '专栏',
|
||||
title: '图文',
|
||||
param: 'contribute',
|
||||
param1: 'opus',
|
||||
count: res.article!.count!,
|
||||
|
||||
Reference in New Issue
Block a user