From 40fb93f036cf5ad16ba0605afcd81d2fbf6932d8 Mon Sep 17 00:00:00 2001 From: dom Date: Sat, 26 Apr 2025 14:54:22 +0800 Subject: [PATCH] refa: article (#757) Signed-off-by: bggRGjQaUbCoE --- lib/common/widgets/article_content.dart | 94 ----- lib/common/widgets/network_img_layer.dart | 10 +- lib/common/widgets/save_panel.dart | 2 +- lib/common/widgets/video_card_v.dart | 19 +- lib/http/api.dart | 5 + lib/http/dynamics.dart | 36 ++ lib/http/html.dart | 196 --------- .../dynamics/article_content_model.dart | 218 ---------- .../dynamics/article_view/article_view.dart | 26 ++ lib/models/dynamics/article_view/author.dart | 60 +++ .../dynamics/article_view/category.dart | 19 + lib/models/dynamics/article_view/data.dart | 185 +++++++++ lib/models/dynamics/article_view/label.dart | 19 + lib/models/dynamics/article_view/media.dart | 47 +++ .../dynamics/article_view/nameplate.dart | 35 ++ .../article_view/official_verify.dart | 20 + lib/models/dynamics/article_view/pendant.dart | 22 + lib/models/dynamics/article_view/stats.dart | 43 ++ lib/models/dynamics/article_view/tag.dart | 16 + lib/models/dynamics/article_view/vip.dart | 47 +++ lib/models/dynamics/opus_detail/avatar.dart | 26 ++ .../dynamics/opus_detail/avatar_icon.dart | 17 + .../dynamics/opus_detail/avatar_layer.dart | 14 + lib/models/dynamics/opus_detail/basic.dart | 39 ++ lib/models/dynamics/opus_detail/coin.dart | 19 + lib/models/dynamics/opus_detail/comment.dart | 16 + .../dynamics/opus_detail/container_size.dart | 16 + lib/models/dynamics/opus_detail/data.dart | 26 ++ lib/models/dynamics/opus_detail/fallback.dart | 19 + .../dynamics/opus_detail/fallback_layers.dart | 24 ++ lib/models/dynamics/opus_detail/favorite.dart | 19 + lib/models/dynamics/opus_detail/forward.dart | 16 + .../dynamics/opus_detail/general_cfg.dart | 20 + .../dynamics/opus_detail/general_config.dart | 17 + .../dynamics/opus_detail/general_spec.dart | 29 ++ .../dynamics/opus_detail/icon_resource.dart | 14 + .../dynamics/opus_detail/image_src.dart | 23 ++ lib/models/dynamics/opus_detail/item.dart | 29 ++ lib/models/dynamics/opus_detail/label.dart | 59 +++ lib/models/dynamics/opus_detail/layer.dart | 32 ++ .../dynamics/opus_detail/layer_config.dart | 20 + lib/models/dynamics/opus_detail/like.dart | 19 + .../dynamics/opus_detail/like_icon.dart | 22 + lib/models/dynamics/opus_detail/module.dart | 63 +++ .../dynamics/opus_detail/module_author.dart | 84 ++++ .../dynamics/opus_detail/module_bottom.dart | 17 + .../dynamics/opus_detail/module_content.dart | 17 + .../dynamics/opus_detail/module_extend.dart | 17 + .../dynamics/opus_detail/module_stat.dart | 47 +++ .../dynamics/opus_detail/module_title.dart | 13 + lib/models/dynamics/opus_detail/node.dart | 30 ++ lib/models/dynamics/opus_detail/official.dart | 22 + .../dynamics/opus_detail/opus_detail.dart | 26 ++ .../dynamics/opus_detail/paragraph.dart | 42 ++ lib/models/dynamics/opus_detail/pendant.dart | 39 ++ lib/models/dynamics/opus_detail/pic.dart | 76 ++++ lib/models/dynamics/opus_detail/pos_spec.dart | 19 + lib/models/dynamics/opus_detail/remote.dart | 16 + .../dynamics/opus_detail/render_spec.dart | 13 + .../dynamics/opus_detail/res_image.dart | 17 + lib/models/dynamics/opus_detail/resource.dart | 20 + lib/models/dynamics/opus_detail/rich.dart | 31 ++ .../dynamics/opus_detail/share_info.dart | 19 + .../dynamics/opus_detail/size_spec.dart | 16 + lib/models/dynamics/opus_detail/style.dart | 22 + lib/models/dynamics/opus_detail/tags.dart | 26 ++ lib/models/dynamics/opus_detail/text.dart | 17 + lib/models/dynamics/opus_detail/vip.dart | 73 ++++ .../dynamics/opus_detail/web_css_style.dart | 13 + lib/models/dynamics/opus_detail/word.dart | 28 ++ lib/pages/article/controller.dart | 198 +++++++++ lib/pages/{html => article}/view.dart | 382 +++++++++--------- .../article}/widgets/html_render.dart | 5 +- lib/pages/article/widgets/opus_content.dart | 196 +++++++++ lib/pages/dynamics/detail/controller.dart | 17 +- lib/pages/dynamics/detail/view.dart | 24 +- lib/pages/fav/article/widget/item.dart | 10 +- lib/pages/history/widgets/item.dart | 10 +- lib/pages/html/controller.dart | 152 ------- lib/pages/html/index.dart | 4 - .../search_panel/article/controller.dart | 8 +- lib/pages/search_panel/article/view.dart | 13 +- .../detail/reply/widgets/reply_item_grpc.dart | 26 +- .../whisper_detail/widget/chat_item.dart | 21 +- lib/router/app_pages.dart | 4 +- lib/utils/app_scheme.dart | 48 +-- lib/utils/page_utils.dart | 26 +- 87 files changed, 2628 insertions(+), 993 deletions(-) delete mode 100644 lib/common/widgets/article_content.dart delete mode 100644 lib/http/html.dart delete mode 100644 lib/models/dynamics/article_content_model.dart create mode 100644 lib/models/dynamics/article_view/article_view.dart create mode 100644 lib/models/dynamics/article_view/author.dart create mode 100644 lib/models/dynamics/article_view/category.dart create mode 100644 lib/models/dynamics/article_view/data.dart create mode 100644 lib/models/dynamics/article_view/label.dart create mode 100644 lib/models/dynamics/article_view/media.dart create mode 100644 lib/models/dynamics/article_view/nameplate.dart create mode 100644 lib/models/dynamics/article_view/official_verify.dart create mode 100644 lib/models/dynamics/article_view/pendant.dart create mode 100644 lib/models/dynamics/article_view/stats.dart create mode 100644 lib/models/dynamics/article_view/tag.dart create mode 100644 lib/models/dynamics/article_view/vip.dart create mode 100644 lib/models/dynamics/opus_detail/avatar.dart create mode 100644 lib/models/dynamics/opus_detail/avatar_icon.dart create mode 100644 lib/models/dynamics/opus_detail/avatar_layer.dart create mode 100644 lib/models/dynamics/opus_detail/basic.dart create mode 100644 lib/models/dynamics/opus_detail/coin.dart create mode 100644 lib/models/dynamics/opus_detail/comment.dart create mode 100644 lib/models/dynamics/opus_detail/container_size.dart create mode 100644 lib/models/dynamics/opus_detail/data.dart create mode 100644 lib/models/dynamics/opus_detail/fallback.dart create mode 100644 lib/models/dynamics/opus_detail/fallback_layers.dart create mode 100644 lib/models/dynamics/opus_detail/favorite.dart create mode 100644 lib/models/dynamics/opus_detail/forward.dart create mode 100644 lib/models/dynamics/opus_detail/general_cfg.dart create mode 100644 lib/models/dynamics/opus_detail/general_config.dart create mode 100644 lib/models/dynamics/opus_detail/general_spec.dart create mode 100644 lib/models/dynamics/opus_detail/icon_resource.dart create mode 100644 lib/models/dynamics/opus_detail/image_src.dart create mode 100644 lib/models/dynamics/opus_detail/item.dart create mode 100644 lib/models/dynamics/opus_detail/label.dart create mode 100644 lib/models/dynamics/opus_detail/layer.dart create mode 100644 lib/models/dynamics/opus_detail/layer_config.dart create mode 100644 lib/models/dynamics/opus_detail/like.dart create mode 100644 lib/models/dynamics/opus_detail/like_icon.dart create mode 100644 lib/models/dynamics/opus_detail/module.dart create mode 100644 lib/models/dynamics/opus_detail/module_author.dart create mode 100644 lib/models/dynamics/opus_detail/module_bottom.dart create mode 100644 lib/models/dynamics/opus_detail/module_content.dart create mode 100644 lib/models/dynamics/opus_detail/module_extend.dart create mode 100644 lib/models/dynamics/opus_detail/module_stat.dart create mode 100644 lib/models/dynamics/opus_detail/module_title.dart create mode 100644 lib/models/dynamics/opus_detail/node.dart create mode 100644 lib/models/dynamics/opus_detail/official.dart create mode 100644 lib/models/dynamics/opus_detail/opus_detail.dart create mode 100644 lib/models/dynamics/opus_detail/paragraph.dart create mode 100644 lib/models/dynamics/opus_detail/pendant.dart create mode 100644 lib/models/dynamics/opus_detail/pic.dart create mode 100644 lib/models/dynamics/opus_detail/pos_spec.dart create mode 100644 lib/models/dynamics/opus_detail/remote.dart create mode 100644 lib/models/dynamics/opus_detail/render_spec.dart create mode 100644 lib/models/dynamics/opus_detail/res_image.dart create mode 100644 lib/models/dynamics/opus_detail/resource.dart create mode 100644 lib/models/dynamics/opus_detail/rich.dart create mode 100644 lib/models/dynamics/opus_detail/share_info.dart create mode 100644 lib/models/dynamics/opus_detail/size_spec.dart create mode 100644 lib/models/dynamics/opus_detail/style.dart create mode 100644 lib/models/dynamics/opus_detail/tags.dart create mode 100644 lib/models/dynamics/opus_detail/text.dart create mode 100644 lib/models/dynamics/opus_detail/vip.dart create mode 100644 lib/models/dynamics/opus_detail/web_css_style.dart create mode 100644 lib/models/dynamics/opus_detail/word.dart create mode 100644 lib/pages/article/controller.dart rename lib/pages/{html => article}/view.dart (78%) rename lib/{common => pages/article}/widgets/html_render.dart (97%) create mode 100644 lib/pages/article/widgets/opus_content.dart delete mode 100644 lib/pages/html/controller.dart delete mode 100644 lib/pages/html/index.dart diff --git a/lib/common/widgets/article_content.dart b/lib/common/widgets/article_content.dart deleted file mode 100644 index d1150c458..000000000 --- a/lib/common/widgets/article_content.dart +++ /dev/null @@ -1,94 +0,0 @@ -import 'package:PiliPlus/common/widgets/interactiveviewer_gallery/interactiveviewer_gallery.dart' - show SourceModel; -import 'package:PiliPlus/common/widgets/network_img_layer.dart'; -import 'package:PiliPlus/models/dynamics/article_content_model.dart'; -import 'package:PiliPlus/utils/extension.dart'; -import 'package:cached_network_image/cached_network_image.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_html/flutter_html.dart'; - -Widget articleContent({ - required BuildContext context, - required List list, - Function(List, int)? callback, - required double maxWidth, -}) { - debugPrint('articleContent'); - List? imgList = list - .where((item) => item.pic != null) - .toList() - .map((item) => item.pic?.pics?.first.url ?? '') - .toList(); - return SliverList.separated( - itemCount: list.length, - itemBuilder: (context, index) { - ArticleContentModel item = list[index]; - if (item.text != null) { - List spanList = []; - item.text?.nodes?.forEach((item) { - spanList.add(TextSpan( - text: item.word?.words, - style: TextStyle( - letterSpacing: 0.3, - fontSize: 17, - height: LineHeight.percent(125).size, - fontStyle: - item.word?.style?.italic == true ? FontStyle.italic : null, - color: item.word?.color != null - ? Color(int.parse( - item.word!.color!.replaceFirst('#', 'FF'), - radix: 16, - )) - : null, - decoration: item.word?.style?.strikethrough == true - ? TextDecoration.lineThrough - : null, - fontWeight: - item.word?.style?.bold == true ? FontWeight.bold : null, - ), - )); - }); - return SelectableText.rich(TextSpan(children: spanList)); - } else if (item.line != null) { - return Container( - alignment: Alignment.center, - padding: const EdgeInsets.symmetric(vertical: 10), - child: CachedNetworkImage( - imageUrl: item.line?.pic?.url?.http2https ?? '', - height: item.line?.pic?.height?.toDouble(), - ), - ); - } else if (item.pic != null) { - return Hero( - tag: item.pic!.pics!.first.url!, - child: GestureDetector( - onTap: () { - if (callback != null) { - callback( - imgList, - imgList.indexOf(item.pic!.pics!.first.url!), - ); - } else { - context.imageView( - initialPage: imgList.indexOf(item.pic!.pics!.first.url!), - imgList: imgList.map((url) => SourceModel(url: url)).toList(), - ); - } - }, - child: NetworkImgLayer( - width: maxWidth, - height: maxWidth * - item.pic!.pics!.first.height! / - item.pic!.pics!.first.width!, - src: item.pic!.pics!.first.url, - ), - ), - ); - } else { - return const SizedBox.shrink(); - // return Text('unsupported content'); - } - }, - separatorBuilder: (context, index) => const SizedBox(height: 10), - ); -} diff --git a/lib/common/widgets/network_img_layer.dart b/lib/common/widgets/network_img_layer.dart index a503bed70..fc38a5cbb 100644 --- a/lib/common/widgets/network_img_layer.dart +++ b/lib/common/widgets/network_img_layer.dart @@ -9,14 +9,13 @@ class NetworkImgLayer extends StatelessWidget { super.key, this.src, required this.width, - required this.height, + this.height, this.type, this.fadeOutDuration, this.fadeInDuration, // 图片质量 默认1% this.quality, this.semanticsLabel, - this.ignoreHeight, this.radius, this.imageBuilder, this.isLongPic, @@ -27,13 +26,12 @@ class NetworkImgLayer extends StatelessWidget { final String? src; final double width; - final double height; + final double? height; final String? type; final Duration? fadeOutDuration; final Duration? fadeInDuration; final int? quality; final String? semanticsLabel; - final bool? ignoreHeight; final double? radius; final ImageWidgetBuilder? imageBuilder; final Function? isLongPic; @@ -59,7 +57,7 @@ class NetworkImgLayer extends StatelessWidget { Widget _buildImage(context) { int? memCacheWidth, memCacheHeight; - if (ignoreHeight == true || callback?.call() == true || width <= height) { + if (height == null || callback?.call() == true || width <= height!) { memCacheWidth = width.cacheSize(context); } else { memCacheHeight = height.cacheSize(context); @@ -67,7 +65,7 @@ class NetworkImgLayer extends StatelessWidget { return CachedNetworkImage( imageUrl: Utils.thumbnailImgUrl(src, quality), width: width, - height: ignoreHeight == null || ignoreHeight == false ? height : null, + height: height, memCacheWidth: memCacheWidth, memCacheHeight: memCacheHeight, fit: boxFit ?? BoxFit.cover, diff --git a/lib/common/widgets/save_panel.dart b/lib/common/widgets/save_panel.dart index 5dcc92ff0..ab403592e 100644 --- a/lib/common/widgets/save_panel.dart +++ b/lib/common/widgets/save_panel.dart @@ -138,7 +138,7 @@ class _SavePanelState extends State { 'bilibili://comment/detail/$type/$oid/$rootId/?${anchor}enterUri=$enterUri', }; } catch (_) {} - } else if (currentRoute.startsWith('/htmlRender')) { + } else if (currentRoute.startsWith('/articlePage')) { try { final type = _item.type.toInt(); late final oid = _item.oid; diff --git a/lib/common/widgets/video_card_v.dart b/lib/common/widgets/video_card_v.dart index 10132342a..9b8ce3eb2 100644 --- a/lib/common/widgets/video_card_v.dart +++ b/lib/common/widgets/video_card_v.dart @@ -53,16 +53,16 @@ class VideoCardV extends StatelessWidget { // 动态 case 'picture': try { - String dynamicType = 'picture'; + String type = 'picture'; String uri = videoItem.uri!; String id = ''; if (uri.startsWith('bilibili://article/')) { - dynamicType = 'read'; + type = 'read'; RegExp regex = RegExp(r'\d+'); Match match = regex.firstMatch(uri)!; String matchedNumber = match.group(0)!; videoItem.param = int.parse(matchedNumber); - id = 'cv${videoItem.param}'; + id = '${videoItem.param}'; } if (uri.startsWith('http')) { String id = Uri.parse(uri).path.split('/')[1]; @@ -71,12 +71,13 @@ class VideoCardV extends StatelessWidget { return; } } - Get.toNamed('/htmlRender', parameters: { - 'url': uri, - 'title': videoItem.title, - 'id': id, - 'dynamicType': dynamicType - }); + Get.toNamed( + '/articlePage', + parameters: { + 'id': id, + 'type': type, + }, + ); } catch (err) { SmartDialog.showToast(err.toString()); } diff --git a/lib/http/api.dart b/lib/http/api.dart index 6b38058d8..3ce31963c 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -781,4 +781,9 @@ class Api { static const String articleInfo = '/x/article/viewinfo'; static const String dynamicReport = '/x/dynamic/feed/dynamic_report/add'; + + // https://github.com/SocialSisterYi/bilibili-API-collect/pull/1242 + static const String articleView = '/x/article/view'; + + static const String opusDetail = '/x/polymer/web-dynamic/v1/opus/detail'; } diff --git a/lib/http/dynamics.dart b/lib/http/dynamics.dart index f55dd61ea..ad68ade10 100644 --- a/lib/http/dynamics.dart +++ b/lib/http/dynamics.dart @@ -1,4 +1,6 @@ import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/models/dynamics/article_view/data.dart'; +import 'package:PiliPlus/models/dynamics/opus_detail/data.dart'; import 'package:PiliPlus/utils/accounts/account.dart'; import 'package:PiliPlus/utils/storage.dart'; import 'package:PiliPlus/utils/wbi_sign.dart'; @@ -157,4 +159,38 @@ class DynamicsHttp { return {'status': false, 'msg': res.data['message']}; } } + + static Future articleView({ + required dynamic cvId, + }) async { + var res = await Request().get( + Api.articleView, + queryParameters: await WbiSign.makSign({ + 'id': cvId, + 'gaia_source': 'main_web', + }), + ); + if (res.data['code'] == 0) { + return {'status': true, 'data': ArticleData.fromJson(res.data['data'])}; + } else { + return {'status': false, 'msg': res.data['message']}; + } + } + + static Future opusDetail({ + required dynamic opusId, + }) async { + var res = await Request().get( + Api.opusDetail, + queryParameters: await WbiSign.makSign({ + 'id': opusId, + 'features': 'htmlNewStyle', + }), + ); + if (res.data['code'] == 0) { + return {'status': true, 'data': OpusData.fromJson(res.data['data'])}; + } else { + return {'status': false, 'msg': res.data['message']}; + } + } } diff --git a/lib/http/html.dart b/lib/http/html.dart deleted file mode 100644 index 42b0437f8..000000000 --- a/lib/http/html.dart +++ /dev/null @@ -1,196 +0,0 @@ -import 'dart:convert'; - -import 'package:PiliPlus/models/dynamics/article_content_model.dart'; -import 'package:PiliPlus/utils/url_utils.dart'; -import 'package:dio/dio.dart'; -import 'package:flutter/material.dart'; -import 'package:html/dom.dart' as dom; -import 'package:html/parser.dart' as parser; -import 'index.dart'; - -class HtmlHttp { - // article - static Future reqHtml(id, dynamicType) async { - var response = await Request().get( - "https://www.bilibili.com/opus/$id", - extra: {'ua': 'pc'}, - options: Options( - followRedirects: false, - validateStatus: (status) => true, - ), - ); - if (response.data is! String && response.data is! List) { - return; - } - try { - if (response.data.contains('Redirecting to')) { - RegExpMatch? cvid = - RegExp(r'/([a-zA-Z]+)/(cv\d+)').firstMatch(response.data); - if (cvid?.group(2) != null) { - return await reqReadHtml(cvid?.group(2), cvid?.group(1), false); - } - - RegExp regex = RegExp(r'//([\w\.]+)/(\w+)/(\w+)'); - Match match = regex.firstMatch(response.data)!; - String matchedString = match.group(0)!; - response = await Request().get( - 'https:$matchedString/', - extra: {'ua': 'pc'}, - ); - } - dom.Document rootTree = parser.parse(response.data); - // log(response.data.body.toString()); - dom.Element body = rootTree.body!; - dom.Element appDom = body.querySelector('#app')!; - dom.Element authorHeader = appDom.querySelector('.fixed-author-header')!; - // 头像 - String avatar = authorHeader.querySelector('img')!.attributes['src']!; - avatar = 'https:${avatar.split('@')[0]}'; - String uname = authorHeader - .querySelector('.fixed-author-header__author__name')! - .text; - - // 动态详情 - dom.Element opusDetail = appDom.querySelector('.opus-detail')!; - // 发布时间 - String updateTime = - opusDetail.querySelector('.opus-module-author__pub__text')!.text; - // - String opusContent = - opusDetail.querySelector('.opus-module-content')!.innerHtml; - String? test; - try { - test = opusDetail - .querySelector('.horizontal-scroll-album__pic__img')! - .innerHtml; - } catch (_) {} - - List comment = opusDetail - .querySelector('.bili-comment-container')! - .className - .split(' ')[1] - .split('-'); - // List imgList = opusDetail.querySelectorAll('bili-album__preview__picture__img'); - - dynamic mid; - Map? favorite; - try { - final regex = RegExp(r'window\.__INITIAL_STATE__\s*=\s*(\{.*?\});'); - final match = regex.firstMatch(response.data); - if (match != null) { - final json = jsonDecode(match.group(1)!); - mid = json['detail']['basic']['uid']; - favorite = json['detail']['modules'].last['module_stat']['favorite']; - } - } catch (e) { - debugPrint('req html: $e'); - } - - return { - 'status': true, - 'mid': mid, - 'avatar': avatar, - 'uname': uname, - 'updateTime': updateTime, - 'content': (test ?? '') + opusContent, - 'commentType': int.parse(comment[1]), - 'commentId': int.parse(comment[2]), - 'favorite': favorite, - }; - } catch (err) { - debugPrint('err: $err'); - } - } - - // read - static Future reqReadHtml(id, dynamicType, [bool redirect = true]) async { - if (redirect) { - String? redirectUrl = await UrlUtils.parseRedirectUrl( - 'https://www.bilibili.com/$dynamicType/$id/'); - if (redirectUrl != null) { - return await reqHtml(redirectUrl.split('/').last, dynamicType); - } - } - - var response = await Request().get( - "https://www.bilibili.com/$dynamicType/$id/", - extra: {'ua': 'pc'}, - options: Options( - headers: { - 'cookie': 'opus-goback=1', - }, - ), - ); - if (response.data is! String && response.data is! List) { - return; - } - try { - dom.Document rootTree = parser.parse(response.data); - dom.Element body = rootTree.body!; - dom.Element appDom = body.querySelector('#app')!; - dom.Element authorHeader = appDom.querySelector('.up-left')!; - // 头像 - // String avatar = - // authorHeader.querySelector('.bili-avatar-img')!.attributes['data-src']!; - // 正则寻找形如"author":{"mid":\d+,"name":".*","face":"xxxx"的匹配项 - final match = - RegExp(r'"author":\{"mid":(\d+)?,"name":".+?","face":"(.+?)"') - .firstMatch(response.data)!; - String mid = match.group(1)!; - String avatar = match.group(2)!.replaceAll(r'\u002F', '/').split('@')[0]; - // debugPrint(avatar); - String uname = authorHeader.querySelector('.up-name')!.text.trim(); - // 动态详情 - dom.Element opusDetail = appDom.querySelector('.article-content')!; - // 发布时间 - // String updateTime = - // opusDetail.querySelector('.opus-module-author__pub__text')!.text; - // debugPrint(updateTime); - - // - dynamic opusContent = - opusDetail.querySelector('#read-article-holder')?.innerHtml ?? ''; - - bool isJsonContent = false; - if (opusContent.isEmpty) { - final regex = RegExp(r'window\.__INITIAL_STATE__\s*=\s*(\{.*?\});'); - final match = regex.firstMatch(response.data); - if (match != null) { - final jsonString = match.group(1); - if (jsonString != null) { - try { - dynamic json = jsonDecode(jsonString); - opusContent = json['readInfo']['content']; - try { - opusContent = (json['readInfo']?['opus']?['content'] - ?['paragraphs'] as List) - .map((item) => ArticleContentModel.fromJson(item)) - .toList(); - isJsonContent = true; - } catch (e) { - debugPrint('second: $e'); - } - } catch (e) { - debugPrint('first: $e'); - } - } - } - } - - String number = RegExp(r'\d+').firstMatch(id)!.group(0)!; - return { - 'status': true, - 'mid': mid, - 'avatar': avatar, - 'uname': uname, - 'updateTime': '', - 'content': opusContent, - 'isJsonContent': isJsonContent, - 'commentType': 12, - 'commentId': int.parse(number), - }; - } catch (e) { - debugPrint(e.toString()); - } - } -} diff --git a/lib/models/dynamics/article_content_model.dart b/lib/models/dynamics/article_content_model.dart deleted file mode 100644 index d51a62551..000000000 --- a/lib/models/dynamics/article_content_model.dart +++ /dev/null @@ -1,218 +0,0 @@ -class ArticleContentModel { - ArticleContentModel({ - this.paraType, - this.text, - this.format, - this.line, - }); - int? paraType; - Text? text; - Format? format; - Line? line; - Pic? pic; - - ArticleContentModel.fromJson(Map json) { - paraType = json['para_type']; - text = json['text'] == null ? null : Text.fromJson(json['text']); - format = json['format'] == null ? null : Format.fromJson(json['format']); - line = json['line'] == null ? null : Line.fromJson(json['line']); - pic = json['pic'] == null ? null : Pic.fromJson(json['pic']); - } -} - -class Pic { - Pic({ - this.url, - this.width, - this.height, - this.size, - this.pics, - this.style, - }); - String? url; - int? width; - int? height; - double? size; - List? pics; - int? style; - - Pic.fromJson(Map json) { - url = json['url']; - width = json['width']; - height = json['height']; - size = json['size']; - pics = (json['pics'] as List?) - ?.map((item) => Pic.fromJson(item)) - .toList(); - style = json['style']; - } -} - -class Line { - Line({ - this.pic, - }); - Pic? pic; - - Line.fromJson(Map json) { - pic = json['pic'] == null ? null : Pic.fromJson(json['pic']); - } -} - -class Format { - Format({ - this.align, - }); - int? align; - - Format.fromJson(Map json) { - align = json['align']; - } -} - -class Text { - Text({ - this.nodes, - }); - List? nodes; - - Text.fromJson(Map json) { - nodes = (json['nodes'] as List?) - ?.map((item) => Nodes.fromJson(item)) - .toList(); - } -} - -class Nodes { - Nodes({ - this.nodeType, - this.word, - }); - int? nodeType; - Word? word; - - Nodes.fromJson(Map json) { - nodeType = json['node_type']; - word = json['word'] == null ? null : Word.fromJson(json['word']); - } -} - -class Word { - Word({ - this.words, - this.fontSize, - this.style, - this.color, - }); - String? words; - int? fontSize; - Style? style; - String? color; - - Word.fromJson(Map json) { - words = json['words']; - fontSize = json['font_size']; - style = json['style'] == null ? null : Style.fromJson(json['style']); - color = json['color']; - } -} - -class Style { - Style({ - this.bold, - this.italic, - this.strikethrough, - }); - bool? bold; - bool? italic; - bool? strikethrough; - - Style.fromJson(Map json) { - bold = json['bold']; - italic = json['italic']; - strikethrough = json['strikethrough']; - } -} - -// class ArticleContentModel { -// ArticleContentModel({ -// this.attributes, -// this.insert, -// }); -// Attributes? attributes; -// dynamic insert; - -// ArticleContentModel.fromJson(Map json) { -// attributes = json['attributes'] == null -// ? null -// : Attributes.fromJson(json['attributes']); -// insert = json['insert'] == null -// ? null -// : json['attributes']?['class'] == 'normal-img' -// ? Insert.fromJson(json['insert']) -// : json['insert']; -// } -// } - -// class Insert { -// Insert({ -// this.nativeImage, -// }); -// NativeImage? nativeImage; - -// Insert.fromJson(Map json) { -// nativeImage = json['native-image'] == null -// ? null -// : NativeImage.fromJson(json['native-image']); -// } -// } - -// class NativeImage { -// NativeImage({ -// this.alt, -// this.url, -// this.width, -// this.height, -// this.size, -// this.status, -// }); - -// dynamic alt; -// dynamic url; -// dynamic width; -// dynamic height; -// dynamic size; -// dynamic status; - -// NativeImage.fromJson(Map json) { -// alt = json['alt']; -// url = json['url']; -// width = json['width']; -// height = json['height']; -// size = json['size']; -// status = json['status']; -// } -// } - -// class Attributes { -// Attributes({ -// this.clazz, -// this.bold, -// this.color, -// this.italic, -// this.strike, -// }); -// String? clazz; -// bool? bold; -// String? color; -// bool? italic; -// bool? strike; - -// Attributes.fromJson(Map json) { -// clazz = json['class']; -// bold = json['bold']; -// color = json['color']; -// italic = json['italic']; -// strike = json['strike']; -// } -// } diff --git a/lib/models/dynamics/article_view/article_view.dart b/lib/models/dynamics/article_view/article_view.dart new file mode 100644 index 000000000..dd0089a35 --- /dev/null +++ b/lib/models/dynamics/article_view/article_view.dart @@ -0,0 +1,26 @@ +import 'data.dart'; + +class ArticleView { + int? code; + String? message; + int? ttl; + ArticleData? data; + + ArticleView({this.code, this.message, this.ttl, this.data}); + + factory ArticleView.fromJson(Map json) => ArticleView( + code: json['code'] as int?, + message: json['message'] as String?, + ttl: json['ttl'] as int?, + data: json['data'] == null + ? null + : ArticleData.fromJson(json['data'] as Map), + ); + + Map toJson() => { + 'code': code, + 'message': message, + 'ttl': ttl, + 'data': data?.toJson(), + }; +} diff --git a/lib/models/dynamics/article_view/author.dart b/lib/models/dynamics/article_view/author.dart new file mode 100644 index 000000000..35f74ab18 --- /dev/null +++ b/lib/models/dynamics/article_view/author.dart @@ -0,0 +1,60 @@ +import 'nameplate.dart'; +import 'official_verify.dart'; +import 'pendant.dart'; +import 'vip.dart'; + +class Author { + int? mid; + String? name; + String? face; + Pendant? pendant; + OfficialVerify? officialVerify; + Nameplate? nameplate; + Vip? vip; + int? fans; + int? level; + + Author({ + this.mid, + this.name, + this.face, + this.pendant, + this.officialVerify, + this.nameplate, + this.vip, + this.fans, + this.level, + }); + + factory Author.fromJson(Map json) => Author( + mid: json['mid'] as int?, + name: json['name'] as String?, + face: json['face'] as String?, + pendant: json['pendant'] == null + ? null + : Pendant.fromJson(json['pendant'] as Map), + officialVerify: json['official_verify'] == null + ? null + : OfficialVerify.fromJson(json['official_verify'] as Map), + nameplate: json['nameplate'] == null + ? null + : Nameplate.fromJson(json['nameplate'] as Map), + vip: json['vip'] == null + ? null + : Vip.fromJson(json['vip'] as Map), + fans: json['fans'] as int?, + level: json['level'] as int?, + ); + + Map toJson() => { + 'mid': mid, + 'name': name, + 'face': face, + 'pendant': pendant?.toJson(), + 'official_verify': officialVerify?.toJson(), + 'nameplate': nameplate?.toJson(), + 'vip': vip?.toJson(), + 'fans': fans, + 'level': level, + }; +} diff --git a/lib/models/dynamics/article_view/category.dart b/lib/models/dynamics/article_view/category.dart new file mode 100644 index 000000000..ea85d9c4e --- /dev/null +++ b/lib/models/dynamics/article_view/category.dart @@ -0,0 +1,19 @@ +class Category { + int? id; + int? parentId; + String? name; + + Category({this.id, this.parentId, this.name}); + + factory Category.fromJson(Map json) => Category( + id: json['id'] as int?, + parentId: json['parent_id'] as int?, + name: json['name'] as String?, + ); + + Map toJson() => { + 'id': id, + 'parent_id': parentId, + 'name': name, + }; +} diff --git a/lib/models/dynamics/article_view/data.dart b/lib/models/dynamics/article_view/data.dart new file mode 100644 index 000000000..8889da01b --- /dev/null +++ b/lib/models/dynamics/article_view/data.dart @@ -0,0 +1,185 @@ +import 'author.dart'; +import 'category.dart'; +import 'media.dart'; +import 'stats.dart'; +import 'tag.dart'; + +class ArticleData { + int? id; + Category? category; + List? categories; + String? title; + String? summary; + String? bannerUrl; + int? templateId; + int? state; + Author? author; + int? reprint; + List? imageUrls; + int? publishTime; + int? ctime; + int? mtime; + Stats? stats; + List? tags; + int? words; + List? originImageUrls; + dynamic list; + bool? isLike; + Media? media; + String? applyTime; + String? checkTime; + int? original; + int? actId; + dynamic dispute; + dynamic authenMark; + int? coverAvid; + dynamic topVideoInfo; + int? type; + int? checkState; + int? originTemplateId; + int? privatePub; + dynamic contentPicList; + String? content; + String? keywords; + int? versionId; + String? dynIdStr; + int? totalArtNum; + + ArticleData({ + this.id, + this.category, + this.categories, + this.title, + this.summary, + this.bannerUrl, + this.templateId, + this.state, + this.author, + this.reprint, + this.imageUrls, + this.publishTime, + this.ctime, + this.mtime, + this.stats, + this.tags, + this.words, + this.originImageUrls, + this.list, + this.isLike, + this.media, + this.applyTime, + this.checkTime, + this.original, + this.actId, + this.dispute, + this.authenMark, + this.coverAvid, + this.topVideoInfo, + this.type, + this.checkState, + this.originTemplateId, + this.privatePub, + this.contentPicList, + this.content, + this.keywords, + this.versionId, + this.dynIdStr, + this.totalArtNum, + }); + + factory ArticleData.fromJson(Map json) => ArticleData( + id: json['id'] as int?, + category: json['category'] == null + ? null + : Category.fromJson(json['category'] as Map), + categories: (json['categories'] as List?) + ?.map((e) => Category.fromJson(e as Map)) + .toList(), + title: json['title'] as String?, + summary: json['summary'] as String?, + bannerUrl: json['banner_url'] as String?, + templateId: json['template_id'] as int?, + state: json['state'] as int?, + author: json['author'] == null + ? null + : Author.fromJson(json['author'] as Map), + reprint: json['reprint'] as int?, + imageUrls: json['image_urls'], + publishTime: json['publish_time'] as int?, + ctime: json['ctime'] as int?, + mtime: json['mtime'] as int?, + stats: json['stats'] == null + ? null + : Stats.fromJson(json['stats'] as Map), + tags: (json['tags'] as List?) + ?.map((e) => Tag.fromJson(e as Map)) + .toList(), + words: json['words'] as int?, + originImageUrls: json['origin_image_urls'], + list: json['list'] as dynamic, + isLike: json['is_like'] as bool?, + media: json['media'] == null + ? null + : Media.fromJson(json['media'] as Map), + applyTime: json['apply_time'] as String?, + checkTime: json['check_time'] as String?, + original: json['original'] as int?, + actId: json['act_id'] as int?, + dispute: json['dispute'] as dynamic, + authenMark: json['authenMark'] as dynamic, + coverAvid: json['cover_avid'] as int?, + topVideoInfo: json['top_video_info'] as dynamic, + type: json['type'] as int?, + checkState: json['check_state'] as int?, + originTemplateId: json['origin_template_id'] as int?, + privatePub: json['private_pub'] as int?, + contentPicList: json['content_pic_list'] as dynamic, + content: json['content'] as String?, + keywords: json['keywords'] as String?, + versionId: json['version_id'] as int?, + dynIdStr: json['dyn_id_str'] as String?, + totalArtNum: json['total_art_num'] as int?, + ); + + Map toJson() => { + 'id': id, + 'category': category?.toJson(), + 'categories': categories?.map((e) => e.toJson()).toList(), + 'title': title, + 'summary': summary, + 'banner_url': bannerUrl, + 'template_id': templateId, + 'state': state, + 'author': author?.toJson(), + 'reprint': reprint, + 'image_urls': imageUrls, + 'publish_time': publishTime, + 'ctime': ctime, + 'mtime': mtime, + 'stats': stats?.toJson(), + 'tags': tags?.map((e) => e.toJson()).toList(), + 'words': words, + 'origin_image_urls': originImageUrls, + 'list': list, + 'is_like': isLike, + 'media': media?.toJson(), + 'apply_time': applyTime, + 'check_time': checkTime, + 'original': original, + 'act_id': actId, + 'dispute': dispute, + 'authenMark': authenMark, + 'cover_avid': coverAvid, + 'top_video_info': topVideoInfo, + 'type': type, + 'check_state': checkState, + 'origin_template_id': originTemplateId, + 'private_pub': privatePub, + 'content_pic_list': contentPicList, + 'content': content, + 'keywords': keywords, + 'version_id': versionId, + 'dyn_id_str': dynIdStr, + 'total_art_num': totalArtNum, + }; +} diff --git a/lib/models/dynamics/article_view/label.dart b/lib/models/dynamics/article_view/label.dart new file mode 100644 index 000000000..6387da61c --- /dev/null +++ b/lib/models/dynamics/article_view/label.dart @@ -0,0 +1,19 @@ +class Label { + String? path; + String? text; + String? labelTheme; + + Label({this.path, this.text, this.labelTheme}); + + factory Label.fromJson(Map json) => Label( + path: json['path'] as String?, + text: json['text'] as String?, + labelTheme: json['label_theme'] as String?, + ); + + Map toJson() => { + 'path': path, + 'text': text, + 'label_theme': labelTheme, + }; +} diff --git a/lib/models/dynamics/article_view/media.dart b/lib/models/dynamics/article_view/media.dart new file mode 100644 index 000000000..4080f495b --- /dev/null +++ b/lib/models/dynamics/article_view/media.dart @@ -0,0 +1,47 @@ +class Media { + int? score; + int? mediaId; + String? title; + String? cover; + String? area; + int? typeId; + String? typeName; + int? spoiler; + int? seasonId; + + Media({ + this.score, + this.mediaId, + this.title, + this.cover, + this.area, + this.typeId, + this.typeName, + this.spoiler, + this.seasonId, + }); + + factory Media.fromJson(Map json) => Media( + score: json['score'] as int?, + mediaId: json['media_id'] as int?, + title: json['title'] as String?, + cover: json['cover'] as String?, + area: json['area'] as String?, + typeId: json['type_id'] as int?, + typeName: json['type_name'] as String?, + spoiler: json['spoiler'] as int?, + seasonId: json['season_id'] as int?, + ); + + Map toJson() => { + 'score': score, + 'media_id': mediaId, + 'title': title, + 'cover': cover, + 'area': area, + 'type_id': typeId, + 'type_name': typeName, + 'spoiler': spoiler, + 'season_id': seasonId, + }; +} diff --git a/lib/models/dynamics/article_view/nameplate.dart b/lib/models/dynamics/article_view/nameplate.dart new file mode 100644 index 000000000..d6f9191dc --- /dev/null +++ b/lib/models/dynamics/article_view/nameplate.dart @@ -0,0 +1,35 @@ +class Nameplate { + int? nid; + String? name; + String? image; + String? imageSmall; + String? level; + String? condition; + + Nameplate({ + this.nid, + this.name, + this.image, + this.imageSmall, + this.level, + this.condition, + }); + + factory Nameplate.fromJson(Map json) => Nameplate( + nid: json['nid'] as int?, + name: json['name'] as String?, + image: json['image'] as String?, + imageSmall: json['image_small'] as String?, + level: json['level'] as String?, + condition: json['condition'] as String?, + ); + + Map toJson() => { + 'nid': nid, + 'name': name, + 'image': image, + 'image_small': imageSmall, + 'level': level, + 'condition': condition, + }; +} diff --git a/lib/models/dynamics/article_view/official_verify.dart b/lib/models/dynamics/article_view/official_verify.dart new file mode 100644 index 000000000..ed4c6f4d8 --- /dev/null +++ b/lib/models/dynamics/article_view/official_verify.dart @@ -0,0 +1,20 @@ +class OfficialVerify { + int? type; + String? desc; + + OfficialVerify({this.type, this.desc}); + + factory OfficialVerify.fromJson(Map json) { + return OfficialVerify( + type: json['type'] as int?, + desc: json['desc'] as String?, + ); + } + + + + Map toJson() => { + 'type': type, + 'desc': desc, + }; +} diff --git a/lib/models/dynamics/article_view/pendant.dart b/lib/models/dynamics/article_view/pendant.dart new file mode 100644 index 000000000..acb2e705a --- /dev/null +++ b/lib/models/dynamics/article_view/pendant.dart @@ -0,0 +1,22 @@ +class Pendant { + int? pid; + String? name; + String? image; + int? expire; + + Pendant({this.pid, this.name, this.image, this.expire}); + + factory Pendant.fromJson(Map json) => Pendant( + pid: json['pid'] as int?, + name: json['name'] as String?, + image: json['image'] as String?, + expire: json['expire'] as int?, + ); + + Map toJson() => { + 'pid': pid, + 'name': name, + 'image': image, + 'expire': expire, + }; +} diff --git a/lib/models/dynamics/article_view/stats.dart b/lib/models/dynamics/article_view/stats.dart new file mode 100644 index 000000000..ba12f9dcd --- /dev/null +++ b/lib/models/dynamics/article_view/stats.dart @@ -0,0 +1,43 @@ +class Stats { + int? view; + int? favorite; + int? like; + int? dislike; + int? reply; + int? share; + int? coin; + int? dynam1c; + + Stats({ + this.view, + this.favorite, + this.like, + this.dislike, + this.reply, + this.share, + this.coin, + this.dynam1c, + }); + + factory Stats.fromJson(Map json) => Stats( + view: json['view'] as int?, + favorite: json['favorite'] as int?, + like: json['like'] as int?, + dislike: json['dislike'] as int?, + reply: json['reply'] as int?, + share: json['share'] as int?, + coin: json['coin'] as int?, + dynam1c: json['dynamic'] as int?, + ); + + Map toJson() => { + 'view': view, + 'favorite': favorite, + 'like': like, + 'dislike': dislike, + 'reply': reply, + 'share': share, + 'coin': coin, + 'dynamic': dynam1c, + }; +} diff --git a/lib/models/dynamics/article_view/tag.dart b/lib/models/dynamics/article_view/tag.dart new file mode 100644 index 000000000..c11893af9 --- /dev/null +++ b/lib/models/dynamics/article_view/tag.dart @@ -0,0 +1,16 @@ +class Tag { + int? tid; + String? name; + + Tag({this.tid, this.name}); + + factory Tag.fromJson(Map json) => Tag( + tid: json['tid'] as int?, + name: json['name'] as String?, + ); + + Map toJson() => { + 'tid': tid, + 'name': name, + }; +} diff --git a/lib/models/dynamics/article_view/vip.dart b/lib/models/dynamics/article_view/vip.dart new file mode 100644 index 000000000..f2c4559e7 --- /dev/null +++ b/lib/models/dynamics/article_view/vip.dart @@ -0,0 +1,47 @@ +import 'label.dart'; + +class Vip { + int? type; + int? status; + int? dueDate; + int? vipPayType; + int? themeType; + Label? label; + int? avatarSubscript; + String? nicknameColor; + + Vip({ + this.type, + this.status, + this.dueDate, + this.vipPayType, + this.themeType, + this.label, + this.avatarSubscript, + this.nicknameColor, + }); + + factory Vip.fromJson(Map json) => Vip( + type: json['type'] as int?, + status: json['status'] as int?, + dueDate: json['due_date'] as int?, + vipPayType: json['vip_pay_type'] as int?, + themeType: json['theme_type'] as int?, + label: json['label'] == null + ? null + : Label.fromJson(json['label'] as Map), + avatarSubscript: json['avatar_subscript'] as int?, + nicknameColor: json['nickname_color'] as String?, + ); + + Map toJson() => { + 'type': type, + 'status': status, + 'due_date': dueDate, + 'vip_pay_type': vipPayType, + 'theme_type': themeType, + 'label': label?.toJson(), + 'avatar_subscript': avatarSubscript, + 'nickname_color': nicknameColor, + }; +} diff --git a/lib/models/dynamics/opus_detail/avatar.dart b/lib/models/dynamics/opus_detail/avatar.dart new file mode 100644 index 000000000..6ee6f30fa --- /dev/null +++ b/lib/models/dynamics/opus_detail/avatar.dart @@ -0,0 +1,26 @@ +import 'container_size.dart'; +import 'fallback_layers.dart'; + +class Avatar { + ContainerSize? containerSize; + FallbackLayers? fallbackLayers; + String? mid; + + Avatar({this.containerSize, this.fallbackLayers, this.mid}); + + factory Avatar.fromJson(Map json) => Avatar( + containerSize: json['container_size'] == null + ? null + : ContainerSize.fromJson(json['container_size'] as Map), + fallbackLayers: json['fallback_layers'] == null + ? null + : FallbackLayers.fromJson(json['fallback_layers'] as Map), + mid: json['mid'] as String?, + ); + + Map toJson() => { + 'container_size': containerSize?.toJson(), + 'fallback_layers': fallbackLayers?.toJson(), + 'mid': mid, + }; +} diff --git a/lib/models/dynamics/opus_detail/avatar_icon.dart b/lib/models/dynamics/opus_detail/avatar_icon.dart new file mode 100644 index 000000000..559f11e30 --- /dev/null +++ b/lib/models/dynamics/opus_detail/avatar_icon.dart @@ -0,0 +1,17 @@ +import 'icon_resource.dart'; + +class AvatarIcon { + IconResource? iconResource; + + AvatarIcon({this.iconResource}); + + factory AvatarIcon.fromJson(Map json) => AvatarIcon( + iconResource: json['icon_resource'] == null + ? null + : IconResource.fromJson(json['icon_resource'] as Map), + ); + + Map toJson() => { + 'icon_resource': iconResource?.toJson(), + }; +} diff --git a/lib/models/dynamics/opus_detail/avatar_layer.dart b/lib/models/dynamics/opus_detail/avatar_layer.dart new file mode 100644 index 000000000..b2af911f1 --- /dev/null +++ b/lib/models/dynamics/opus_detail/avatar_layer.dart @@ -0,0 +1,14 @@ + +class AvatarLayer { + AvatarLayer(); + + factory AvatarLayer.fromJson(Map json) { + // TODO: implement fromJson + throw UnimplementedError('AvatarLayer.fromJson($json) is not implemented'); + } + + Map toJson() { + // TODO: implement toJson + throw UnimplementedError(); + } +} \ No newline at end of file diff --git a/lib/models/dynamics/opus_detail/basic.dart b/lib/models/dynamics/opus_detail/basic.dart new file mode 100644 index 000000000..75f3e23b2 --- /dev/null +++ b/lib/models/dynamics/opus_detail/basic.dart @@ -0,0 +1,39 @@ +import 'like_icon.dart'; + +class Basic { + String? commentIdStr; + int? commentType; + LikeIcon? likeIcon; + String? ridStr; + String? title; + int? uid; + + Basic({ + this.commentIdStr, + this.commentType, + this.likeIcon, + this.ridStr, + this.title, + this.uid, + }); + + factory Basic.fromJson(Map json) => Basic( + commentIdStr: json['comment_id_str'] as String?, + commentType: json['comment_type'] as int?, + likeIcon: json['like_icon'] == null + ? null + : LikeIcon.fromJson(json['like_icon'] as Map), + ridStr: json['rid_str'] as String?, + title: json['title'] as String?, + uid: json['uid'] as int?, + ); + + Map toJson() => { + 'comment_id_str': commentIdStr, + 'comment_type': commentType, + 'like_icon': likeIcon?.toJson(), + 'rid_str': ridStr, + 'title': title, + 'uid': uid, + }; +} diff --git a/lib/models/dynamics/opus_detail/coin.dart b/lib/models/dynamics/opus_detail/coin.dart new file mode 100644 index 000000000..3bb246345 --- /dev/null +++ b/lib/models/dynamics/opus_detail/coin.dart @@ -0,0 +1,19 @@ +class Coin { + int? count; + bool? forbidden; + bool? status; + + Coin({this.count, this.forbidden, this.status}); + + factory Coin.fromJson(Map json) => Coin( + count: json['count'] as int?, + forbidden: json['forbidden'] as bool?, + status: json['status'] as bool?, + ); + + Map toJson() => { + 'count': count, + 'forbidden': forbidden, + 'status': status, + }; +} diff --git a/lib/models/dynamics/opus_detail/comment.dart b/lib/models/dynamics/opus_detail/comment.dart new file mode 100644 index 000000000..811158a2e --- /dev/null +++ b/lib/models/dynamics/opus_detail/comment.dart @@ -0,0 +1,16 @@ +class Comment { + int? count; + bool? forbidden; + + Comment({this.count, this.forbidden}); + + factory Comment.fromJson(Map json) => Comment( + count: json['count'] as int?, + forbidden: json['forbidden'] as bool?, + ); + + Map toJson() => { + 'count': count, + 'forbidden': forbidden, + }; +} diff --git a/lib/models/dynamics/opus_detail/container_size.dart b/lib/models/dynamics/opus_detail/container_size.dart new file mode 100644 index 000000000..ea8185d04 --- /dev/null +++ b/lib/models/dynamics/opus_detail/container_size.dart @@ -0,0 +1,16 @@ +class ContainerSize { + double? height; + double? width; + + ContainerSize({this.height, this.width}); + + factory ContainerSize.fromJson(Map json) => ContainerSize( + height: (json['height'] as num?)?.toDouble(), + width: (json['width'] as num?)?.toDouble(), + ); + + Map toJson() => { + 'height': height, + 'width': width, + }; +} diff --git a/lib/models/dynamics/opus_detail/data.dart b/lib/models/dynamics/opus_detail/data.dart new file mode 100644 index 000000000..a05db401c --- /dev/null +++ b/lib/models/dynamics/opus_detail/data.dart @@ -0,0 +1,26 @@ +import 'fallback.dart'; +import 'item.dart'; + +class OpusData { + Item? item; + Fallback? fallback; + + OpusData({ + this.item, + this.fallback, + }); + + factory OpusData.fromJson(Map json) => OpusData( + item: json['item'] == null + ? null + : Item.fromJson(json['item'] as Map), + fallback: json['fallback'] == null + ? null + : Fallback.fromJson(json['fallback']), + ); + + Map toJson() => { + 'item': item?.toJson(), + 'fallback': fallback?.toJson(), + }; +} diff --git a/lib/models/dynamics/opus_detail/fallback.dart b/lib/models/dynamics/opus_detail/fallback.dart new file mode 100644 index 000000000..8f5bfd5de --- /dev/null +++ b/lib/models/dynamics/opus_detail/fallback.dart @@ -0,0 +1,19 @@ +class Fallback { + String? id; + int? type; + + Fallback({ + this.id, + this.type, + }); + + factory Fallback.fromJson(Map json) => Fallback( + id: json['id'], + type: json['type'], + ); + + Map toJson() => { + 'id': id, + 'type': type, + }; +} diff --git a/lib/models/dynamics/opus_detail/fallback_layers.dart b/lib/models/dynamics/opus_detail/fallback_layers.dart new file mode 100644 index 000000000..aba634f77 --- /dev/null +++ b/lib/models/dynamics/opus_detail/fallback_layers.dart @@ -0,0 +1,24 @@ +import 'layer.dart'; + +class FallbackLayers { + bool? isCriticalGroup; + List? layers; + + FallbackLayers({this.isCriticalGroup, this.layers}); + + factory FallbackLayers.fromJson(Map json) { + return FallbackLayers( + isCriticalGroup: json['is_critical_group'] as bool?, + layers: (json['layers'] as List?) + ?.map((e) => Layer.fromJson(e as Map)) + .toList(), + ); + } + + + + Map toJson() => { + 'is_critical_group': isCriticalGroup, + 'layers': layers?.map((e) => e.toJson()).toList(), + }; +} diff --git a/lib/models/dynamics/opus_detail/favorite.dart b/lib/models/dynamics/opus_detail/favorite.dart new file mode 100644 index 000000000..b6f64e663 --- /dev/null +++ b/lib/models/dynamics/opus_detail/favorite.dart @@ -0,0 +1,19 @@ +class Favorite { + int? count; + bool? forbidden; + bool? status; + + Favorite({this.count, this.forbidden, this.status}); + + factory Favorite.fromJson(Map json) => Favorite( + count: json['count'] as int?, + forbidden: json['forbidden'] as bool?, + status: json['status'] as bool?, + ); + + Map toJson() => { + 'count': count, + 'forbidden': forbidden, + 'status': status, + }; +} diff --git a/lib/models/dynamics/opus_detail/forward.dart b/lib/models/dynamics/opus_detail/forward.dart new file mode 100644 index 000000000..62cbf1bd3 --- /dev/null +++ b/lib/models/dynamics/opus_detail/forward.dart @@ -0,0 +1,16 @@ +class Forward { + int? count; + bool? forbidden; + + Forward({this.count, this.forbidden}); + + factory Forward.fromJson(Map json) => Forward( + count: json['count'] as int?, + forbidden: json['forbidden'] as bool?, + ); + + Map toJson() => { + 'count': count, + 'forbidden': forbidden, + }; +} diff --git a/lib/models/dynamics/opus_detail/general_cfg.dart b/lib/models/dynamics/opus_detail/general_cfg.dart new file mode 100644 index 000000000..ebd2334c8 --- /dev/null +++ b/lib/models/dynamics/opus_detail/general_cfg.dart @@ -0,0 +1,20 @@ +import 'general_config.dart'; + +class GeneralCfg { + int? configType; + GeneralConfig? generalConfig; + + GeneralCfg({this.configType, this.generalConfig}); + + factory GeneralCfg.fromJson(Map json) => GeneralCfg( + configType: json['config_type'] as int?, + generalConfig: json['general_config'] == null + ? null + : GeneralConfig.fromJson(json['general_config'] as Map), + ); + + Map toJson() => { + 'config_type': configType, + 'general_config': generalConfig?.toJson(), + }; +} diff --git a/lib/models/dynamics/opus_detail/general_config.dart b/lib/models/dynamics/opus_detail/general_config.dart new file mode 100644 index 000000000..5730e93a9 --- /dev/null +++ b/lib/models/dynamics/opus_detail/general_config.dart @@ -0,0 +1,17 @@ +import 'web_css_style.dart'; + +class GeneralConfig { + WebCssStyle? webCssStyle; + + GeneralConfig({this.webCssStyle}); + + factory GeneralConfig.fromJson(Map json) => GeneralConfig( + webCssStyle: json['web_css_style'] == null + ? null + : WebCssStyle.fromJson(json['web_css_style'] as Map), + ); + + Map toJson() => { + 'web_css_style': webCssStyle?.toJson(), + }; +} diff --git a/lib/models/dynamics/opus_detail/general_spec.dart b/lib/models/dynamics/opus_detail/general_spec.dart new file mode 100644 index 000000000..f6381068b --- /dev/null +++ b/lib/models/dynamics/opus_detail/general_spec.dart @@ -0,0 +1,29 @@ +import 'pos_spec.dart'; +import 'render_spec.dart'; +import 'size_spec.dart'; + +class GeneralSpec { + PosSpec? posSpec; + RenderSpec? renderSpec; + SizeSpec? sizeSpec; + + GeneralSpec({this.posSpec, this.renderSpec, this.sizeSpec}); + + factory GeneralSpec.fromJson(Map json) => GeneralSpec( + posSpec: json['pos_spec'] == null + ? null + : PosSpec.fromJson(json['pos_spec'] as Map), + renderSpec: json['render_spec'] == null + ? null + : RenderSpec.fromJson(json['render_spec'] as Map), + sizeSpec: json['size_spec'] == null + ? null + : SizeSpec.fromJson(json['size_spec'] as Map), + ); + + Map toJson() => { + 'pos_spec': posSpec?.toJson(), + 'render_spec': renderSpec?.toJson(), + 'size_spec': sizeSpec?.toJson(), + }; +} diff --git a/lib/models/dynamics/opus_detail/icon_resource.dart b/lib/models/dynamics/opus_detail/icon_resource.dart new file mode 100644 index 000000000..07fea5cd8 --- /dev/null +++ b/lib/models/dynamics/opus_detail/icon_resource.dart @@ -0,0 +1,14 @@ + +class IconResource { + IconResource(); + + factory IconResource.fromJson(Map json) { + // TODO: implement fromJson + throw UnimplementedError('IconResource.fromJson($json) is not implemented'); + } + + Map toJson() { + // TODO: implement toJson + throw UnimplementedError(); + } +} \ No newline at end of file diff --git a/lib/models/dynamics/opus_detail/image_src.dart b/lib/models/dynamics/opus_detail/image_src.dart new file mode 100644 index 000000000..1b396594f --- /dev/null +++ b/lib/models/dynamics/opus_detail/image_src.dart @@ -0,0 +1,23 @@ +import 'remote.dart'; + +class ImageSrc { + int? placeholder; + Remote? remote; + int? srcType; + + ImageSrc({this.placeholder, this.remote, this.srcType}); + + factory ImageSrc.fromJson(Map json) => ImageSrc( + placeholder: json['placeholder'] as int?, + remote: json['remote'] == null + ? null + : Remote.fromJson(json['remote'] as Map), + srcType: json['src_type'] as int?, + ); + + Map toJson() => { + 'placeholder': placeholder, + 'remote': remote?.toJson(), + 'src_type': srcType, + }; +} diff --git a/lib/models/dynamics/opus_detail/item.dart b/lib/models/dynamics/opus_detail/item.dart new file mode 100644 index 000000000..0b67e99c4 --- /dev/null +++ b/lib/models/dynamics/opus_detail/item.dart @@ -0,0 +1,29 @@ +import 'basic.dart'; +import 'module.dart'; + +class Item { + Basic? basic; + String? idStr; + List? modules; + int? type; + + Item({this.basic, this.idStr, this.modules, this.type}); + + factory Item.fromJson(Map json) => Item( + basic: json['basic'] == null + ? null + : Basic.fromJson(json['basic'] as Map), + idStr: json['id_str'] as String?, + modules: (json['modules'] as List?) + ?.map((e) => OpusModule.fromJson(e as Map)) + .toList(), + type: json['type'] as int?, + ); + + Map toJson() => { + 'basic': basic?.toJson(), + 'id_str': idStr, + 'modules': modules?.map((e) => e.toJson()).toList(), + 'type': type, + }; +} diff --git a/lib/models/dynamics/opus_detail/label.dart b/lib/models/dynamics/opus_detail/label.dart new file mode 100644 index 000000000..093c0663d --- /dev/null +++ b/lib/models/dynamics/opus_detail/label.dart @@ -0,0 +1,59 @@ +class Label { + String? bgColor; + int? bgStyle; + String? borderColor; + String? imgLabelUriHans; + String? imgLabelUriHansStatic; + String? imgLabelUriHant; + String? imgLabelUriHantStatic; + String? labelTheme; + String? path; + String? text; + String? textColor; + bool? useImgLabel; + + Label({ + this.bgColor, + this.bgStyle, + this.borderColor, + this.imgLabelUriHans, + this.imgLabelUriHansStatic, + this.imgLabelUriHant, + this.imgLabelUriHantStatic, + this.labelTheme, + this.path, + this.text, + this.textColor, + this.useImgLabel, + }); + + factory Label.fromJson(Map json) => Label( + bgColor: json['bg_color'] as String?, + bgStyle: json['bg_style'] as int?, + borderColor: json['border_color'] as String?, + imgLabelUriHans: json['img_label_uri_hans'] as String?, + imgLabelUriHansStatic: json['img_label_uri_hans_static'] as String?, + imgLabelUriHant: json['img_label_uri_hant'] as String?, + imgLabelUriHantStatic: json['img_label_uri_hant_static'] as String?, + labelTheme: json['label_theme'] as String?, + path: json['path'] as String?, + text: json['text'] as String?, + textColor: json['text_color'] as String?, + useImgLabel: json['use_img_label'] as bool?, + ); + + Map toJson() => { + 'bg_color': bgColor, + 'bg_style': bgStyle, + 'border_color': borderColor, + 'img_label_uri_hans': imgLabelUriHans, + 'img_label_uri_hans_static': imgLabelUriHansStatic, + 'img_label_uri_hant': imgLabelUriHant, + 'img_label_uri_hant_static': imgLabelUriHantStatic, + 'label_theme': labelTheme, + 'path': path, + 'text': text, + 'text_color': textColor, + 'use_img_label': useImgLabel, + }; +} diff --git a/lib/models/dynamics/opus_detail/layer.dart b/lib/models/dynamics/opus_detail/layer.dart new file mode 100644 index 000000000..33854bc51 --- /dev/null +++ b/lib/models/dynamics/opus_detail/layer.dart @@ -0,0 +1,32 @@ +import 'general_spec.dart'; +import 'layer_config.dart'; +import 'resource.dart'; + +class Layer { + GeneralSpec? generalSpec; + LayerConfig? layerConfig; + Resource? resource; + bool? visible; + + Layer({this.generalSpec, this.layerConfig, this.resource, this.visible}); + + factory Layer.fromJson(Map json) => Layer( + generalSpec: json['general_spec'] == null + ? null + : GeneralSpec.fromJson(json['general_spec'] as Map), + layerConfig: json['layer_config'] == null + ? null + : LayerConfig.fromJson(json['layer_config'] as Map), + resource: json['resource'] == null + ? null + : Resource.fromJson(json['resource'] as Map), + visible: json['visible'] as bool?, + ); + + Map toJson() => { + 'general_spec': generalSpec?.toJson(), + 'layer_config': layerConfig?.toJson(), + 'resource': resource?.toJson(), + 'visible': visible, + }; +} diff --git a/lib/models/dynamics/opus_detail/layer_config.dart b/lib/models/dynamics/opus_detail/layer_config.dart new file mode 100644 index 000000000..711079c66 --- /dev/null +++ b/lib/models/dynamics/opus_detail/layer_config.dart @@ -0,0 +1,20 @@ +import 'tags.dart'; + +class LayerConfig { + bool? isCritical; + Tags? tags; + + LayerConfig({this.isCritical, this.tags}); + + factory LayerConfig.fromJson(Map json) => LayerConfig( + isCritical: json['is_critical'] as bool?, + tags: json['tags'] == null + ? null + : Tags.fromJson(json['tags'] as Map), + ); + + Map toJson() => { + 'is_critical': isCritical, + 'tags': tags?.toJson(), + }; +} diff --git a/lib/models/dynamics/opus_detail/like.dart b/lib/models/dynamics/opus_detail/like.dart new file mode 100644 index 000000000..c705cd532 --- /dev/null +++ b/lib/models/dynamics/opus_detail/like.dart @@ -0,0 +1,19 @@ +class Like { + int? count; + bool? forbidden; + bool? status; + + Like({this.count, this.forbidden, this.status}); + + factory Like.fromJson(Map json) => Like( + count: json['count'] as int?, + forbidden: json['forbidden'] as bool?, + status: json['status'] as bool?, + ); + + Map toJson() => { + 'count': count, + 'forbidden': forbidden, + 'status': status, + }; +} diff --git a/lib/models/dynamics/opus_detail/like_icon.dart b/lib/models/dynamics/opus_detail/like_icon.dart new file mode 100644 index 000000000..8e22daac2 --- /dev/null +++ b/lib/models/dynamics/opus_detail/like_icon.dart @@ -0,0 +1,22 @@ +class LikeIcon { + String? actionUrl; + String? endUrl; + int? id; + String? startUrl; + + LikeIcon({this.actionUrl, this.endUrl, this.id, this.startUrl}); + + factory LikeIcon.fromJson(Map json) => LikeIcon( + actionUrl: json['action_url'] as String?, + endUrl: json['end_url'] as String?, + id: json['id'] as int?, + startUrl: json['start_url'] as String?, + ); + + Map toJson() => { + 'action_url': actionUrl, + 'end_url': endUrl, + 'id': id, + 'start_url': startUrl, + }; +} diff --git a/lib/models/dynamics/opus_detail/module.dart b/lib/models/dynamics/opus_detail/module.dart new file mode 100644 index 000000000..f83115308 --- /dev/null +++ b/lib/models/dynamics/opus_detail/module.dart @@ -0,0 +1,63 @@ +import 'module_author.dart'; +import 'module_bottom.dart'; +import 'module_content.dart'; +import 'module_extend.dart'; +import 'module_stat.dart'; +import 'module_title.dart'; + +class OpusModule { + ModuleTitle? moduleTitle; + String? moduleType; + ModuleAuthor? moduleAuthor; + ModuleContent? moduleContent; + ModuleExtend? moduleExtend; + ModuleBottom? moduleBottom; + ModuleStat? moduleStat; + + OpusModule({ + this.moduleTitle, + this.moduleType, + this.moduleAuthor, + this.moduleContent, + this.moduleExtend, + this.moduleBottom, + this.moduleStat, + }); + + factory OpusModule.fromJson(Map json) => OpusModule( + moduleTitle: json['module_title'] == null + ? null + : ModuleTitle.fromJson( + json['module_title'] as Map), + moduleType: json['module_type'] as String?, + moduleAuthor: json['module_author'] == null + ? null + : ModuleAuthor.fromJson( + json['module_author'] as Map), + moduleContent: json['module_content'] == null + ? null + : ModuleContent.fromJson( + json['module_content'] as Map), + moduleExtend: json['module_extend'] == null + ? null + : ModuleExtend.fromJson( + json['module_extend'] as Map), + moduleBottom: json['module_bottom'] == null + ? null + : ModuleBottom.fromJson( + json['module_bottom'] as Map), + moduleStat: json['module_stat'] == null + ? null + : ModuleStat.fromJson(json['module_stat'] as Map), + ); + + Map toJson() => { + 'module_title': moduleTitle?.toJson(), + 'module_type': moduleType, + 'module_author': moduleAuthor?.toJson(), + 'module_content': moduleContent?.toJson(), + 'module_extend': moduleExtend?.toJson(), + 'module_bottom': moduleBottom?.toJson(), + 'module_stat': moduleStat?.toJson(), + }; +} diff --git a/lib/models/dynamics/opus_detail/module_author.dart b/lib/models/dynamics/opus_detail/module_author.dart new file mode 100644 index 000000000..6a0b4fd92 --- /dev/null +++ b/lib/models/dynamics/opus_detail/module_author.dart @@ -0,0 +1,84 @@ +import 'avatar.dart'; +import 'official.dart'; +import 'pendant.dart'; +import 'vip.dart'; + +class ModuleAuthor { + Avatar? avatar; + String? face; + bool? faceNft; + dynamic following; + String? jumpUrl; + String? label; + int? mid; + String? name; + Official? official; + Pendant? pendant; + String? pubLocationText; + String? pubTime; + int? pubTs; + String? viewsText; + Vip? vip; + + ModuleAuthor({ + this.avatar, + this.face, + this.faceNft, + this.following, + this.jumpUrl, + this.label, + this.mid, + this.name, + this.official, + this.pendant, + this.pubLocationText, + this.pubTime, + this.pubTs, + this.viewsText, + this.vip, + }); + + factory ModuleAuthor.fromJson(Map json) => ModuleAuthor( + avatar: json['avatar'] == null + ? null + : Avatar.fromJson(json['avatar'] as Map), + face: json['face'] as String?, + faceNft: json['face_nft'] as bool?, + following: json['following'] as dynamic, + jumpUrl: json['jump_url'] as String?, + label: json['label'] as String?, + mid: json['mid'] as int?, + name: json['name'] as String?, + official: json['official'] == null + ? null + : Official.fromJson(json['official'] as Map), + pendant: json['pendant'] == null + ? null + : Pendant.fromJson(json['pendant'] as Map), + pubLocationText: json['pub_location_text'] as String?, + pubTime: json['pub_time'] as String?, + pubTs: json['pub_ts'] as int?, + viewsText: json['views_text'] as String?, + vip: json['vip'] == null + ? null + : Vip.fromJson(json['vip'] as Map), + ); + + Map toJson() => { + 'avatar': avatar?.toJson(), + 'face': face, + 'face_nft': faceNft, + 'following': following, + 'jump_url': jumpUrl, + 'label': label, + 'mid': mid, + 'name': name, + 'official': official?.toJson(), + 'pendant': pendant?.toJson(), + 'pub_location_text': pubLocationText, + 'pub_time': pubTime, + 'pub_ts': pubTs, + 'views_text': viewsText, + 'vip': vip?.toJson(), + }; +} diff --git a/lib/models/dynamics/opus_detail/module_bottom.dart b/lib/models/dynamics/opus_detail/module_bottom.dart new file mode 100644 index 000000000..e7e410c84 --- /dev/null +++ b/lib/models/dynamics/opus_detail/module_bottom.dart @@ -0,0 +1,17 @@ +import 'share_info.dart'; + +class ModuleBottom { + ShareInfo? shareInfo; + + ModuleBottom({this.shareInfo}); + + factory ModuleBottom.fromJson(Map json) => ModuleBottom( + shareInfo: json['share_info'] == null + ? null + : ShareInfo.fromJson(json['share_info'] as Map), + ); + + Map toJson() => { + 'share_info': shareInfo?.toJson(), + }; +} diff --git a/lib/models/dynamics/opus_detail/module_content.dart b/lib/models/dynamics/opus_detail/module_content.dart new file mode 100644 index 000000000..6c90fa099 --- /dev/null +++ b/lib/models/dynamics/opus_detail/module_content.dart @@ -0,0 +1,17 @@ +import 'paragraph.dart'; + +class ModuleContent { + List? paragraphs; + + ModuleContent({this.paragraphs}); + + factory ModuleContent.fromJson(Map json) => ModuleContent( + paragraphs: (json['paragraphs'] as List?) + ?.map((e) => Paragraph.fromJson(e as Map)) + .toList(), + ); + + Map toJson() => { + 'paragraphs': paragraphs?.map((e) => e.toJson()).toList(), + }; +} diff --git a/lib/models/dynamics/opus_detail/module_extend.dart b/lib/models/dynamics/opus_detail/module_extend.dart new file mode 100644 index 000000000..85f9befda --- /dev/null +++ b/lib/models/dynamics/opus_detail/module_extend.dart @@ -0,0 +1,17 @@ +import 'item.dart'; + +class ModuleExtend { + List? items; + + ModuleExtend({this.items}); + + factory ModuleExtend.fromJson(Map json) => ModuleExtend( + items: (json['items'] as List?) + ?.map((e) => Item.fromJson(e as Map)) + .toList(), + ); + + Map toJson() => { + 'items': items?.map((e) => e.toJson()).toList(), + }; +} diff --git a/lib/models/dynamics/opus_detail/module_stat.dart b/lib/models/dynamics/opus_detail/module_stat.dart new file mode 100644 index 000000000..4dce5fb12 --- /dev/null +++ b/lib/models/dynamics/opus_detail/module_stat.dart @@ -0,0 +1,47 @@ +import 'coin.dart'; +import 'comment.dart'; +import 'favorite.dart'; +import 'forward.dart'; +import 'like.dart'; + +class ModuleStat { + Coin? coin; + Comment? comment; + Favorite? favorite; + Forward? forward; + Like? like; + + ModuleStat({ + this.coin, + this.comment, + this.favorite, + this.forward, + this.like, + }); + + factory ModuleStat.fromJson(Map json) => ModuleStat( + coin: json['coin'] == null + ? null + : Coin.fromJson(json['coin'] as Map), + comment: json['comment'] == null + ? null + : Comment.fromJson(json['comment'] as Map), + favorite: json['favorite'] == null + ? null + : Favorite.fromJson(json['favorite'] as Map), + forward: json['forward'] == null + ? null + : Forward.fromJson(json['forward'] as Map), + like: json['like'] == null + ? null + : Like.fromJson(json['like'] as Map), + ); + + Map toJson() => { + 'coin': coin?.toJson(), + 'comment': comment?.toJson(), + 'favorite': favorite?.toJson(), + 'forward': forward?.toJson(), + 'like': like?.toJson(), + }; +} diff --git a/lib/models/dynamics/opus_detail/module_title.dart b/lib/models/dynamics/opus_detail/module_title.dart new file mode 100644 index 000000000..2cf1ee3c6 --- /dev/null +++ b/lib/models/dynamics/opus_detail/module_title.dart @@ -0,0 +1,13 @@ +class ModuleTitle { + String? text; + + ModuleTitle({this.text}); + + factory ModuleTitle.fromJson(Map json) => ModuleTitle( + text: json['text'] as String?, + ); + + Map toJson() => { + 'text': text, + }; +} diff --git a/lib/models/dynamics/opus_detail/node.dart b/lib/models/dynamics/opus_detail/node.dart new file mode 100644 index 000000000..8f4169d49 --- /dev/null +++ b/lib/models/dynamics/opus_detail/node.dart @@ -0,0 +1,30 @@ +import 'rich.dart'; +import 'word.dart'; + +class Node { + String? type; + Word? word; + Rich? rich; + + Node({ + this.type, + this.word, + this.rich, + }); + + factory Node.fromJson(Map json) => Node( + type: json['type'] as String?, + word: json['word'] == null + ? null + : Word.fromJson(json['word'] as Map), + rich: json['rich'] == null + ? null + : Rich.fromJson(json['rich'] as Map), + ); + + Map toJson() => { + 'type': type, + 'word': word?.toJson(), + 'rich': rich?.toJson(), + }; +} diff --git a/lib/models/dynamics/opus_detail/official.dart b/lib/models/dynamics/opus_detail/official.dart new file mode 100644 index 000000000..7667b60d0 --- /dev/null +++ b/lib/models/dynamics/opus_detail/official.dart @@ -0,0 +1,22 @@ +class Official { + String? desc; + int? role; + String? title; + int? type; + + Official({this.desc, this.role, this.title, this.type}); + + factory Official.fromJson(Map json) => Official( + desc: json['desc'] as String?, + role: json['role'] as int?, + title: json['title'] as String?, + type: json['type'] as int?, + ); + + Map toJson() => { + 'desc': desc, + 'role': role, + 'title': title, + 'type': type, + }; +} diff --git a/lib/models/dynamics/opus_detail/opus_detail.dart b/lib/models/dynamics/opus_detail/opus_detail.dart new file mode 100644 index 000000000..b7c1a0d48 --- /dev/null +++ b/lib/models/dynamics/opus_detail/opus_detail.dart @@ -0,0 +1,26 @@ +import 'data.dart'; + +class OpusDetail { + int? code; + String? message; + int? ttl; + OpusData? data; + + OpusDetail({this.code, this.message, this.ttl, this.data}); + + factory OpusDetail.fromJson(Map json) => OpusDetail( + code: json['code'] as int?, + message: json['message'] as String?, + ttl: json['ttl'] as int?, + data: json['data'] == null + ? null + : OpusData.fromJson(json['data'] as Map), + ); + + Map toJson() => { + 'code': code, + 'message': message, + 'ttl': ttl, + 'data': data?.toJson(), + }; +} diff --git a/lib/models/dynamics/opus_detail/paragraph.dart b/lib/models/dynamics/opus_detail/paragraph.dart new file mode 100644 index 000000000..86a04b718 --- /dev/null +++ b/lib/models/dynamics/opus_detail/paragraph.dart @@ -0,0 +1,42 @@ +import 'pic.dart'; +import 'text.dart'; + +class Paragraph { + int? align; + int? paraType; + ParagraphText? text; + Pic? pic; + Line? line; + + Paragraph({this.align, this.paraType, this.text, this.pic, this.line}); + + factory Paragraph.fromJson(Map json) => Paragraph( + align: json['align'] as int?, + paraType: json['para_type'] as int?, + text: json['text'] == null + ? null + : ParagraphText.fromJson(json['text'] as Map), + pic: json['pic'] == null + ? null + : Pic.fromJson(json['pic'] as Map), + line: json['line'] == null ? null : Line.fromJson(json['line']), + ); + + Map toJson() => { + 'align': align, + 'para_type': paraType, + 'text': text?.toJson(), + 'pic': pic?.toJson(), + }; +} + +class Line { + Line({ + this.pic, + }); + Pic? pic; + + Line.fromJson(Map json) { + pic = json['pic'] == null ? null : Pic.fromJson(json['pic']); + } +} diff --git a/lib/models/dynamics/opus_detail/pendant.dart b/lib/models/dynamics/opus_detail/pendant.dart new file mode 100644 index 000000000..6b53177c8 --- /dev/null +++ b/lib/models/dynamics/opus_detail/pendant.dart @@ -0,0 +1,39 @@ +class Pendant { + int? expire; + String? image; + String? imageEnhance; + String? imageEnhanceFrame; + int? nPid; + String? name; + int? pid; + + Pendant({ + this.expire, + this.image, + this.imageEnhance, + this.imageEnhanceFrame, + this.nPid, + this.name, + this.pid, + }); + + factory Pendant.fromJson(Map json) => Pendant( + expire: json['expire'] as int?, + image: json['image'] as String?, + imageEnhance: json['image_enhance'] as String?, + imageEnhanceFrame: json['image_enhance_frame'] as String?, + nPid: json['n_pid'] as int?, + name: json['name'] as String?, + pid: json['pid'] as int?, + ); + + Map toJson() => { + 'expire': expire, + 'image': image, + 'image_enhance': imageEnhance, + 'image_enhance_frame': imageEnhanceFrame, + 'n_pid': nPid, + 'name': name, + 'pid': pid, + }; +} diff --git a/lib/models/dynamics/opus_detail/pic.dart b/lib/models/dynamics/opus_detail/pic.dart new file mode 100644 index 000000000..fdbcab53a --- /dev/null +++ b/lib/models/dynamics/opus_detail/pic.dart @@ -0,0 +1,76 @@ +class Pic { + List? pics; + int? style; + String? url; + num? width; + double? height; + num? size; + + Pic({ + this.pics, + this.style, + this.url, + this.height, + this.width, + this.size, + }); + + factory Pic.fromJson(Map json) => Pic( + pics: (json['pics'] as List?) + ?.map((e) => PicItem.fromJson(e as Map)) + .toList(), + style: json['style'] as int?, + url: json['url'], + height: (json['height'] as num?)?.toDouble(), + width: json['width'] as num?, + size: json['size'] as num?, + ); + + Map toJson() => { + 'pics': pics?.map((e) => e.toJson()).toList(), + 'style': style, + 'height': height, + 'width': width, + 'size': size, + 'url': url, + }; +} + +class PicItem { + num? height; + num? width; + num? size; + String? url; + String? liveUrl; + double? calHeight; + + void onCalHeight(maxWidth) { + if (calHeight == null && height != null && width != null) { + calHeight = maxWidth * height! / width!; + } + } + + PicItem({ + this.height, + this.width, + this.size, + this.url, + this.liveUrl, + }); + + factory PicItem.fromJson(Map json) => PicItem( + height: json['height'] as num?, + width: json['width'] as num?, + size: json['size'] as num?, + url: json['url'] as String?, + liveUrl: json['live_url'] as String?, + ); + + Map toJson() => { + 'height': height, + 'width': width, + 'size': size, + 'url': url, + 'live_url': liveUrl, + }; +} diff --git a/lib/models/dynamics/opus_detail/pos_spec.dart b/lib/models/dynamics/opus_detail/pos_spec.dart new file mode 100644 index 000000000..a80a78e56 --- /dev/null +++ b/lib/models/dynamics/opus_detail/pos_spec.dart @@ -0,0 +1,19 @@ +class PosSpec { + double? axisX; + double? axisY; + int? coordinatePos; + + PosSpec({this.axisX, this.axisY, this.coordinatePos}); + + factory PosSpec.fromJson(Map json) => PosSpec( + axisX: (json['axis_x'] as num?)?.toDouble(), + axisY: (json['axis_y'] as num?)?.toDouble(), + coordinatePos: json['coordinate_pos'] as int?, + ); + + Map toJson() => { + 'axis_x': axisX, + 'axis_y': axisY, + 'coordinate_pos': coordinatePos, + }; +} diff --git a/lib/models/dynamics/opus_detail/remote.dart b/lib/models/dynamics/opus_detail/remote.dart new file mode 100644 index 000000000..e4daaafb3 --- /dev/null +++ b/lib/models/dynamics/opus_detail/remote.dart @@ -0,0 +1,16 @@ +class Remote { + String? bfsStyle; + String? url; + + Remote({this.bfsStyle, this.url}); + + factory Remote.fromJson(Map json) => Remote( + bfsStyle: json['bfs_style'] as String?, + url: json['url'] as String?, + ); + + Map toJson() => { + 'bfs_style': bfsStyle, + 'url': url, + }; +} diff --git a/lib/models/dynamics/opus_detail/render_spec.dart b/lib/models/dynamics/opus_detail/render_spec.dart new file mode 100644 index 000000000..926b96500 --- /dev/null +++ b/lib/models/dynamics/opus_detail/render_spec.dart @@ -0,0 +1,13 @@ +class RenderSpec { + int? opacity; + + RenderSpec({this.opacity}); + + factory RenderSpec.fromJson(Map json) => RenderSpec( + opacity: json['opacity'] as int?, + ); + + Map toJson() => { + 'opacity': opacity, + }; +} diff --git a/lib/models/dynamics/opus_detail/res_image.dart b/lib/models/dynamics/opus_detail/res_image.dart new file mode 100644 index 000000000..f5bf88b8d --- /dev/null +++ b/lib/models/dynamics/opus_detail/res_image.dart @@ -0,0 +1,17 @@ +import 'image_src.dart'; + +class ResImage { + ImageSrc? imageSrc; + + ResImage({this.imageSrc}); + + factory ResImage.fromJson(Map json) => ResImage( + imageSrc: json['image_src'] == null + ? null + : ImageSrc.fromJson(json['image_src'] as Map), + ); + + Map toJson() => { + 'image_src': imageSrc?.toJson(), + }; +} diff --git a/lib/models/dynamics/opus_detail/resource.dart b/lib/models/dynamics/opus_detail/resource.dart new file mode 100644 index 000000000..03f5d9ef8 --- /dev/null +++ b/lib/models/dynamics/opus_detail/resource.dart @@ -0,0 +1,20 @@ +import 'res_image.dart'; + +class Resource { + ResImage? resImage; + int? resType; + + Resource({this.resImage, this.resType}); + + factory Resource.fromJson(Map json) => Resource( + resImage: json['res_image'] == null + ? null + : ResImage.fromJson(json['res_image'] as Map), + resType: json['res_type'] as int?, + ); + + Map toJson() => { + 'res_image': resImage?.toJson(), + 'res_type': resType, + }; +} diff --git a/lib/models/dynamics/opus_detail/rich.dart b/lib/models/dynamics/opus_detail/rich.dart new file mode 100644 index 000000000..3cc2a9f78 --- /dev/null +++ b/lib/models/dynamics/opus_detail/rich.dart @@ -0,0 +1,31 @@ +import 'style.dart'; + +class Rich { + Style? style; + String? jumpUrl; + String? origText; + String? text; + + Rich({ + this.style, + this.jumpUrl, + this.origText, + this.text, + }); + + factory Rich.fromJson(Map json) => Rich( + style: json['style'] == null + ? null + : Style.fromJson(json['style'] as Map), + jumpUrl: json['jump_url'] as String?, + origText: json['orig_text'] as String?, + text: json['text'] as String?, + ); + + Map toJson() => { + 'style': style?.toJson(), + 'jump_url': jumpUrl, + 'orig_text': origText, + 'text': text, + }; +} diff --git a/lib/models/dynamics/opus_detail/share_info.dart b/lib/models/dynamics/opus_detail/share_info.dart new file mode 100644 index 000000000..9d991ac0e --- /dev/null +++ b/lib/models/dynamics/opus_detail/share_info.dart @@ -0,0 +1,19 @@ +class ShareInfo { + String? pic; + String? summary; + String? title; + + ShareInfo({this.pic, this.summary, this.title}); + + factory ShareInfo.fromJson(Map json) => ShareInfo( + pic: json['pic'] as String?, + summary: json['summary'] as String?, + title: json['title'] as String?, + ); + + Map toJson() => { + 'pic': pic, + 'summary': summary, + 'title': title, + }; +} diff --git a/lib/models/dynamics/opus_detail/size_spec.dart b/lib/models/dynamics/opus_detail/size_spec.dart new file mode 100644 index 000000000..88b13ec6e --- /dev/null +++ b/lib/models/dynamics/opus_detail/size_spec.dart @@ -0,0 +1,16 @@ +class SizeSpec { + num? height; + num? width; + + SizeSpec({this.height, this.width}); + + factory SizeSpec.fromJson(Map json) => SizeSpec( + height: json['height'], + width: json['width'], + ); + + Map toJson() => { + 'height': height, + 'width': width, + }; +} diff --git a/lib/models/dynamics/opus_detail/style.dart b/lib/models/dynamics/opus_detail/style.dart new file mode 100644 index 000000000..000bad738 --- /dev/null +++ b/lib/models/dynamics/opus_detail/style.dart @@ -0,0 +1,22 @@ +class Style { + Style({ + this.bold, + this.italic, + this.strikethrough, + }); + bool? bold; + bool? italic; + bool? strikethrough; + + factory Style.fromJson(Map json) => Style( + bold: json['bold'], + italic: json['italic'], + strikethrough: json['strikethrough'], + ); + + Map toJson() => { + 'bold': bold, + 'italic': italic, + 'strikethrough': strikethrough, + }; +} diff --git a/lib/models/dynamics/opus_detail/tags.dart b/lib/models/dynamics/opus_detail/tags.dart new file mode 100644 index 000000000..f73a40952 --- /dev/null +++ b/lib/models/dynamics/opus_detail/tags.dart @@ -0,0 +1,26 @@ +import 'general_cfg.dart'; + +class Tags { + // AvatarLayer? avatarLayer; + GeneralCfg? generalCfg; + + Tags({ + // this.avatarLayer, + this.generalCfg, + }); + + factory Tags.fromJson(Map json) => Tags( + // avatarLayer: json['AVATAR_LAYER'] == null + // ? null + // : AvatarLayer.fromJson( + // json['AVATAR_LAYER'] as Map), + generalCfg: json['GENERAL_CFG'] == null + ? null + : GeneralCfg.fromJson(json['GENERAL_CFG'] as Map), + ); + + Map toJson() => { + // 'AVATAR_LAYER': avatarLayer?.toJson(), + 'GENERAL_CFG': generalCfg?.toJson(), + }; +} diff --git a/lib/models/dynamics/opus_detail/text.dart b/lib/models/dynamics/opus_detail/text.dart new file mode 100644 index 000000000..03d65086d --- /dev/null +++ b/lib/models/dynamics/opus_detail/text.dart @@ -0,0 +1,17 @@ +import 'node.dart'; + +class ParagraphText { + List? nodes; + + ParagraphText({this.nodes}); + + factory ParagraphText.fromJson(Map json) => ParagraphText( + nodes: (json['nodes'] as List?) + ?.map((e) => Node.fromJson(e as Map)) + .toList(), + ); + + Map toJson() => { + 'nodes': nodes?.map((e) => e.toJson()).toList(), + }; +} diff --git a/lib/models/dynamics/opus_detail/vip.dart b/lib/models/dynamics/opus_detail/vip.dart new file mode 100644 index 000000000..3dabed7d4 --- /dev/null +++ b/lib/models/dynamics/opus_detail/vip.dart @@ -0,0 +1,73 @@ +import 'label.dart'; + +class Vip { + // AvatarIcon? avatarIcon; + int? avatarSubscript; + String? avatarSubscriptUrl; + int? dueDate; + Label? label; + String? nicknameColor; + int? role; + int? status; + int? themeType; + int? tvDueDate; + int? tvVipPayType; + int? tvVipStatus; + int? type; + int? vipPayType; + + Vip({ + // this.avatarIcon, + this.avatarSubscript, + this.avatarSubscriptUrl, + this.dueDate, + this.label, + this.nicknameColor, + this.role, + this.status, + this.themeType, + this.tvDueDate, + this.tvVipPayType, + this.tvVipStatus, + this.type, + this.vipPayType, + }); + + factory Vip.fromJson(Map json) => Vip( + // avatarIcon: json['avatar_icon'] == null + // ? null + // : AvatarIcon.fromJson(json['avatar_icon'] as Map), + avatarSubscript: json['avatar_subscript'] as int?, + avatarSubscriptUrl: json['avatar_subscript_url'] as String?, + dueDate: json['due_date'] as int?, + label: json['label'] == null + ? null + : Label.fromJson(json['label'] as Map), + nicknameColor: json['nickname_color'] as String?, + role: json['role'] as int?, + status: json['status'] as int?, + themeType: json['theme_type'] as int?, + tvDueDate: json['tv_due_date'] as int?, + tvVipPayType: json['tv_vip_pay_type'] as int?, + tvVipStatus: json['tv_vip_status'] as int?, + type: json['type'] as int?, + vipPayType: json['vip_pay_type'] as int?, + ); + + Map toJson() => { + // 'avatar_icon': avatarIcon?.toJson(), + 'avatar_subscript': avatarSubscript, + 'avatar_subscript_url': avatarSubscriptUrl, + 'due_date': dueDate, + 'label': label?.toJson(), + 'nickname_color': nicknameColor, + 'role': role, + 'status': status, + 'theme_type': themeType, + 'tv_due_date': tvDueDate, + 'tv_vip_pay_type': tvVipPayType, + 'tv_vip_status': tvVipStatus, + 'type': type, + 'vip_pay_type': vipPayType, + }; +} diff --git a/lib/models/dynamics/opus_detail/web_css_style.dart b/lib/models/dynamics/opus_detail/web_css_style.dart new file mode 100644 index 000000000..1d6a48b59 --- /dev/null +++ b/lib/models/dynamics/opus_detail/web_css_style.dart @@ -0,0 +1,13 @@ +class WebCssStyle { + String? borderRadius; + + WebCssStyle({this.borderRadius}); + + factory WebCssStyle.fromJson(Map json) => WebCssStyle( + borderRadius: json['borderRadius'] as String?, + ); + + Map toJson() => { + 'borderRadius': borderRadius, + }; +} diff --git a/lib/models/dynamics/opus_detail/word.dart b/lib/models/dynamics/opus_detail/word.dart new file mode 100644 index 000000000..327782012 --- /dev/null +++ b/lib/models/dynamics/opus_detail/word.dart @@ -0,0 +1,28 @@ +import 'style.dart'; + +class Word { + int? color; + double? fontSize; + Style? style; + String? words; + + Word({this.color, this.fontSize, this.style, this.words}); + + factory Word.fromJson(Map json) => Word( + color: json['color'] == null + ? null + : int.tryParse('0xFF${(json['color'] as String).substring(1)}'), + fontSize: (json['font_size'] as num?)?.toDouble(), + style: json['style'] == null + ? null + : Style.fromJson(json['style'] as Map), + words: json['words'] as String?, + ); + + Map toJson() => { + 'color': color, + 'font_size': fontSize, + 'style': style?.toJson(), + 'words': words, + }; +} diff --git a/lib/pages/article/controller.dart b/lib/pages/article/controller.dart new file mode 100644 index 000000000..2adbe4110 --- /dev/null +++ b/lib/pages/article/controller.dart @@ -0,0 +1,198 @@ +import 'package:PiliPlus/grpc/app/main/community/reply/v1/reply.pb.dart'; +import 'package:PiliPlus/http/dynamics.dart'; +import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/http/user.dart'; +import 'package:PiliPlus/http/video.dart'; +import 'package:PiliPlus/models/dynamics/article_view/data.dart'; +import 'package:PiliPlus/models/dynamics/opus_detail/data.dart'; +import 'package:PiliPlus/models/dynamics/opus_detail/favorite.dart'; +import 'package:PiliPlus/models/dynamics/result.dart'; +import 'package:PiliPlus/pages/common/reply_controller.dart'; +import 'package:PiliPlus/pages/mine/controller.dart'; +import 'package:PiliPlus/utils/storage.dart'; +import 'package:PiliPlus/utils/url_utils.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:get/get.dart'; +import 'package:PiliPlus/http/reply.dart'; +import 'package:fixnum/fixnum.dart' as $fixnum; + +class ArticleController extends ReplyController { + late String id; + late String type; + + late final String url; + late int commentType; + dynamic commentId; + + RxBool showTitle = false.obs; + + late final horizontalPreview = GStorage.horizontalPreview; + late final showDynActionBar = GStorage.showDynActionBar; + + @override + dynamic get sourceId => id; + + RxBool isLoaded = false.obs; + late ArticleData articleData; + late OpusData opusData; + + late final Rx item = DynamicItemModel().obs; + late final RxMap favStat = {'status': false}.obs; + + @override + void onInit() { + super.onInit(); + id = Get.parameters['id']!; + type = Get.parameters['type']!; + + // to opus + if (type == 'read') { + UrlUtils.parseRedirectUrl('https://www.bilibili.com/read/cv$id/') + .then((url) { + if (url != null) { + id = url.split('/').last; + type = 'opus'; + } + init(); + }); + } else { + init(); + } + } + + init() { + url = type == 'read' + ? 'https://www.bilibili.com/read/cv$id' + : 'https://www.bilibili.com/opus/$id'; + commentType = type == 'picture' ? 11 : 12; + + if (Get.arguments?['item'] is DynamicItemModel) { + item.value = Get.arguments['item']; + } + + _queryDynItem(); + _queryContent(); + } + + _queryDynItem() async { + if (showDynActionBar) { + if (type == 'read') { + if (item.value.idStr == null) { + UrlUtils.parseRedirectUrl('https://www.bilibili.com/read/cv$id/') + .then((url) { + if (url != null) { + _queryDyn(url.split('/').last); + } + }); + } + _queryInfo(); + } else { + _queryDyn(id); + } + } + } + + _queryInfo() { + DynamicsHttp.articleInfo(cvId: id).then((res) { + if (res['status']) { + favStat.addAll({ + 'status': true, + 'isFav': res['data']?['favorite'] ?? false, + 'favNum': res['data']?['stats']?['favorite'] ?? 0, + 'data': res['data'], + }); + } + }); + } + + _queryDyn(id) { + if (item.value.idStr != null) { + return; + } + DynamicsHttp.dynamicDetail(id: id).then((res) { + if (res['status']) { + item.value = res['data']; + } + }); + } + + Future _queryContent() async { + final res = type == 'read' + ? await DynamicsHttp.articleView(cvId: id) + : await DynamicsHttp.opusDetail(opusId: id); + if (res['status']) { + if (type == 'read') { + articleData = res['data']; + commentId = int.parse(id); + } else { + opusData = res['data']; + // fallback + if (opusData.fallback?.id != null) { + id = opusData.fallback!.id!; + type = 'read'; + commentType = 12; + _queryInfo(); + _queryContent(); + return; + } else { + commentType = opusData.item?.basic?.commentType ?? + (type == 'picture' ? 11 : 12); + commentId = int.parse(opusData.item!.basic!.commentIdStr!); + Favorite? favorite = + opusData.item?.modules?.lastOrNull?.moduleStat?.favorite; + favStat.addAll({ + 'status': true, + 'isFav': favorite?.status ?? false, + 'favNum': favorite?.count ?? 0, + }); + } + } + + isLoaded.value = true; + + if (isLogin && !MineController.anonymity.value) { + VideoHttp.historyReport(aid: commentId, type: 5); + } + + queryData(); + } + } + + @override + List? getDataList(MainListReply response) { + return response.replies; + } + + @override + Future> customGetData() { + return ReplyHttp.replyListGrpc( + type: commentType, + oid: commentId, + cursor: CursorReq( + next: cursor?.next ?? $fixnum.Int64(0), + mode: mode.value, + ), + antiGoodsReply: antiGoodsReply, + ); + } + + Future onFav() async { + bool isFav = favStat['isFav'] == true; + final res = type == 'read' + ? isFav + ? await UserHttp.delFavArticle(id: id) + : await UserHttp.addFavArticle(id: id) + : await UserHttp.communityAction(opusId: id, action: isFav ? 4 : 3); + if (res['status']) { + favStat['isFav'] = !isFav; + if (isFav) { + favStat['favNum'] -= 1; + } else { + favStat['favNum'] += 1; + } + SmartDialog.showToast('${isFav ? '取消' : ''}收藏成功'); + } else { + SmartDialog.showToast(res['msg']); + } + } +} diff --git a/lib/pages/html/view.dart b/lib/pages/article/view.dart similarity index 78% rename from lib/pages/html/view.dart rename to lib/pages/article/view.dart index 2390bb322..aa1f9d82e 100644 --- a/lib/pages/html/view.dart +++ b/lib/pages/article/view.dart @@ -1,10 +1,11 @@ import 'dart:math'; -import 'package:PiliPlus/common/widgets/article_content.dart'; +import 'package:PiliPlus/common/widgets/network_img_layer.dart'; +import 'package:PiliPlus/pages/article/widgets/opus_content.dart'; +import 'package:PiliPlus/pages/article/widgets/html_render.dart'; import 'package:PiliPlus/common/widgets/http_error.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/grpc/app/main/community/reply/v1/reply.pb.dart'; -import 'package:PiliPlus/http/constants.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models/common/reply_sort_type.dart'; import 'package:PiliPlus/pages/dynamics/repost_dyn_panel.dart'; @@ -21,8 +22,6 @@ import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:get/get.dart'; import 'package:PiliPlus/common/skeleton/video_reply.dart'; -import 'package:PiliPlus/common/widgets/html_render.dart'; -import 'package:PiliPlus/common/widgets/network_img_layer.dart'; import 'package:PiliPlus/models/common/reply_type.dart'; import 'package:PiliPlus/pages/video/detail/reply_reply/index.dart'; import 'package:PiliPlus/utils/feed_back.dart'; @@ -31,24 +30,19 @@ import 'package:html/parser.dart' as parser; import '../../utils/grid.dart'; import 'controller.dart'; -class HtmlRenderPage extends StatefulWidget { - const HtmlRenderPage({super.key}); +class ArticlePage extends StatefulWidget { + const ArticlePage({super.key}); @override - State createState() => _HtmlRenderPageState(); + State createState() => _ArticlePageState(); } -class _HtmlRenderPageState extends State +class _ArticlePageState extends State with TickerProviderStateMixin { - late final HtmlRenderController _htmlRenderCtr = Get.put( - HtmlRenderController(), - tag: Utils.makeHeroTag(id), + final ArticleController _articleCtr = Get.put( + ArticleController(), + tag: Utils.generateRandomString(8), ); - late String title; - late String id; - late String url; - late String dynamicType; - late int type; bool _isFabVisible = true; bool? _imageStatus; late AnimationController fabAnimationCtr; @@ -57,7 +51,7 @@ class _HtmlRenderPageState extends State bool get _horizontalPreview => context.orientation == Orientation.landscape && - _htmlRenderCtr.horizontalPreview; + _articleCtr.horizontalPreview; late final _key = GlobalKey(); @@ -104,35 +98,39 @@ class _HtmlRenderPageState extends State @override void initState() { super.initState(); - title = Get.parameters['title']!; - id = Get.parameters['id']!; - url = Get.parameters['url']!; - dynamicType = Get.parameters['dynamicType']!; - type = dynamicType == 'picture' ? 11 : 12; fabAnimationCtr = AnimationController( vsync: this, duration: const Duration(milliseconds: 300), ); fabAnimationCtr.forward(); - scrollListener(); + _articleCtr.scrollController.addListener(listener); } @override void dispose() { fabAnimationCtr.dispose(); - _htmlRenderCtr.scrollController.removeListener(listener); + _articleCtr.scrollController.removeListener(listener); super.dispose(); } - void scrollListener() { - _htmlRenderCtr.scrollController.addListener(listener); + @override + void didChangeDependencies() { + super.didChangeDependencies(); + WidgetsBinding.instance.addPostFrameCallback((_) { + if (_articleCtr.scrollController.hasClients) { + _articleCtr.showTitle.value = + _articleCtr.scrollController.positions.last.pixels >= 45; + } + }); } void listener() { + _articleCtr.showTitle.value = + _articleCtr.scrollController.positions.last.pixels >= 45; final ScrollDirection direction1 = - _htmlRenderCtr.scrollController.positions.first.userScrollDirection; + _articleCtr.scrollController.positions.first.userScrollDirection; late final ScrollDirection direction2 = - _htmlRenderCtr.scrollController.positions.last.userScrollDirection; + _articleCtr.scrollController.positions.last.userScrollDirection; if (direction1 == ScrollDirection.forward || direction2 == ScrollDirection.forward) { _showFab(); @@ -178,7 +176,7 @@ class _HtmlRenderPageState extends State oid: oid, rpid: rpid, source: 'dynamic', - replyType: ReplyType.values[type], + replyType: ReplyType.values[_articleCtr.commentType], firstFloor: replyItem, onDispose: onDispose, ), @@ -189,7 +187,7 @@ class _HtmlRenderPageState extends State replyReplyPage, routeName: 'htmlRender-Copy', arguments: { - 'id': _htmlRenderCtr.id, + 'id': _articleCtr.id, }, ); } else { @@ -219,7 +217,7 @@ class _HtmlRenderPageState extends State replyReplyPage, routeName: 'htmlRender-Copy', arguments: { - 'id': _htmlRenderCtr.id, + 'id': _articleCtr.id, }, ); } @@ -248,10 +246,9 @@ class _HtmlRenderPageState extends State return Padding( padding: EdgeInsets.symmetric(horizontal: padding), child: CustomScrollView( - controller: _htmlRenderCtr.scrollController, + controller: _articleCtr.scrollController, physics: const AlwaysScrollableScrollPhysics(), slivers: [ - _buildHeader, _buildContent(maxWidth), SliverToBoxAdapter( child: Divider( @@ -262,8 +259,8 @@ class _HtmlRenderPageState extends State ), ), _buildReplyHeader, - Obx(() => _buildReplyList( - _htmlRenderCtr.loadingState.value)), + Obx(() => + _buildReplyList(_articleCtr.loadingState.value)), ], ), ); @@ -279,13 +276,9 @@ class _HtmlRenderPageState extends State final maxWidth = constraints.maxWidth - padding / 4 - 24; return CustomScrollView( - controller: _htmlRenderCtr.scrollController, + controller: _articleCtr.scrollController, physics: const AlwaysScrollableScrollPhysics(), slivers: [ - SliverPadding( - padding: EdgeInsets.only(left: padding / 4), - sliver: _buildHeader, - ), SliverPadding( padding: EdgeInsets.only( left: padding / 4, @@ -311,17 +304,17 @@ class _HtmlRenderPageState extends State backgroundColor: Colors.transparent, body: refreshIndicator( onRefresh: () async { - await _htmlRenderCtr.onRefresh(); + await _articleCtr.onRefresh(); }, child: Padding( padding: EdgeInsets.only(right: padding / 4), child: CustomScrollView( - controller: _htmlRenderCtr.scrollController, + controller: _articleCtr.scrollController, physics: const AlwaysScrollableScrollPhysics(), slivers: [ _buildReplyHeader, Obx(() => _buildReplyList( - _htmlRenderCtr.loadingState.value)), + _articleCtr.loadingState.value)), ], ), ), @@ -344,29 +337,95 @@ class _HtmlRenderPageState extends State padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), sliver: Obx( () { - if (_htmlRenderCtr.loaded.value) { - if (_htmlRenderCtr.response['isJsonContent'] == true) { - return articleContent( + if (_articleCtr.isLoaded.value) { + if (_articleCtr.type == 'read') { + var res = parser.parse(_articleCtr.articleData.content); + return SliverMainAxisGroup( + slivers: [ + if (_articleCtr.articleData.title != null) + SliverToBoxAdapter( + child: Text( + _articleCtr.articleData.title!, + style: TextStyle( + fontSize: 17, + fontWeight: FontWeight.bold, + ), + ), + ), + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: GestureDetector( + onTap: () { + Get.toNamed( + '/member?mid=${_articleCtr.articleData.author?.mid}'); + }, + child: Row( + children: [ + NetworkImgLayer( + width: 40, + height: 40, + type: 'avatar', + src: _articleCtr.articleData.author?.face, + ), + const SizedBox(width: 10), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + _articleCtr.articleData.author?.name ?? "", + style: TextStyle( + fontSize: Theme.of(context) + .textTheme + .titleSmall! + .fontSize, + ), + ), + if (_articleCtr.articleData.publishTime != + null) + Text( + Utils.dateFormat( + _articleCtr.articleData.publishTime), + style: TextStyle( + color: Theme.of(context) + .colorScheme + .outline, + fontSize: Theme.of(context) + .textTheme + .labelSmall! + .fontSize, + ), + ), + ], + ), + ], + ), + ), + ), + ), + SliverList.separated( + itemCount: res.body!.children.length, + itemBuilder: (context, index) { + return htmlRender( + context: context, + element: res.body!.children[index], + maxWidth: maxWidth, + callback: _getImageCallback, + ); + }, + separatorBuilder: (context, index) => + const SizedBox(height: 10), + ), + ], + ); + } else { + return opusContent( context: context, - list: _htmlRenderCtr.response['content'], + modules: _articleCtr.opusData.item?.modules, callback: _getImageCallback, maxWidth: maxWidth, ); } - - // html - var res = parser.parse(_htmlRenderCtr.response['content']); - return SliverList.builder( - itemCount: res.body!.children.length, - itemBuilder: (context, index) { - return htmlRender( - context: context, - element: res.body!.children[index], - maxWidth: maxWidth, - callback: _getImageCallback, - ); - }, - ); } return const SliverToBoxAdapter(); @@ -387,14 +446,14 @@ class _HtmlRenderPageState extends State itemCount: loadingState.response!.length + 1, itemBuilder: (context, index) { if (index == loadingState.response!.length) { - _htmlRenderCtr.onLoadMore(); + _articleCtr.onLoadMore(); return Container( alignment: Alignment.center, margin: EdgeInsets.only( bottom: MediaQuery.of(context).padding.bottom), height: 125, child: Text( - _htmlRenderCtr.isEnd.not + _articleCtr.isEnd.not ? '加载中...' : loadingState.response!.isEmpty ? '还没有评论' @@ -412,22 +471,22 @@ class _HtmlRenderPageState extends State replyReply: (replyItem, id) => replyReply(context, replyItem, id), onReply: () { - _htmlRenderCtr.onReply( + _articleCtr.onReply( context, replyItem: loadingState.response![index], index: index, ); }, onDelete: (subIndex) => - _htmlRenderCtr.onRemove(index, subIndex), - upMid: _htmlRenderCtr.upMid, + _articleCtr.onRemove(index, subIndex), + upMid: _articleCtr.upMid, callback: _getImageCallback, onCheckReply: (item) => - _htmlRenderCtr.onCheckReply(context, item), - onToggleTop: (isUpTop, rpid) => _htmlRenderCtr.onToggleTop( + _articleCtr.onCheckReply(context, item), + onToggleTop: (isUpTop, rpid) => _articleCtr.onToggleTop( index, - _htmlRenderCtr.oid, - _htmlRenderCtr.type, + _articleCtr.commentId, + _articleCtr.commentType, isUpTop, rpid, ), @@ -436,11 +495,11 @@ class _HtmlRenderPageState extends State }, ) : HttpError( - onReload: _htmlRenderCtr.onReload, + onReload: _articleCtr.onReload, ), Error() => HttpError( errMsg: loadingState.errMsg, - onReload: _htmlRenderCtr.onReload, + onReload: _articleCtr.onReload, ), LoadingState() => throw UnimplementedError(), }; @@ -458,11 +517,11 @@ class _HtmlRenderPageState extends State SizedBox( height: 35, child: TextButton.icon( - onPressed: () => _htmlRenderCtr.queryBySort(), + onPressed: () => _articleCtr.queryBySort(), icon: const Icon(Icons.sort, size: 16), label: Obx( () => Text( - _htmlRenderCtr.sortType.value.label, + _articleCtr.sortType.value.label, style: const TextStyle(fontSize: 13), ), ), @@ -474,61 +533,17 @@ class _HtmlRenderPageState extends State ); } - Widget get _buildHeader => SliverToBoxAdapter( - child: Obx( - () => _htmlRenderCtr.loaded.value - ? Padding( - padding: const EdgeInsets.fromLTRB(12, 12, 12, 8), - child: GestureDetector( - onTap: () { - if (_htmlRenderCtr.mid != null) { - Get.toNamed('/member?mid=${_htmlRenderCtr.mid}'); - } - }, - child: Row( - children: [ - NetworkImgLayer( - width: 40, - height: 40, - type: 'avatar', - src: _htmlRenderCtr.response['avatar']!, - ), - const SizedBox(width: 10), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - _htmlRenderCtr.response['uname'], - style: TextStyle( - fontSize: Theme.of(context) - .textTheme - .titleSmall! - .fontSize, - ), - ), - Text( - _htmlRenderCtr.response['updateTime'], - style: TextStyle( - color: Theme.of(context).colorScheme.outline, - fontSize: Theme.of(context) - .textTheme - .labelSmall! - .fontSize, - ), - ), - ], - ), - const Spacer(), - ], - ), - ), - ) - : const SizedBox.shrink(), - ), - ); - PreferredSizeWidget get _buildAppBar => AppBar( - title: Text(title), + title: Obx(() { + if (_articleCtr.isLoaded.value && _articleCtr.showTitle.value) { + return Text(_articleCtr.type == 'read' + ? _articleCtr.articleData.title ?? '' + : _articleCtr.opusData.item?.modules?.firstOrNull?.moduleTitle + ?.text ?? + ''); + } + return const SizedBox.shrink(); + }), actions: [ const SizedBox(width: 4), if (context.orientation == Orientation.landscape) @@ -577,8 +592,7 @@ class _HtmlRenderPageState extends State IconButton( tooltip: '浏览器打开', onPressed: () { - PageUtils.inAppWebview( - url.startsWith('http') ? url : 'https:$url'); + PageUtils.inAppWebview(_articleCtr.url); }, icon: const Icon(Icons.open_in_browser_outlined, size: 19), ), @@ -586,34 +600,7 @@ class _HtmlRenderPageState extends State icon: const Icon(Icons.more_vert, size: 19), itemBuilder: (BuildContext context) => [ PopupMenuItem( - onTap: () => { - _htmlRenderCtr.reqHtml(), - }, - child: const Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(Icons.refresh, size: 19), - SizedBox(width: 10), - Text('刷新'), - ], - ), - ), - PopupMenuItem( - onTap: () { - PageUtils.inAppWebview( - url.startsWith('http') ? url : 'https:$url'); - }, - child: const Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(Icons.open_in_new, size: 19), - SizedBox(width: 10), - Text('浏览器打开'), - ], - ), - ), - PopupMenuItem( - onTap: () => Utils.copyText(url), + onTap: () => Utils.copyText(_articleCtr.url), child: const Row( mainAxisSize: MainAxisSize.min, children: [ @@ -624,7 +611,7 @@ class _HtmlRenderPageState extends State ), ), PopupMenuItem( - onTap: () => Utils.shareText(url), + onTap: () => Utils.shareText(_articleCtr.url), child: const Row( mainAxisSize: MainAxisSize.min, children: [ @@ -634,25 +621,23 @@ class _HtmlRenderPageState extends State ], ), ), - if (_htmlRenderCtr.dynamicType == 'read' && - _htmlRenderCtr.favStat['status']) + if (_articleCtr.type == 'read' && _articleCtr.favStat['status']) PopupMenuItem( onTap: () { try { PageUtils.pmShare( content: { - "id": _htmlRenderCtr.id.substring(2), + "id": _articleCtr.id, "title": "- 哔哩哔哩专栏", - "headline": _htmlRenderCtr.favStat['data']['title'], + "headline": _articleCtr.favStat['data']['title'], "source": 6, - "thumb": (_htmlRenderCtr.favStat['data'] + "thumb": (_articleCtr.favStat['data'] ['origin_image_urls'] as List?) ?.firstOrNull ?? '', - "author": _htmlRenderCtr.favStat['data'] - ['author_name'], + "author": _articleCtr.favStat['data']['author_name'], "author_id": - _htmlRenderCtr.favStat['data']['mid'].toString(), + _articleCtr.favStat['data']['mid'].toString(), }, ); } catch (e) { @@ -692,16 +677,16 @@ class _HtmlRenderPageState extends State heroTag: null, onPressed: () { feedBack(); - _htmlRenderCtr.onReply( + _articleCtr.onReply( context, - oid: _htmlRenderCtr.oid.value, - replyType: ReplyType.values[type], + oid: _articleCtr.commentId, + replyType: ReplyType.values[_articleCtr.commentType], ); }, tooltip: '评论动态', child: const Icon(Icons.reply), ); - return _htmlRenderCtr.showDynActionBar.not + return _articleCtr.showDynActionBar.not ? Align( alignment: Alignment.bottomRight, child: Padding( @@ -721,13 +706,13 @@ class _HtmlRenderPageState extends State padding: EdgeInsets.only( right: 14, bottom: 14 + - (_htmlRenderCtr.item.value.idStr != null + (_articleCtr.favStat['status'] ? 0 : MediaQuery.of(context).padding.bottom), ), child: button(), ), - _htmlRenderCtr.item.value.idStr != null + _articleCtr.favStat['status'] ? Container( decoration: BoxDecoration( color: @@ -759,11 +744,10 @@ class _HtmlRenderPageState extends State useSafeArea: true, builder: (context) => RepostPanel( - item: - _htmlRenderCtr.item.value, + item: _articleCtr.item.value, callback: () { int count = int.tryParse( - _htmlRenderCtr + _articleCtr .item .value .modules @@ -772,7 +756,7 @@ class _HtmlRenderPageState extends State ?.count ?? '0') ?? 0; - _htmlRenderCtr + _articleCtr .item .value .modules @@ -805,7 +789,7 @@ class _HtmlRenderPageState extends State .outline, ), label: Text( - _htmlRenderCtr + _articleCtr .item .value .modules @@ -813,14 +797,13 @@ class _HtmlRenderPageState extends State ?.forward! .count != null - ? Utils.numFormat( - _htmlRenderCtr - .item - .value - .modules - ?.moduleStat - ?.forward! - .count) + ? Utils.numFormat(_articleCtr + .item + .value + .modules + ?.moduleStat + ?.forward! + .count) : '转发', ), ), @@ -829,8 +812,7 @@ class _HtmlRenderPageState extends State Expanded( child: TextButton.icon( onPressed: () { - Utils.shareText( - '${HttpString.dynamicShareBaseUrl}/${_htmlRenderCtr.item.value.idStr}'); + Utils.shareText(_articleCtr.url); }, icon: Icon( FontAwesomeIcons.shareNodes, @@ -850,19 +832,19 @@ class _HtmlRenderPageState extends State label: const Text('分享'), ), ), - if (_htmlRenderCtr.favStat['status']) + if (_articleCtr.favStat['status']) Expanded( child: TextButton.icon( onPressed: () { - _htmlRenderCtr.onFav(); + _articleCtr.onFav(); }, icon: Icon( - _htmlRenderCtr.favStat['isFav'] == + _articleCtr.favStat['isFav'] == true ? FontAwesomeIcons.solidStar : FontAwesomeIcons.star, size: 16, - color: _htmlRenderCtr + color: _articleCtr .favStat['isFav'] == true ? Theme.of(context) @@ -881,7 +863,7 @@ class _HtmlRenderPageState extends State .colorScheme .outline, ), - label: Text(_htmlRenderCtr + label: Text(_articleCtr .favStat['favNum'] .toString()), ), @@ -891,7 +873,7 @@ class _HtmlRenderPageState extends State builder: (context) => TextButton.icon( onPressed: () => RequestUtils.onLikeDynamic( - _htmlRenderCtr.item.value, + _articleCtr.item.value, () { if (context.mounted) { (context as Element?) @@ -900,7 +882,7 @@ class _HtmlRenderPageState extends State }, ), icon: Icon( - _htmlRenderCtr + _articleCtr .item .value .modules @@ -912,7 +894,7 @@ class _HtmlRenderPageState extends State .solidThumbsUp : FontAwesomeIcons.thumbsUp, size: 16, - color: _htmlRenderCtr + color: _articleCtr .item .value .modules @@ -926,7 +908,7 @@ class _HtmlRenderPageState extends State : Theme.of(context) .colorScheme .outline, - semanticLabel: _htmlRenderCtr + semanticLabel: _articleCtr .item .value .modules @@ -955,7 +937,7 @@ class _HtmlRenderPageState extends State child: child); }, child: Text( - _htmlRenderCtr + _articleCtr .item .value .modules @@ -964,7 +946,7 @@ class _HtmlRenderPageState extends State ?.count != null ? Utils.numFormat( - _htmlRenderCtr + _articleCtr .item .value .modules! @@ -973,7 +955,7 @@ class _HtmlRenderPageState extends State .count) : '点赞', style: TextStyle( - color: _htmlRenderCtr + color: _articleCtr .item .value .modules diff --git a/lib/common/widgets/html_render.dart b/lib/pages/article/widgets/html_render.dart similarity index 97% rename from lib/common/widgets/html_render.dart rename to lib/pages/article/widgets/html_render.dart index bac8f6a5e..04cfe83ad 100644 --- a/lib/common/widgets/html_render.dart +++ b/lib/pages/article/widgets/html_render.dart @@ -5,7 +5,7 @@ import 'package:PiliPlus/utils/utils.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter_html/flutter_html.dart'; -import 'network_img_layer.dart'; +import '../../../common/widgets/network_img_layer.dart'; import 'package:html/dom.dart' as dom; Widget htmlRender({ @@ -64,9 +64,8 @@ Widget htmlRender({ }, child: NetworkImgLayer( width: isEmote ? 22 : maxWidth, - height: isEmote ? 22 : 200, + height: isEmote ? 22 : null, src: imgUrl, - ignoreHeight: !isEmote, ), ), ); diff --git a/lib/pages/article/widgets/opus_content.dart b/lib/pages/article/widgets/opus_content.dart new file mode 100644 index 000000000..8635161e9 --- /dev/null +++ b/lib/pages/article/widgets/opus_content.dart @@ -0,0 +1,196 @@ +import 'package:PiliPlus/common/widgets/interactiveviewer_gallery/interactiveviewer_gallery.dart' + show SourceModel; +import 'package:PiliPlus/common/widgets/network_img_layer.dart'; +import 'package:PiliPlus/models/dynamics/opus_detail/module.dart'; +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:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +Widget opusContent({ + required BuildContext context, + required List? modules, + Function(List, int)? callback, + required double maxWidth, +}) { + debugPrint('opusContent'); + + if (modules.isNullOrEmpty) { + return const SliverToBoxAdapter(); + } + + return SliverMainAxisGroup( + slivers: modules!.map((item) { + try { + return switch (item.moduleType) { + // + 'MODULE_TYPE_TITLE' => SliverToBoxAdapter( + child: Text( + item.moduleTitle!.text!, + style: TextStyle( + fontSize: 17, + fontWeight: FontWeight.bold, + ), + ), + ), + + // + 'MODULE_TYPE_AUTHOR' => SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: GestureDetector( + onTap: () { + Get.toNamed('/member?mid=${item.moduleAuthor?.mid}'); + }, + child: Row( + children: [ + NetworkImgLayer( + width: 40, + height: 40, + type: 'avatar', + src: item.moduleAuthor?.face, + ), + const SizedBox(width: 10), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + item.moduleAuthor!.name!, + style: TextStyle( + fontSize: Theme.of(context) + .textTheme + .titleSmall! + .fontSize, + ), + ), + if (item.moduleAuthor?.pubTime != null) + Text( + item.moduleAuthor!.pubTime!, + style: TextStyle( + color: Theme.of(context).colorScheme.outline, + fontSize: Theme.of(context) + .textTheme + .labelSmall! + .fontSize, + ), + ), + ], + ), + ], + ), + ), + ), + ), + + // + 'MODULE_TYPE_CONTENT' => SliverList.separated( + itemCount: item.moduleContent!.paragraphs!.length, + itemBuilder: (context, index) { + final element = item.moduleContent!.paragraphs![index]; + + if ((element.paraType == 1 || element.paraType == 4)) { + return 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: TextStyle( + decoration: item.rich?.style?.strikethrough == true + ? TextDecoration.lineThrough + : null, + fontStyle: item.rich?.style?.italic == true + ? FontStyle.italic + : null, + fontWeight: item.rich?.style?.bold == true + ? FontWeight.bold + : null, + color: Theme.of(context).colorScheme.primary, + ), + recognizer: TapGestureRecognizer() + ..onTap = () { + if (item.rich!.jumpUrl != null) { + PiliScheme.routePushFromUrl( + item.rich!.jumpUrl!); + } + }, + ); + } + return TextSpan( + text: item.word?.words, + style: TextStyle( + decoration: item.word?.style?.strikethrough == true + ? TextDecoration.lineThrough + : null, + fontStyle: item.word?.style?.italic == true + ? FontStyle.italic + : null, + fontWeight: item.word?.style?.bold == true + ? FontWeight.bold + : null, + color: item.word?.color != null + ? Color(item.word!.color!) + : null, + fontSize: item.word?.fontSize, + ), + ); + }).toList()), + ); + } + + if (element.paraType == 2) { + element.pic!.pics!.first.onCalHeight(maxWidth); + return Hero( + tag: element.pic!.pics!.first.url!, + child: GestureDetector( + onTap: () { + if (callback != null) { + callback( + [element.pic!.pics!.first.url!], + 0, + ); + } else { + context.imageView( + initialPage: 0, + imgList: [ + SourceModel(url: element.pic!.pics!.first.url!) + ], + ); + } + }, + child: NetworkImgLayer( + width: maxWidth, + height: element.pic!.pics!.first.calHeight, + src: element.pic!.pics!.first.url!, + quality: 60, + ), + ), + ); + } + + if (element.paraType == 3) { + return CachedNetworkImage( + imageUrl: Utils.thumbnailImgUrl(element.line!.pic!.url!), + height: element.line?.pic?.height, + ); + } + + return const SizedBox.shrink(); + }, + separatorBuilder: (BuildContext context, int index) => + const SizedBox(height: 10), + ), + + // + _ => const SliverToBoxAdapter(), + }; + } catch (e) { + return SliverToBoxAdapter(child: Text(e.toString())); + } + }).toList(), + ); +} diff --git a/lib/pages/dynamics/detail/controller.dart b/lib/pages/dynamics/detail/controller.dart index cee22a79d..c0fba446c 100644 --- a/lib/pages/dynamics/detail/controller.dart +++ b/lib/pages/dynamics/detail/controller.dart @@ -1,12 +1,13 @@ import 'package:PiliPlus/grpc/app/main/community/reply/v1/reply.pb.dart'; +import 'package:PiliPlus/http/dynamics.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models/common/reply_type.dart'; +import 'package:PiliPlus/models/dynamics/opus_detail/data.dart'; import 'package:PiliPlus/models/dynamics/result.dart'; import 'package:PiliPlus/pages/common/reply_controller.dart'; import 'package:PiliPlus/utils/id_utils.dart'; import 'package:PiliPlus/utils/storage.dart'; import 'package:get/get.dart'; -import 'package:PiliPlus/http/html.dart'; import 'package:PiliPlus/http/reply.dart'; import 'package:fixnum/fixnum.dart' as $fixnum; @@ -38,12 +39,14 @@ class DynamicDetailController extends ReplyController { } } - // 根据jumpUrl获取动态html - reqHtmlByOpusId(int id) async { - var res = await HtmlHttp.reqHtml(id, 'opus'); - type = res['commentType']; - oid = res['commentId']; - queryData(); + getCommentParams(int id) async { + var res = await DynamicsHttp.opusDetail(opusId: id); + if (res['status']) { + OpusData data = res['data']; + type = data.item!.basic!.commentType!; + oid = int.parse(data.item!.basic!.commentIdStr!); + queryData(); + } } @override diff --git a/lib/pages/dynamics/detail/view.dart b/lib/pages/dynamics/detail/view.dart index 5cdabbf82..aad932473 100644 --- a/lib/pages/dynamics/detail/view.dart +++ b/lib/pages/dynamics/detail/view.dart @@ -109,8 +109,7 @@ class _DynamicDetailPageState extends State duration: const Duration(milliseconds: 300), ); _fabAnimationCtr?.forward(); - // 滚动事件监听 - scrollListener(); + _dynamicDetailController.scrollController.addListener(listener); } // 页面初始化 @@ -139,7 +138,7 @@ class _DynamicDetailPageState extends State DynamicDetailController(oid, replyType), tag: Utils.makeHeroTag(opusId), ); - await _dynamicDetailController.reqHtmlByOpusId(opusId!); + await _dynamicDetailController.getCommentParams(opusId!); setState(() {}); } } else { @@ -227,17 +226,22 @@ class _DynamicDetailPageState extends State }); } - // 滑动事件监听 - void scrollListener() { - _dynamicDetailController.scrollController.addListener(listener); + @override + void didChangeDependencies() { + super.didChangeDependencies(); + WidgetsBinding.instance.addPostFrameCallback((_) { + if (_dynamicDetailController.scrollController.hasClients) { + _visibleTitle.value = + _dynamicDetailController.scrollController.positions.first.pixels > + 55; + } + }); } void listener() { // 标题 - if (_dynamicDetailController.scrollController.positions.length == 1) { - _visibleTitle.value = - _dynamicDetailController.scrollController.offset > 55; - } + _visibleTitle.value = + _dynamicDetailController.scrollController.positions.first.pixels > 55; // fab按钮 final ScrollDirection direction1 = _dynamicDetailController diff --git a/lib/pages/fav/article/widget/item.dart b/lib/pages/fav/article/widget/item.dart index c1ea2ecfb..416da2d60 100644 --- a/lib/pages/fav/article/widget/item.dart +++ b/lib/pages/fav/article/widget/item.dart @@ -2,8 +2,8 @@ import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/icon_button.dart'; import 'package:PiliPlus/common/widgets/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/stat/stat.dart'; -import 'package:PiliPlus/utils/app_scheme.dart'; import 'package:flutter/material.dart'; +import 'package:get/get.dart'; class FavArticleItem extends StatelessWidget { const FavArticleItem({ @@ -24,7 +24,13 @@ class FavArticleItem extends StatelessWidget { children: [ InkWell( onTap: () { - PiliScheme.routePushFromUrl(item['jump_url']); + Get.toNamed( + '/articlePage', + parameters: { + 'id': item['opus_id'], + 'type': 'opus', + }, + ); }, child: Padding( padding: const EdgeInsets.symmetric( diff --git a/lib/pages/history/widgets/item.dart b/lib/pages/history/widgets/item.dart index 5fc7e6184..bc48f0ec3 100644 --- a/lib/pages/history/widgets/item.dart +++ b/lib/pages/history/widgets/item.dart @@ -48,12 +48,12 @@ class HistoryItem extends StatelessWidget { } if (videoItem.history.business?.contains('article') == true) { PageUtils.toDupNamed( - '/htmlRender', + '/articlePage', parameters: { - 'url': 'https://www.bilibili.com/read/cv${videoItem.history.oid}', - 'title': '', - 'id': 'cv${videoItem.history.oid}', - 'dynamicType': 'read' + 'id': videoItem.history.business == 'article-list' + ? '${videoItem.history.cid}' + : '${videoItem.history.oid}', + 'type': 'read', }, ); } else if (videoItem.history.business == 'live') { diff --git a/lib/pages/html/controller.dart b/lib/pages/html/controller.dart deleted file mode 100644 index ab2b5cea8..000000000 --- a/lib/pages/html/controller.dart +++ /dev/null @@ -1,152 +0,0 @@ -import 'package:PiliPlus/grpc/app/main/community/reply/v1/reply.pb.dart'; -import 'package:PiliPlus/http/dynamics.dart'; -import 'package:PiliPlus/http/loading_state.dart'; -import 'package:PiliPlus/http/user.dart'; -import 'package:PiliPlus/http/video.dart'; -import 'package:PiliPlus/models/dynamics/result.dart'; -import 'package:PiliPlus/pages/common/reply_controller.dart'; -import 'package:PiliPlus/pages/mine/controller.dart'; -import 'package:PiliPlus/utils/storage.dart'; -import 'package:PiliPlus/utils/url_utils.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; -import 'package:get/get.dart'; -import 'package:PiliPlus/http/html.dart'; -import 'package:PiliPlus/http/reply.dart'; -import 'package:fixnum/fixnum.dart' as $fixnum; - -class HtmlRenderController extends ReplyController { - late String id; - late String dynamicType; - late int type; - RxInt oid = (-1).obs; - late Map response; - int? floor; - dynamic mid; - - Rx item = DynamicItemModel().obs; - - final RxMap favStat = {'status': false}.obs; - final RxBool loaded = false.obs; - - late final horizontalPreview = GStorage.horizontalPreview; - late final showDynActionBar = GStorage.showDynActionBar; - - @override - dynamic get sourceId => id; - - @override - void onInit() { - super.onInit(); - id = Get.parameters['id']!; - dynamicType = Get.parameters['dynamicType']!; - type = dynamicType == 'picture' ? 11 : 12; - - if (showDynActionBar) { - if (dynamicType == 'read') { - UrlUtils.parseRedirectUrl('https://www.bilibili.com/read/$id/') - .then((url) { - if (url != null) { - _queryDyn(url.split('/').last); - } - }); - DynamicsHttp.articleInfo(cvId: id.substring(2)).then((res) { - if (res['status']) { - favStat.addAll({ - 'status': true, - 'isFav': res['data']?['favorite'] ?? false, - 'favNum': res['data']?['stats']?['favorite'] ?? 0, - 'data': res['data'], - }); - } - }); - } else { - _queryDyn(id); - } - } - - reqHtml(); - } - - _queryDyn(id) { - DynamicsHttp.dynamicDetail(id: id).then((res) { - if (res['status']) { - item.value = res['data']; - } else { - debugPrint('${res['msg']}'); - } - }); - } - - // 请求动态内容 - Future reqHtml() async { - late dynamic res; - if (dynamicType == 'opus' || dynamicType == 'picture') { - res = await HtmlHttp.reqHtml(id, dynamicType); - if (res != null) { - if (res['commentType'] is int) { - type = res['commentType']; - } - if (res['favorite'] != null) { - favStat.addAll({ - 'status': true, - 'isFav': res['favorite']['status'] ?? false, - 'favNum': res['favorite']['count'] ?? 0, - }); - } - } - } else { - res = await HtmlHttp.reqReadHtml(id, dynamicType); - } - if (res != null) { - response = res; - mid = res['mid']; - oid.value = res['commentId']; - if (isLogin && !MineController.anonymity.value) { - VideoHttp.historyReport(aid: oid.value, type: 5); - } - queryData(); - if (res['status'] == true) { - loaded.value = true; - } - } - } - - @override - List? getDataList(MainListReply response) { - return response.replies; - } - - @override - Future> customGetData() { - return ReplyHttp.replyListGrpc( - type: type, - oid: oid.value, - cursor: CursorReq( - next: cursor?.next ?? $fixnum.Int64(0), - mode: mode.value, - ), - antiGoodsReply: antiGoodsReply, - ); - } - - Future onFav() async { - bool isFav = favStat['isFav'] == true; - final res = dynamicType == 'read' - ? isFav - ? await UserHttp.delFavArticle(id: id.substring(2)) - : await UserHttp.addFavArticle(id: id.substring(2)) - : await UserHttp.communityAction(opusId: id, action: isFav ? 4 : 3); - if (res['status']) { - favStat['isFav'] = !isFav; - if (isFav) { - favStat['favNum'] -= 1; - } else { - favStat['favNum'] += 1; - } - SmartDialog.showToast('${isFav ? '取消' : ''}收藏成功'); - } else { - SmartDialog.showToast(res['msg']); - } - } -} diff --git a/lib/pages/html/index.dart b/lib/pages/html/index.dart deleted file mode 100644 index c62e60b7a..000000000 --- a/lib/pages/html/index.dart +++ /dev/null @@ -1,4 +0,0 @@ -library html_render; - -export './controller.dart'; -export './view.dart'; diff --git a/lib/pages/search_panel/article/controller.dart b/lib/pages/search_panel/article/controller.dart index 612c6ba29..abdd5041b 100644 --- a/lib/pages/search_panel/article/controller.dart +++ b/lib/pages/search_panel/article/controller.dart @@ -28,12 +28,10 @@ class SearchArticleController if (cvid != null) { WidgetsBinding.instance.addPostFrameCallback((_) { Get.toNamed( - '/htmlRender', + '/articlePage', parameters: { - 'url': 'https://www.bilibili.com/read/cv$cvid', - 'title': '', - 'id': 'cv$cvid', - 'dynamicType': 'read' + 'id': cvid, + 'type': 'read', }, ); }); diff --git a/lib/pages/search_panel/article/view.dart b/lib/pages/search_panel/article/view.dart index 796fc9adf..a8cd8b7a3 100644 --- a/lib/pages/search_panel/article/view.dart +++ b/lib/pages/search_panel/article/view.dart @@ -122,12 +122,13 @@ class _SearchArticlePanelState extends CommonSearchPanelState< final item = list[index]; return InkWell( onTap: () { - Get.toNamed('/htmlRender', parameters: { - 'url': 'www.bilibili.com/read/cv${item.id}', - 'title': item.subTitle ?? '', - 'id': 'cv${item.id}', - 'dynamicType': 'read' - }); + Get.toNamed( + '/articlePage', + parameters: { + 'id': '${item.id}', + 'type': 'read', + }, + ); }, onLongPress: () => imageSaveDialog( context: context, diff --git a/lib/pages/video/detail/reply/widgets/reply_item_grpc.dart b/lib/pages/video/detail/reply/widgets/reply_item_grpc.dart index e1e4f73db..74ebaac40 100644 --- a/lib/pages/video/detail/reply/widgets/reply_item_grpc.dart +++ b/lib/pages/video/detail/reply/widgets/reply_item_grpc.dart @@ -793,12 +793,13 @@ class ReplyItemGrpc extends StatelessWidget { firstMatch?.group(2) ?? firstMatch?.group(3); if (cvid != null) { - Get.toNamed('/htmlRender', parameters: { - 'url': 'https://www.bilibili.com/read/cv$cvid', - 'title': title, - 'id': 'cv$cvid', - 'dynamicType': 'read' - }); + Get.toNamed( + '/articlePage', + parameters: { + 'id': cvid, + 'type': 'read', + }, + ); return; } PageUtils.handleWebview(matchStr); @@ -902,12 +903,13 @@ class ReplyItemGrpc extends StatelessWidget { .firstMatch(patternStr) ?.group(1); if (cvid != null) { - Get.toNamed('/htmlRender', parameters: { - 'url': 'https://www.bilibili.com/read/cv$cvid', - 'title': '', - 'id': 'cv$cvid', - 'dynamicType': 'read' - }); + Get.toNamed( + '/articlePage', + parameters: { + 'id': cvid, + 'type': 'read', + }, + ); return; } diff --git a/lib/pages/whisper_detail/widget/chat_item.dart b/lib/pages/whisper_detail/widget/chat_item.dart index 1d3968f7f..9619fcd72 100644 --- a/lib/pages/whisper_detail/widget/chat_item.dart +++ b/lib/pages/whisper_detail/widget/chat_item.dart @@ -219,12 +219,10 @@ class ChatItem extends StatelessWidget { type = '专栏'; onTap = () { Get.toNamed( - '/htmlRender', + '/articlePage', parameters: { - 'url': 'www.bilibili.com/opus/cv${content['id']}', - 'title': '', - 'id': 'cv${content['id']}', - 'dynamicType': 'read' + 'id': '${content['id']}', + 'type': 'read', }, ); }; @@ -463,12 +461,13 @@ class ChatItem extends StatelessWidget { case MsgType.article_card: return GestureDetector( onTap: () async { - Get.toNamed('/htmlRender', parameters: { - 'url': "https://www.bilibili.com/read/cv${content['rid']}/", - 'title': content['title'] ?? "", - 'id': "cv${content['rid']}", - 'dynamicType': "read" - }); + Get.toNamed( + '/articlePage', + parameters: { + 'id': '${content['rid']}', + 'type': "read", + }, + ); }, child: Column( crossAxisAlignment: CrossAxisAlignment.start, diff --git a/lib/router/app_pages.dart b/lib/router/app_pages.dart index 68c32db43..c014b22e5 100644 --- a/lib/router/app_pages.dart +++ b/lib/router/app_pages.dart @@ -1,4 +1,5 @@ import 'package:PiliPlus/pages/about/view.dart'; +import 'package:PiliPlus/pages/article/view.dart'; import 'package:PiliPlus/pages/fav/view.dart'; import 'package:PiliPlus/pages/fav_search/view.dart'; import 'package:PiliPlus/pages/follow_search/view.dart'; @@ -33,7 +34,6 @@ import '../pages/follow/index.dart'; import '../pages/history/index.dart'; import '../pages/home/index.dart'; import '../pages/hot/index.dart'; -import '../pages/html/index.dart'; import '../pages/later/index.dart'; import '../pages/live_room/view.dart'; import '../pages/login/index.dart'; @@ -126,7 +126,7 @@ class Routes { // 关于 CustomGetPage(name: '/about', page: () => const AboutPage()), // - CustomGetPage(name: '/htmlRender', page: () => const HtmlRenderPage()), + CustomGetPage(name: '/articlePage', page: () => const ArticlePage()), // 历史记录搜索 CustomGetPage(name: '/playSpeedSet', page: () => const PlaySpeedPage()), diff --git a/lib/utils/app_scheme.dart b/lib/utils/app_scheme.dart index a1532646d..6e9e190fe 100644 --- a/lib/utils/app_scheme.dart +++ b/lib/utils/app_scheme.dart @@ -204,22 +204,6 @@ class PiliScheme { } return false; case 'opus': - // bilibili://opus/detail/12345678?h5awaken=random - // String? id = uriDigitRegExp.firstMatch(path)?.group(1); - // if (id != null) { - // PageUtils.toDupNamed( - // '/htmlRender', - // parameters: { - // 'url': 'https://www.bilibili.com/opus/$id', - // 'title': '', - // 'id': id, - // 'dynamicType': 'opus' - // }, - // off: off, - // ); - // return true; - // } - // return false; bool hasMatch = await _onPushDynDetail(path, off); return hasMatch; case 'search': @@ -234,12 +218,10 @@ class PiliScheme { String? id = uriDigitRegExp.firstMatch(path)?.group(1); if (id != null) { PageUtils.toDupNamed( - '/htmlRender', + '/articlePage', parameters: { - 'url': 'www.bilibili.com/read/cv$id', - 'title': '', - 'id': 'cv$id', - 'dynamicType': 'read' + 'id': id, + 'type': 'read', }, off: off, ); @@ -364,12 +346,10 @@ class PiliScheme { ?.group(1); if (cvid != null) { PageUtils.toDupNamed( - '/htmlRender', + '/articlePage', parameters: { - 'url': 'https://www.bilibili.com/read/cv$cvid', - 'title': '', - 'id': 'cv$cvid', - 'dynamicType': 'read' + 'id': cvid, + 'type': 'read', }, off: off, ); @@ -581,12 +561,10 @@ class PiliScheme { ?.group(1); if (id != null) { PageUtils.toDupNamed( - '/htmlRender', + '/articlePage', parameters: { - 'url': 'https://www.bilibili.com/read/cv$id', - 'title': '', - 'id': 'cv$id', - 'dynamicType': 'read' + 'id': id, + 'type': 'read', }, off: off, ); @@ -649,12 +627,10 @@ class PiliScheme { RegExp(r'cv(\d+)', caseSensitive: false).firstMatch(path)?.group(1); if (id != null) { PageUtils.toDupNamed( - '/htmlRender', + '/articlePage', parameters: { - 'url': 'https://www.bilibili.com/read/cv$id', - 'title': '', - 'id': 'cv$id', - 'dynamicType': 'read' + 'id': id, + 'type': 'read', }, off: off, ); diff --git a/lib/utils/page_utils.dart b/lib/utils/page_utils.dart index 94edef4f6..8bab2ef3d 100644 --- a/lib/utils/page_utils.dart +++ b/lib/utils/page_utils.dart @@ -267,12 +267,13 @@ class PageUtils { DynamicItemModel data = res['data']; if (data.basic?['comment_type'] == 12) { toDupNamed( - '/htmlRender', + '/articlePage', parameters: { - 'url': 'www.bilibili.com/opus/$id', - 'title': '', 'id': id, - 'dynamicType': 'opus' + 'type': 'opus', + }, + arguments: { + 'item': data, }, off: off, ); @@ -398,20 +399,17 @@ class PageUtils { case 'DYNAMIC_TYPE_ARTICLE': String? url = item?.modules?.moduleDynamic?.major?.opus?.jumpUrl; if (url != null) { - String? title = item?.modules?.moduleDynamic?.major?.opus?.title; if (url.contains('opus') || url.contains('read')) { RegExp digitRegExp = RegExp(r'\d+'); Iterable matches = digitRegExp.allMatches(url); String number = matches.first.group(0)!; - if (url.contains('read')) { - number = 'cv$number'; - } - toDupNamed('/htmlRender', parameters: { - 'url': url.startsWith('//') ? url.split('//').last : url, - 'title': title ?? '', - 'id': number, - 'dynamicType': url.split('//').last.split('/')[1] - }); + toDupNamed( + '/articlePage', + parameters: { + 'id': number, + 'type': url.split('//').last.split('/')[1], + }, + ); } else { handleWebview('https:$url'); }