diff --git a/lib/models/dynamics/article_content_model.dart b/lib/models/dynamics/article_content_model.dart index 6f8a3d4f..b48111f4 100644 --- a/lib/models/dynamics/article_content_model.dart +++ b/lib/models/dynamics/article_content_model.dart @@ -173,11 +173,28 @@ class Card { String? oid; String? type; Ugc? ugc; + ItemNull? itemNull; Card.fromJson(Map json) { oid = json['oid']; type = json['type']; ugc = json['ugc'] == null ? null : Ugc.fromJson(json['ugc']); + itemNull = + json['item_null'] == null ? null : ItemNull.fromJson(json['item_null']); + } +} + +class ItemNull { + ItemNull({ + this.icon, + this.text, + }); + String? icon; + String? text; + + ItemNull.fromJson(Map json) { + icon = json['icon']; + text = json['text']; } } diff --git a/lib/models/dynamics/result.dart b/lib/models/dynamics/result.dart index bd980096..4f9b6b28 100644 --- a/lib/models/dynamics/result.dart +++ b/lib/models/dynamics/result.dart @@ -100,6 +100,7 @@ class ItemModulesModel { // 专栏 List? moduleExtend; // opus的tag List? moduleContent; + ModuleBlocked? moduleBlocked; // moduleBottom @@ -137,6 +138,11 @@ class ItemModulesModel { ?.map((i) => ArticleContentModel.fromJson(i)) .toList(); break; + case 'MODULE_TYPE_BLOCKED': + moduleBlocked = i['module_blocked'] == null + ? null + : ModuleBlocked.fromJson(i['module_blocked']); + break; case 'MODULE_TYPE_EXTEND': moduleExtend = (i['module_extend']['items'] as List?) ?.map((i) => ModuleTag.fromJson(i)) @@ -156,6 +162,46 @@ class ItemModulesModel { } } +class ModuleBlocked { + BgImg? bgImg; + int? blockedType; + Button? button; + String? hintMessage; + BgImg? icon; + + ModuleBlocked.fromJson(Map json) { + bgImg = json['bg_img'] == null ? null : BgImg.fromJson(json['bg_img']); + blockedType = json['blocked_type']; + button = json['button'] == null ? null : Button.fromJson(json['button']); + hintMessage = json['hint_message']; + icon = json['icon'] == null ? null : BgImg.fromJson(json['icon']); + } +} + +class Button { + int? handleType; + String? icon; + String? jumpUrl; + String? text; + + Button.fromJson(Map json) { + handleType = json['handle_type']; + icon = json['icon']; + jumpUrl = json['jump_url']; + text = json['text']; + } +} + +class BgImg { + String? imgDark; + String? imgDay; + + BgImg.fromJson(Map json) { + imgDark = json['img_dark']; + imgDay = json['img_day']; + } +} + class Basic { String? commentIdStr; int? commentType; diff --git a/lib/pages/article/controller.dart b/lib/pages/article/controller.dart index 9d8bcb5c..e2568ad8 100644 --- a/lib/pages/article/controller.dart +++ b/lib/pages/article/controller.dart @@ -119,7 +119,7 @@ class ArticleController extends ReplyController { } // stats - Future getArticleInfo() async { + Future getArticleInfo([bool isGetCover = false]) async { final res = await DynamicsHttp.articleInfo(cvId: commentId); if (res['status']) { summary @@ -140,6 +140,9 @@ class ArticleController extends ReplyController { ); return true; } + if (isGetCover) { + SmartDialog.showToast(res['msg']); + } return false; } diff --git a/lib/pages/article/view.dart b/lib/pages/article/view.dart index 0d01eaca..62595b84 100644 --- a/lib/pages/article/view.dart +++ b/lib/pages/article/view.dart @@ -338,29 +338,48 @@ class _ArticlePageState extends State () { if (_articleCtr.isLoaded.value) { late Widget content; - if (_articleCtr.opus == null) { - debugPrint('html page'); - final res = parser.parse(_articleCtr.articleData!.content!); - content = 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 { + if (_articleCtr.opus != null) { debugPrint('json page'); content = OpusContent( opus: _articleCtr.opus!, callback: _getImageCallback, maxWidth: maxWidth, ); + } else if (_articleCtr.opusData?.modules.moduleBlocked != null) { + debugPrint('moduleBlocked'); + final moduleBlocked = + _articleCtr.opusData!.modules.moduleBlocked!; + final width = maxWidth * 0.8; + content = moduleBlockedItem(moduleBlocked, width); + } else if (_articleCtr.articleData?.content != null) { + debugPrint('html page'); + final res = parser.parse(_articleCtr.articleData!.content!); + if (res.body!.children.isEmpty) { + content = SliverToBoxAdapter( + child: htmlRender( + context: context, + html: _articleCtr.articleData!.content!, + maxWidth: maxWidth, + callback: _getImageCallback, + ), + ); + } else { + content = 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 { + content = SliverToBoxAdapter(child: Text('NULL')); } int? pubTime = @@ -615,12 +634,13 @@ class _ArticlePageState extends State ), ), if (_articleCtr.commentType == 12 && - _articleCtr.stats.value != null) + _articleCtr.stats.value != null && + _articleCtr.opusData?.modules.moduleBlocked == null) PopupMenuItem( onTap: () async { try { if (_articleCtr.summary.cover == null) { - if (!await _articleCtr.getArticleInfo()) { + if (!await _articleCtr.getArticleInfo(true)) { return; } } diff --git a/lib/pages/article/widgets/html_render.dart b/lib/pages/article/widgets/html_render.dart index dc8c5454..09684826 100644 --- a/lib/pages/article/widgets/html_render.dart +++ b/lib/pages/article/widgets/html_render.dart @@ -10,120 +10,129 @@ import 'package:html/dom.dart' as dom; Widget htmlRender({ required BuildContext context, - required dom.Element element, + dom.Element? element, + String? html, int? imgCount, List? imgList, required double maxWidth, Function(List, int)? callback, }) { debugPrint('htmlRender'); - return SelectionArea( - child: Html.fromElement( - documentElement: element, - onLinkTap: (String? url, Map buildContext, attributes) {}, - extensions: [ - TagExtension( - tagsToExtend: {'img'}, - builder: (ExtensionContext extensionContext) { - try { - final Map attributes = extensionContext.attributes; - final List key = attributes.keys.toList(); - String imgUrl = key.contains('src') - ? attributes['src'] as String - : attributes['data-src'] as String; - imgUrl = imgUrl.contains('@') ? imgUrl.split('@').first : imgUrl; - final bool isEmote = imgUrl.contains('/emote/'); - final bool isMall = imgUrl.contains('/mall/'); - if (isMall) { - return const SizedBox.shrink(); - } - - String? clazz = attributes['class']; - String? height = RegExp(r'max-height:(\d+)px') - .firstMatch('${attributes['style']}') - ?.group(1); - if (clazz?.contains('cut-off') == true || height != null) { - return CachedNetworkImage( - width: maxWidth, - height: height != null ? double.parse(height) : null, - imageUrl: Utils.thumbnailImgUrl(imgUrl), - fit: BoxFit.contain, - ); - } - return Hero( - tag: imgUrl, - child: GestureDetector( - onTap: () { - if (callback != null) { - callback([imgUrl], 0); - } else { - context.imageView( - imgList: [SourceModel(url: imgUrl)], - ); - } - }, - child: NetworkImgLayer( - width: isEmote ? 22 : maxWidth, - height: isEmote ? 22 : null, - src: imgUrl, - ), - ), - ); - } catch (err) { - debugPrint('错误的HTML: $element'); + final extensions = [ + TagExtension( + tagsToExtend: {'img'}, + builder: (ExtensionContext extensionContext) { + try { + final Map attributes = extensionContext.attributes; + final List key = attributes.keys.toList(); + String imgUrl = key.contains('src') + ? attributes['src'] as String + : attributes['data-src'] as String; + imgUrl = imgUrl.contains('@') ? imgUrl.split('@').first : imgUrl; + final bool isEmote = imgUrl.contains('/emote/'); + final bool isMall = imgUrl.contains('/mall/'); + if (isMall) { return const SizedBox.shrink(); } - }, - ), - ], - style: { - 'html': Style( - fontSize: FontSize(16), - lineHeight: LineHeight.percent(160), - letterSpacing: 0.3, - ), - 'body': Style(margin: Margins.zero, padding: HtmlPaddings.zero), - 'a': Style( - color: Theme.of(context).colorScheme.primary, - textDecoration: TextDecoration.none, - ), - 'br': Style( - lineHeight: LineHeight.percent(-1), - ), - 'p': Style( - margin: Margins.only(bottom: 4), - ), - 'span': Style( - fontSize: FontSize.large, - height: Height(1.8), - ), - 'div': Style(height: Height.auto()), - 'li > p': Style( - display: Display.inline, - ), - 'li': Style( - padding: HtmlPaddings.only(bottom: 4), - textAlign: TextAlign.justify, - ), - 'img': Style(margin: Margins.only(top: 4, bottom: 4)), - 'h1,h2': Style( - fontSize: FontSize.xLarge, - fontWeight: FontWeight.bold, - margin: Margins.only(bottom: 8), - ), - 'h3,h4,h5': Style( - fontSize: FontSize(16), - fontWeight: FontWeight.bold, - margin: Margins.only(bottom: 4), - ), - 'figcaption': Style( - fontSize: FontSize.large, - textAlign: TextAlign.center, - ), - 'strong': Style(fontWeight: FontWeight.bold), - 'figure': Style( - margin: Margins.zero, - ), - }, - )); + + String? clazz = attributes['class']; + String? height = RegExp(r'max-height:(\d+)px') + .firstMatch('${attributes['style']}') + ?.group(1); + if (clazz?.contains('cut-off') == true || height != null) { + return CachedNetworkImage( + width: maxWidth, + height: height != null ? double.parse(height) : null, + imageUrl: Utils.thumbnailImgUrl(imgUrl), + fit: BoxFit.contain, + ); + } + return Hero( + tag: imgUrl, + child: GestureDetector( + onTap: () { + if (callback != null) { + callback([imgUrl], 0); + } else { + context.imageView( + imgList: [SourceModel(url: imgUrl)], + ); + } + }, + child: NetworkImgLayer( + width: isEmote ? 22 : maxWidth, + height: isEmote ? 22 : null, + src: imgUrl, + ), + ), + ); + } catch (err) { + debugPrint('错误的HTML: $element'); + return const SizedBox.shrink(); + } + }, + ), + ]; + final style = { + 'html': Style( + fontSize: FontSize(16), + lineHeight: LineHeight.percent(160), + letterSpacing: 0.3, + ), + 'body': Style(margin: Margins.zero, padding: HtmlPaddings.zero), + 'a': Style( + color: Theme.of(context).colorScheme.primary, + textDecoration: TextDecoration.none, + ), + 'br': Style( + lineHeight: LineHeight.percent(-1), + ), + 'p': Style( + margin: Margins.only(bottom: 4), + ), + 'span': Style( + fontSize: FontSize.large, + height: Height(1.8), + ), + 'div': Style(height: Height.auto()), + 'li > p': Style( + display: Display.inline, + ), + 'li': Style( + padding: HtmlPaddings.only(bottom: 4), + textAlign: TextAlign.justify, + ), + 'img': Style(margin: Margins.only(top: 4, bottom: 4)), + 'h1,h2': Style( + fontSize: FontSize.xLarge, + fontWeight: FontWeight.bold, + margin: Margins.only(bottom: 8), + ), + 'h3,h4,h5': Style( + fontSize: FontSize(16), + fontWeight: FontWeight.bold, + margin: Margins.only(bottom: 4), + ), + 'figcaption': Style( + fontSize: FontSize.large, + textAlign: TextAlign.center, + ), + 'strong': Style(fontWeight: FontWeight.bold), + 'figure': Style( + margin: Margins.zero, + ), + }; + return SelectionArea( + child: element != null + ? Html.fromElement( + documentElement: element, + extensions: extensions, + style: style, + ) + : Html( + data: html, + extensions: extensions, + style: style, + ), + ); } diff --git a/lib/pages/article/widgets/opus_content.dart b/lib/pages/article/widgets/opus_content.dart index 82d464c2..73020a91 100644 --- a/lib/pages/article/widgets/opus_content.dart +++ b/lib/pages/article/widgets/opus_content.dart @@ -4,12 +4,14 @@ import 'package:PiliPlus/common/widgets/interactiveviewer_gallery/interactivevie import 'package:PiliPlus/common/widgets/network_img_layer.dart'; import 'package:PiliPlus/models/dynamics/article_content_model.dart' show ArticleContentModel, Style, Word; +import 'package:PiliPlus/models/dynamics/result.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'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import 'package:re_highlight/languages/all.dart'; import 'package:re_highlight/re_highlight.dart'; @@ -168,7 +170,7 @@ class OpusContent extends StatelessWidget { }).toList(), ), ); - case 6 when (element.linkCard?.card?.ugc != null): + case 6: return Material( shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(8)), @@ -186,32 +188,46 @@ class OpusContent extends StatelessWidget { borderRadius: const BorderRadius.all(Radius.circular(8)), child: Padding( padding: const EdgeInsets.all(8), - child: Row( - children: [ - NetworkImgLayer( - radius: 6, - width: 65 * StyleString.aspectRatio, - height: 65, - src: element.linkCard!.card!.ugc!.cover, - ), - const SizedBox(width: 10), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(element.linkCard!.card!.ugc!.title!), - Text( - element.linkCard!.card!.ugc!.descSecond!, - style: TextStyle( - fontSize: 13, - color: colorScheme.outline, - ), + child: switch (element.linkCard?.card?.type) { + 'LINK_CARD_TYPE_UGC' => Row( + children: [ + NetworkImgLayer( + radius: 6, + width: 65 * StyleString.aspectRatio, + height: 65, + src: element.linkCard!.card!.ugc!.cover, + ), + const SizedBox(width: 10), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(element.linkCard!.card!.ugc!.title!), + Text( + element.linkCard!.card!.ugc!.descSecond!, + style: TextStyle( + fontSize: 13, + color: colorScheme.outline, + ), + ), + ], ), - ], - ), + ), + ], ), - ], - ), + 'LINK_CARD_TYPE_ITEM_NULL' => Row( + children: [ + if (element.linkCard?.card?.itemNull?.icon + ?.isNullOrEmpty == + true) + Icon(Icons.info, size: 20), + Text(' ${element.linkCard?.card?.itemNull?.text}'), + ], + ), + _ => throw UnimplementedError( + '\nparaType: ${element.paraType},\ncard type: ${element.linkCard?.card?.type}', + ), + }, ), ), ); @@ -269,3 +285,80 @@ class OpusContent extends StatelessWidget { ); } } + +Widget moduleBlockedItem(ModuleBlocked moduleBlocked, double width) { + return SliverToBoxAdapter( + child: Stack( + clipBehavior: Clip.none, + children: [ + if (moduleBlocked.bgImg != null) + CachedNetworkImage( + width: width, + fit: BoxFit.cover, + imageUrl: Utils.thumbnailImgUrl( + Get.isDarkMode + ? moduleBlocked.bgImg!.imgDark + : moduleBlocked.bgImg!.imgDay, + ), + ), + Container( + width: width, + height: width, + padding: const EdgeInsets.all(12), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (moduleBlocked.icon != null) + CachedNetworkImage( + width: width / 7, + fit: BoxFit.contain, + imageUrl: Utils.thumbnailImgUrl( + Get.isDarkMode + ? moduleBlocked.icon!.imgDark + : moduleBlocked.icon!.imgDay, + ), + ), + if (moduleBlocked.hintMessage != null) ...[ + const SizedBox(height: 5), + Text( + moduleBlocked.hintMessage!, + textAlign: TextAlign.center, + ), + ], + if (moduleBlocked.button != null) ...[ + const SizedBox(height: 5), + FilledButton.tonal( + style: FilledButton.styleFrom( + visualDensity: const VisualDensity(vertical: -2.5), + backgroundColor: Get.isDarkMode + ? const Color(0xFF8F0030) + : const Color(0xFFFF6699), + foregroundColor: Colors.white, + ), + onPressed: () { + if (moduleBlocked.button!.jumpUrl != null) { + PiliScheme.routePushFromUrl( + moduleBlocked.button!.jumpUrl!); + } + }, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (moduleBlocked.button!.icon != null) + CachedNetworkImage( + height: 16, + color: Colors.white, + imageUrl: moduleBlocked.button!.icon!, + ), + Text(moduleBlocked.button!.text ?? ''), + ], + ), + ), + ], + ], + ), + ), + ], + ), + ); +}