feat: opus专栏内容渲染
This commit is contained in:
35
lib/http/read.dart
Normal file
35
lib/http/read.dart
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'package:html/parser.dart';
|
||||||
|
import 'package:pilipala/models/read/opus.dart';
|
||||||
|
import 'init.dart';
|
||||||
|
|
||||||
|
class ReadHttp {
|
||||||
|
static List<String> extractScriptContents(String htmlContent) {
|
||||||
|
RegExp scriptRegExp = RegExp(r'<script>([\s\S]*?)<\/script>');
|
||||||
|
Iterable<Match> matches = scriptRegExp.allMatches(htmlContent);
|
||||||
|
List<String> scriptContents = [];
|
||||||
|
for (Match match in matches) {
|
||||||
|
String scriptContent = match.group(1)!;
|
||||||
|
scriptContents.add(scriptContent);
|
||||||
|
}
|
||||||
|
return scriptContents;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析专栏 opus格式
|
||||||
|
static Future parseArticleOpus({required String id}) async {
|
||||||
|
var res = await Request().get('https://www.bilibili.com/opus/$id', extra: {
|
||||||
|
'ua': 'pc',
|
||||||
|
});
|
||||||
|
String scriptContent =
|
||||||
|
extractScriptContents(parse(res.data).body!.outerHtml)[0];
|
||||||
|
int startIndex = scriptContent.indexOf('{');
|
||||||
|
int endIndex = scriptContent.lastIndexOf('};');
|
||||||
|
String jsonContent = scriptContent.substring(startIndex, endIndex + 1);
|
||||||
|
// 解析JSON字符串为Map
|
||||||
|
Map<String, dynamic> jsonData = json.decode(jsonContent);
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'data': OpusDataModel.fromJson(jsonData),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
479
lib/models/read/opus.dart
Normal file
479
lib/models/read/opus.dart
Normal file
@ -0,0 +1,479 @@
|
|||||||
|
class OpusDataModel {
|
||||||
|
OpusDataModel({
|
||||||
|
this.id,
|
||||||
|
this.detail,
|
||||||
|
this.type,
|
||||||
|
this.theme,
|
||||||
|
this.themeMode,
|
||||||
|
});
|
||||||
|
|
||||||
|
String? id;
|
||||||
|
OpusDetailDataModel? detail;
|
||||||
|
int? type;
|
||||||
|
String? theme;
|
||||||
|
String? themeMode;
|
||||||
|
|
||||||
|
OpusDataModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
id = json['id'];
|
||||||
|
detail = json['detail'] != null
|
||||||
|
? OpusDetailDataModel.fromJson(json['detail'])
|
||||||
|
: null;
|
||||||
|
type = json['type'];
|
||||||
|
theme = json['theme'];
|
||||||
|
themeMode = json['themeMode'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OpusDetailDataModel {
|
||||||
|
OpusDetailDataModel({
|
||||||
|
this.basic,
|
||||||
|
this.idStr,
|
||||||
|
this.modules,
|
||||||
|
this.type,
|
||||||
|
});
|
||||||
|
|
||||||
|
Basic? basic;
|
||||||
|
String? idStr;
|
||||||
|
List<OpusModuleDataModel>? modules;
|
||||||
|
int? type;
|
||||||
|
|
||||||
|
OpusDetailDataModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
basic = json['basic'] != null ? Basic.fromJson(json['basic']) : null;
|
||||||
|
idStr = json['id_str'];
|
||||||
|
if (json['modules'] != null) {
|
||||||
|
modules = <OpusModuleDataModel>[];
|
||||||
|
json['modules'].forEach((v) {
|
||||||
|
modules!.add(OpusModuleDataModel.fromJson(v));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
type = json['type'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Basic {
|
||||||
|
Basic({
|
||||||
|
this.commentIdStr,
|
||||||
|
this.commentType,
|
||||||
|
this.ridStr,
|
||||||
|
this.title,
|
||||||
|
this.uid,
|
||||||
|
});
|
||||||
|
|
||||||
|
String? commentIdStr;
|
||||||
|
int? commentType;
|
||||||
|
String? ridStr;
|
||||||
|
String? title;
|
||||||
|
int? uid;
|
||||||
|
|
||||||
|
Basic.fromJson(Map<String, dynamic> json) {
|
||||||
|
commentIdStr = json['comment_id_str'];
|
||||||
|
commentType = json['comment_type'];
|
||||||
|
ridStr = json['rid_str'];
|
||||||
|
title = json['title'];
|
||||||
|
uid = json['uid'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OpusModuleDataModel {
|
||||||
|
OpusModuleDataModel({
|
||||||
|
this.moduleTitle,
|
||||||
|
this.moduleAuthor,
|
||||||
|
this.moduleContent,
|
||||||
|
this.moduleExtend,
|
||||||
|
this.moduleBottom,
|
||||||
|
this.moduleStat,
|
||||||
|
});
|
||||||
|
|
||||||
|
ModuleTop? moduleTop;
|
||||||
|
ModuleTitle? moduleTitle;
|
||||||
|
ModuleAuthor? moduleAuthor;
|
||||||
|
ModuleContent? moduleContent;
|
||||||
|
ModuleExtend? moduleExtend;
|
||||||
|
ModuleBottom? moduleBottom;
|
||||||
|
ModuleStat? moduleStat;
|
||||||
|
|
||||||
|
OpusModuleDataModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
moduleTop = json['module_top'] != null
|
||||||
|
? ModuleTop.fromJson(json['module_top'])
|
||||||
|
: null;
|
||||||
|
moduleTitle = json['module_title'] != null
|
||||||
|
? ModuleTitle.fromJson(json['module_title'])
|
||||||
|
: null;
|
||||||
|
moduleAuthor = json['module_author'] != null
|
||||||
|
? ModuleAuthor.fromJson(json['module_author'])
|
||||||
|
: null;
|
||||||
|
moduleContent = json['module_content'] != null
|
||||||
|
? ModuleContent.fromJson(json['module_content'])
|
||||||
|
: null;
|
||||||
|
moduleExtend = json['module_extend'] != null
|
||||||
|
? ModuleExtend.fromJson(json['module_extend'])
|
||||||
|
: null;
|
||||||
|
moduleBottom = json['module_bottom'] != null
|
||||||
|
? ModuleBottom.fromJson(json['module_bottom'])
|
||||||
|
: null;
|
||||||
|
moduleStat = json['module_stat'] != null
|
||||||
|
? ModuleStat.fromJson(json['module_stat'])
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ModuleTop {
|
||||||
|
ModuleTop({
|
||||||
|
this.type,
|
||||||
|
this.video,
|
||||||
|
});
|
||||||
|
|
||||||
|
int? type;
|
||||||
|
Map? video;
|
||||||
|
|
||||||
|
ModuleTop.fromJson(Map<String, dynamic> json) {
|
||||||
|
type = json['type'];
|
||||||
|
video = json['video'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ModuleTitle {
|
||||||
|
ModuleTitle({
|
||||||
|
this.text,
|
||||||
|
});
|
||||||
|
|
||||||
|
String? text;
|
||||||
|
|
||||||
|
ModuleTitle.fromJson(Map<String, dynamic> json) {
|
||||||
|
text = json['text'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ModuleAuthor {
|
||||||
|
ModuleAuthor({
|
||||||
|
this.face,
|
||||||
|
this.mid,
|
||||||
|
this.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
String? face;
|
||||||
|
int? mid;
|
||||||
|
String? name;
|
||||||
|
|
||||||
|
ModuleAuthor.fromJson(Map<String, dynamic> json) {
|
||||||
|
face = json['face'];
|
||||||
|
mid = json['mid'];
|
||||||
|
name = json['name'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ModuleContent {
|
||||||
|
ModuleContent({
|
||||||
|
this.paragraphs,
|
||||||
|
this.moduleType,
|
||||||
|
});
|
||||||
|
|
||||||
|
List<ModuleParagraph>? paragraphs;
|
||||||
|
String? moduleType;
|
||||||
|
|
||||||
|
ModuleContent.fromJson(Map<String, dynamic> json) {
|
||||||
|
if (json['paragraphs'] != null) {
|
||||||
|
paragraphs = <ModuleParagraph>[];
|
||||||
|
json['paragraphs'].forEach((v) {
|
||||||
|
paragraphs!.add(ModuleParagraph.fromJson(v));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
moduleType = json['module_type'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ModuleParagraph {
|
||||||
|
ModuleParagraph({
|
||||||
|
this.align,
|
||||||
|
this.paraType,
|
||||||
|
this.pic,
|
||||||
|
this.text,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 0 左对齐 1 居中 2 右对齐
|
||||||
|
int? align;
|
||||||
|
int? paraType;
|
||||||
|
Pics? pic;
|
||||||
|
ModuleParagraphText? text;
|
||||||
|
LinkCard? linkCard;
|
||||||
|
|
||||||
|
ModuleParagraph.fromJson(Map<String, dynamic> json) {
|
||||||
|
align = json['align'];
|
||||||
|
paraType = json['para_type'] == null && json['link_card'] != null
|
||||||
|
? 3
|
||||||
|
: json['para_type'];
|
||||||
|
pic = json['pic'] != null ? Pics.fromJson(json['pic']) : null;
|
||||||
|
text = json['text'] != null
|
||||||
|
? ModuleParagraphText.fromJson(json['text'])
|
||||||
|
: null;
|
||||||
|
linkCard =
|
||||||
|
json['link_card'] != null ? LinkCard.fromJson(json['link_card']) : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Pics {
|
||||||
|
Pics({
|
||||||
|
this.pics,
|
||||||
|
this.style,
|
||||||
|
});
|
||||||
|
|
||||||
|
List<Pic>? pics;
|
||||||
|
int? style;
|
||||||
|
|
||||||
|
Pics.fromJson(Map<String, dynamic> json) {
|
||||||
|
if (json['pics'] != null) {
|
||||||
|
pics = <Pic>[];
|
||||||
|
json['pics'].forEach((v) {
|
||||||
|
pics!.add(Pic.fromJson(v));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
style = json['style'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Pic {
|
||||||
|
Pic({
|
||||||
|
this.height,
|
||||||
|
this.size,
|
||||||
|
this.url,
|
||||||
|
this.width,
|
||||||
|
this.aspectRatio,
|
||||||
|
this.scale,
|
||||||
|
});
|
||||||
|
|
||||||
|
int? height;
|
||||||
|
double? size;
|
||||||
|
String? url;
|
||||||
|
int? width;
|
||||||
|
double? aspectRatio;
|
||||||
|
double? scale;
|
||||||
|
|
||||||
|
Pic.fromJson(Map<String, dynamic> json) {
|
||||||
|
height = json['height'];
|
||||||
|
size = json['size'];
|
||||||
|
url = json['url'];
|
||||||
|
width = json['width'];
|
||||||
|
aspectRatio = json['width'] / json['height'];
|
||||||
|
scale = customDivision(json['width'], 600);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LinkCard {
|
||||||
|
LinkCard({
|
||||||
|
this.cover,
|
||||||
|
this.descSecond,
|
||||||
|
this.duration,
|
||||||
|
this.jumpUrl,
|
||||||
|
this.title,
|
||||||
|
});
|
||||||
|
|
||||||
|
String? cover;
|
||||||
|
String? descSecond;
|
||||||
|
String? duration;
|
||||||
|
String? jumpUrl;
|
||||||
|
String? title;
|
||||||
|
|
||||||
|
LinkCard.fromJson(Map<String, dynamic> json) {
|
||||||
|
cover = json['card']['cover'];
|
||||||
|
descSecond = json['card']['desc_second'];
|
||||||
|
duration = json['card']['duration'];
|
||||||
|
jumpUrl = json['card']['jump_url'];
|
||||||
|
title = json['card']['title'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ModuleParagraphText {
|
||||||
|
ModuleParagraphText({
|
||||||
|
this.nodes,
|
||||||
|
});
|
||||||
|
|
||||||
|
List<ModuleParagraphTextNode>? nodes;
|
||||||
|
|
||||||
|
ModuleParagraphText.fromJson(Map<String, dynamic> json) {
|
||||||
|
if (json['nodes'] != null) {
|
||||||
|
nodes = <ModuleParagraphTextNode>[];
|
||||||
|
json['nodes'].forEach((v) {
|
||||||
|
nodes!.add(ModuleParagraphTextNode.fromJson(v));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ModuleParagraphTextNode {
|
||||||
|
ModuleParagraphTextNode({
|
||||||
|
this.type,
|
||||||
|
this.word,
|
||||||
|
});
|
||||||
|
|
||||||
|
String? type;
|
||||||
|
ModuleParagraphTextNodeWord? word;
|
||||||
|
|
||||||
|
ModuleParagraphTextNode.fromJson(Map<String, dynamic> json) {
|
||||||
|
type = json['type'];
|
||||||
|
word = json['word'] != null
|
||||||
|
? ModuleParagraphTextNodeWord.fromJson(json['word'])
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ModuleParagraphTextNodeWord {
|
||||||
|
ModuleParagraphTextNodeWord({
|
||||||
|
this.color,
|
||||||
|
this.fontSize,
|
||||||
|
this.style,
|
||||||
|
this.words,
|
||||||
|
});
|
||||||
|
|
||||||
|
String? color;
|
||||||
|
int? fontSize;
|
||||||
|
ModuleParagraphTextNodeWordStyle? style;
|
||||||
|
String? words;
|
||||||
|
|
||||||
|
ModuleParagraphTextNodeWord.fromJson(Map<String, dynamic> json) {
|
||||||
|
color = json['color'];
|
||||||
|
fontSize = json['font_size'];
|
||||||
|
style = json['style'] != null
|
||||||
|
? ModuleParagraphTextNodeWordStyle.fromJson(json['style'])
|
||||||
|
: null;
|
||||||
|
words = json['words'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ModuleParagraphTextNodeWordStyle {
|
||||||
|
ModuleParagraphTextNodeWordStyle({
|
||||||
|
this.bold,
|
||||||
|
});
|
||||||
|
|
||||||
|
bool? bold;
|
||||||
|
|
||||||
|
ModuleParagraphTextNodeWordStyle.fromJson(Map<String, dynamic> json) {
|
||||||
|
bold = json['bold'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ModuleExtend {
|
||||||
|
ModuleExtend({
|
||||||
|
this.items,
|
||||||
|
});
|
||||||
|
|
||||||
|
List<ModuleExtendItem>? items;
|
||||||
|
|
||||||
|
ModuleExtend.fromJson(Map<String, dynamic> json) {
|
||||||
|
if (json['items'] != null) {
|
||||||
|
items = <ModuleExtendItem>[];
|
||||||
|
json['items'].forEach((v) {
|
||||||
|
items!.add(ModuleExtendItem.fromJson(v));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ModuleExtendItem {
|
||||||
|
ModuleExtendItem({
|
||||||
|
this.bizId,
|
||||||
|
this.bizType,
|
||||||
|
this.icon,
|
||||||
|
this.jumpUrl,
|
||||||
|
this.text,
|
||||||
|
});
|
||||||
|
|
||||||
|
dynamic bizId;
|
||||||
|
int? bizType;
|
||||||
|
dynamic icon;
|
||||||
|
String? jumpUrl;
|
||||||
|
String? text;
|
||||||
|
|
||||||
|
ModuleExtendItem.fromJson(Map<String, dynamic> json) {
|
||||||
|
bizId = json['biz_id'];
|
||||||
|
bizType = json['biz_type'];
|
||||||
|
icon = json['icon'];
|
||||||
|
jumpUrl = json['jump_url'];
|
||||||
|
text = json['text'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ModuleBottom {
|
||||||
|
ModuleBottom({
|
||||||
|
this.shareInfo,
|
||||||
|
});
|
||||||
|
|
||||||
|
ShareInfo? shareInfo;
|
||||||
|
|
||||||
|
ModuleBottom.fromJson(Map<String, dynamic> json) {
|
||||||
|
shareInfo = json['share_info'] != null
|
||||||
|
? ShareInfo.fromJson(json['share_info'])
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ShareInfo {
|
||||||
|
ShareInfo({
|
||||||
|
this.pic,
|
||||||
|
this.summary,
|
||||||
|
this.title,
|
||||||
|
});
|
||||||
|
|
||||||
|
String? pic;
|
||||||
|
String? summary;
|
||||||
|
String? title;
|
||||||
|
|
||||||
|
ShareInfo.fromJson(Map<String, dynamic> json) {
|
||||||
|
pic = json['pic'];
|
||||||
|
summary = json['summary'];
|
||||||
|
title = json['title'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ModuleStat {
|
||||||
|
ModuleStat({
|
||||||
|
this.coin,
|
||||||
|
this.comment,
|
||||||
|
this.favorite,
|
||||||
|
this.forward,
|
||||||
|
this.like,
|
||||||
|
});
|
||||||
|
|
||||||
|
StatItem? coin;
|
||||||
|
StatItem? comment;
|
||||||
|
StatItem? favorite;
|
||||||
|
StatItem? forward;
|
||||||
|
StatItem? like;
|
||||||
|
|
||||||
|
ModuleStat.fromJson(Map<String, dynamic> json) {
|
||||||
|
coin = json['coin'] != null ? StatItem.fromJson(json['coin']) : null;
|
||||||
|
comment =
|
||||||
|
json['comment'] != null ? StatItem.fromJson(json['comment']) : null;
|
||||||
|
favorite =
|
||||||
|
json['favorite'] != null ? StatItem.fromJson(json['favorite']) : null;
|
||||||
|
forward =
|
||||||
|
json['forward'] != null ? StatItem.fromJson(json['forward']) : null;
|
||||||
|
like = json['like'] != null ? StatItem.fromJson(json['like']) : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class StatItem {
|
||||||
|
StatItem({
|
||||||
|
this.count,
|
||||||
|
this.forbidden,
|
||||||
|
this.status,
|
||||||
|
});
|
||||||
|
|
||||||
|
int? count;
|
||||||
|
bool? forbidden;
|
||||||
|
bool? status;
|
||||||
|
|
||||||
|
StatItem.fromJson(Map<String, dynamic> json) {
|
||||||
|
count = json['count'];
|
||||||
|
forbidden = json['forbidden'];
|
||||||
|
status = json['status'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double customDivision(int a, int b) {
|
||||||
|
double result = a / b;
|
||||||
|
if (result < 1) {
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
return 1.0;
|
||||||
|
}
|
||||||
|
}
|
@ -146,20 +146,29 @@ class DynamicsController extends GetxController {
|
|||||||
/// 专栏文章查看
|
/// 专栏文章查看
|
||||||
case 'DYNAMIC_TYPE_ARTICLE':
|
case 'DYNAMIC_TYPE_ARTICLE':
|
||||||
String title = item.modules.moduleDynamic.major.opus.title;
|
String title = item.modules.moduleDynamic.major.opus.title;
|
||||||
String url = item.modules.moduleDynamic.major.opus.jumpUrl;
|
String jumpUrl = item.modules.moduleDynamic.major.opus.jumpUrl;
|
||||||
if (url.contains('opus') || url.contains('read')) {
|
String url =
|
||||||
|
jumpUrl.startsWith('//') ? jumpUrl.split('//').last : jumpUrl;
|
||||||
|
if (jumpUrl.contains('opus') || jumpUrl.contains('read')) {
|
||||||
RegExp digitRegExp = RegExp(r'\d+');
|
RegExp digitRegExp = RegExp(r'\d+');
|
||||||
Iterable<Match> matches = digitRegExp.allMatches(url);
|
Iterable<Match> matches = digitRegExp.allMatches(jumpUrl);
|
||||||
String number = matches.first.group(0)!;
|
String number = matches.first.group(0)!;
|
||||||
if (url.contains('read')) {
|
if (jumpUrl.contains('read')) {
|
||||||
number = 'cv$number';
|
number = 'cv$number';
|
||||||
|
Get.toNamed('/htmlRender', parameters: {
|
||||||
|
'url': url,
|
||||||
|
'title': title,
|
||||||
|
'id': number,
|
||||||
|
'dynamicType': url.split('/')[1]
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Get.toNamed('/opus', parameters: {
|
||||||
|
'url': url,
|
||||||
|
'title': title,
|
||||||
|
'id': number,
|
||||||
|
'dynamicType': url.split('/')[1]
|
||||||
|
});
|
||||||
}
|
}
|
||||||
Get.toNamed('/htmlRender', parameters: {
|
|
||||||
'url': url.startsWith('//') ? url.split('//').last : url,
|
|
||||||
'title': title,
|
|
||||||
'id': number,
|
|
||||||
'dynamicType': url.split('//').last.split('/')[1]
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
Get.toNamed(
|
Get.toNamed(
|
||||||
'/webview',
|
'/webview',
|
||||||
|
28
lib/pages/opus/controller.dart
Normal file
28
lib/pages/opus/controller.dart
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:pilipala/http/read.dart';
|
||||||
|
import 'package:pilipala/models/read/opus.dart';
|
||||||
|
|
||||||
|
class OpusController extends GetxController {
|
||||||
|
late String url;
|
||||||
|
late String title;
|
||||||
|
late String id;
|
||||||
|
late String dynamicType;
|
||||||
|
Rx<OpusDataModel> opusData = OpusDataModel().obs;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onInit() {
|
||||||
|
super.onInit();
|
||||||
|
url = Get.parameters['url']!;
|
||||||
|
title = Get.parameters['title']!;
|
||||||
|
id = Get.parameters['id']!;
|
||||||
|
dynamicType = Get.parameters['dynamicType']!;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future fetchOpusData() async {
|
||||||
|
var res = await ReadHttp.parseArticleOpus(id: id);
|
||||||
|
if (res['status']) {
|
||||||
|
opusData.value = res['data'];
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
4
lib/pages/opus/index.dart
Normal file
4
lib/pages/opus/index.dart
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
library opus;
|
||||||
|
|
||||||
|
export 'controller.dart';
|
||||||
|
export 'view.dart';
|
42
lib/pages/opus/text_helper.dart
Normal file
42
lib/pages/opus/text_helper.dart
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:pilipala/models/read/opus.dart';
|
||||||
|
|
||||||
|
class TextHelper {
|
||||||
|
static Alignment getAlignment(int? align) {
|
||||||
|
switch (align) {
|
||||||
|
case 1:
|
||||||
|
return Alignment.center;
|
||||||
|
case 0:
|
||||||
|
return Alignment.centerLeft;
|
||||||
|
case 2:
|
||||||
|
return Alignment.centerRight;
|
||||||
|
default:
|
||||||
|
return Alignment.centerLeft;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static TextSpan buildTextSpan(
|
||||||
|
ModuleParagraphTextNode node, int? align, BuildContext context) {
|
||||||
|
switch (node.type) {
|
||||||
|
case 'TEXT_NODE_TYPE_WORD':
|
||||||
|
return TextSpan(
|
||||||
|
text: node.word?.words ?? '',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize:
|
||||||
|
node.word?.fontSize != null ? node.word!.fontSize! * 0.95 : 14,
|
||||||
|
fontWeight: node.word?.style?.bold != null
|
||||||
|
? FontWeight.bold
|
||||||
|
: FontWeight.normal,
|
||||||
|
height: align == 1 ? 2 : 1.5,
|
||||||
|
color: node.word?.color != null
|
||||||
|
? Color(
|
||||||
|
int.parse(node.word!.color!.substring(1, 7), radix: 16) +
|
||||||
|
0xFF000000)
|
||||||
|
: Theme.of(context).colorScheme.onBackground,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return const TextSpan(text: '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
230
lib/pages/opus/view.dart
Normal file
230
lib/pages/opus/view.dart
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
|
import 'package:pilipala/models/read/opus.dart';
|
||||||
|
import 'package:pilipala/plugin/pl_gallery/hero_dialog_route.dart';
|
||||||
|
import 'package:pilipala/plugin/pl_gallery/interactiveviewer_gallery.dart';
|
||||||
|
import 'controller.dart';
|
||||||
|
import 'text_helper.dart';
|
||||||
|
|
||||||
|
class OpusPage extends StatefulWidget {
|
||||||
|
const OpusPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<OpusPage> createState() => _OpusPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _OpusPageState extends State<OpusPage> {
|
||||||
|
final OpusController controller = Get.put(OpusController());
|
||||||
|
late Future _futureBuilderFuture;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_futureBuilderFuture = controller.fetchOpusData();
|
||||||
|
}
|
||||||
|
|
||||||
|
void onPreviewImg(picList, initIndex, context) {
|
||||||
|
Navigator.of(context).push(
|
||||||
|
HeroDialogRoute<void>(
|
||||||
|
builder: (BuildContext context) => InteractiveviewerGallery(
|
||||||
|
sources: picList,
|
||||||
|
initIndex: initIndex,
|
||||||
|
itemBuilder: (
|
||||||
|
BuildContext context,
|
||||||
|
int index,
|
||||||
|
bool isFocus,
|
||||||
|
bool enablePageView,
|
||||||
|
) {
|
||||||
|
return GestureDetector(
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
onTap: () {
|
||||||
|
if (enablePageView) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Center(
|
||||||
|
child: Hero(
|
||||||
|
tag: picList[index],
|
||||||
|
child: CachedNetworkImage(
|
||||||
|
fadeInDuration: const Duration(milliseconds: 0),
|
||||||
|
imageUrl: picList[index],
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onPageChanged: (int pageIndex) {},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.more_vert_rounded),
|
||||||
|
onPressed: () {},
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(16, 16, 16, 4),
|
||||||
|
child: Text(
|
||||||
|
controller.title,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
letterSpacing: 1,
|
||||||
|
height: 1.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
FutureBuilder(
|
||||||
|
future: _futureBuilderFuture,
|
||||||
|
builder: (BuildContext context, AsyncSnapshot snapshot) {
|
||||||
|
if (snapshot.connectionState == ConnectionState.done) {
|
||||||
|
if (snapshot.data == null) {
|
||||||
|
return const SizedBox();
|
||||||
|
}
|
||||||
|
if (snapshot.data['status']) {
|
||||||
|
final modules = controller.opusData.value.detail!.modules!;
|
||||||
|
final ModuleStat moduleStat = modules.last.moduleStat!;
|
||||||
|
late ModuleContent moduleContent;
|
||||||
|
final int moduleIndex = modules
|
||||||
|
.indexWhere((module) => module.moduleContent != null);
|
||||||
|
if (moduleIndex != -1) {
|
||||||
|
moduleContent = modules[moduleIndex].moduleContent!;
|
||||||
|
} else {
|
||||||
|
print('No moduleContent found');
|
||||||
|
}
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(16, 0, 16,
|
||||||
|
MediaQuery.of(context).padding.bottom + 40),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(0, 0, 0, 20),
|
||||||
|
child: SelectableText.rich(
|
||||||
|
TextSpan(
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
fontSize: 12,
|
||||||
|
letterSpacing: 1,
|
||||||
|
),
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: '${moduleStat.comment!.count}评论'),
|
||||||
|
const TextSpan(text: ' '),
|
||||||
|
const TextSpan(text: ' '),
|
||||||
|
TextSpan(text: '${moduleStat.like!.count}赞'),
|
||||||
|
const TextSpan(text: ' '),
|
||||||
|
const TextSpan(text: ' '),
|
||||||
|
TextSpan(
|
||||||
|
text: '${moduleStat.favorite!.count}转发'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
...moduleContent.paragraphs!.map(
|
||||||
|
(ModuleParagraph paragraph) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
if (paragraph.paraType == 1) ...[
|
||||||
|
Container(
|
||||||
|
alignment: TextHelper.getAlignment(
|
||||||
|
paragraph.align),
|
||||||
|
margin: const EdgeInsets.only(bottom: 10),
|
||||||
|
child: Text.rich(
|
||||||
|
TextSpan(
|
||||||
|
children: paragraph.text?.nodes
|
||||||
|
?.map((node) {
|
||||||
|
return TextHelper.buildTextSpan(
|
||||||
|
node,
|
||||||
|
paragraph.align,
|
||||||
|
context);
|
||||||
|
}).toList() ??
|
||||||
|
[],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
] else if (paragraph.paraType == 2) ...[
|
||||||
|
...paragraph.pic?.pics?.map(
|
||||||
|
(Pic pic) => Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
top: 10, bottom: 10),
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
onPreviewImg(
|
||||||
|
paragraph.pic!.pics!
|
||||||
|
.map((pic) => pic.url)
|
||||||
|
.toList(),
|
||||||
|
paragraph.pic!.pics!
|
||||||
|
.indexWhere((pic) =>
|
||||||
|
pic.url ==
|
||||||
|
pic.url),
|
||||||
|
context);
|
||||||
|
},
|
||||||
|
child: NetworkImgLayer(
|
||||||
|
src: pic.url,
|
||||||
|
width: (Get.size.width - 32) *
|
||||||
|
pic.scale!,
|
||||||
|
height:
|
||||||
|
(Get.size.width - 32) *
|
||||||
|
pic.scale! /
|
||||||
|
pic.aspectRatio!,
|
||||||
|
type: 'emote',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
) ??
|
||||||
|
[],
|
||||||
|
] else
|
||||||
|
const SizedBox(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// 请求错误
|
||||||
|
return SizedBox(
|
||||||
|
height: 100,
|
||||||
|
child: Center(
|
||||||
|
child: Text(snapshot.data['message']),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return const SizedBox(
|
||||||
|
height: 100,
|
||||||
|
child: Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -9,6 +9,7 @@ import 'package:pilipala/pages/message/at/index.dart';
|
|||||||
import 'package:pilipala/pages/message/like/index.dart';
|
import 'package:pilipala/pages/message/like/index.dart';
|
||||||
import 'package:pilipala/pages/message/reply/index.dart';
|
import 'package:pilipala/pages/message/reply/index.dart';
|
||||||
import 'package:pilipala/pages/message/system/index.dart';
|
import 'package:pilipala/pages/message/system/index.dart';
|
||||||
|
import 'package:pilipala/pages/opus/index.dart';
|
||||||
import 'package:pilipala/pages/setting/pages/logs.dart';
|
import 'package:pilipala/pages/setting/pages/logs.dart';
|
||||||
|
|
||||||
import '../pages/about/index.dart';
|
import '../pages/about/index.dart';
|
||||||
@ -186,6 +187,9 @@ class Routes {
|
|||||||
name: '/messageSystem', page: () => const MessageSystemPage()),
|
name: '/messageSystem', page: () => const MessageSystemPage()),
|
||||||
// 收藏夹编辑
|
// 收藏夹编辑
|
||||||
CustomGetPage(name: '/favEdit', page: () => const FavEditPage()),
|
CustomGetPage(name: '/favEdit', page: () => const FavEditPage()),
|
||||||
|
|
||||||
|
// 专栏
|
||||||
|
CustomGetPage(name: '/opus', page: () => const OpusPage()),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user