mod: opus: show itemnull, moduleblocked

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-04-29 10:23:16 +08:00
parent 30bad3a066
commit b876840d08
6 changed files with 341 additions and 153 deletions

View File

@@ -173,11 +173,28 @@ class Card {
String? oid; String? oid;
String? type; String? type;
Ugc? ugc; Ugc? ugc;
ItemNull? itemNull;
Card.fromJson(Map<String, dynamic> json) { Card.fromJson(Map<String, dynamic> json) {
oid = json['oid']; oid = json['oid'];
type = json['type']; type = json['type'];
ugc = json['ugc'] == null ? null : Ugc.fromJson(json['ugc']); 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<String, dynamic> json) {
icon = json['icon'];
text = json['text'];
} }
} }

View File

@@ -100,6 +100,7 @@ class ItemModulesModel {
// 专栏 // 专栏
List<ModuleTag>? moduleExtend; // opus的tag List<ModuleTag>? moduleExtend; // opus的tag
List<ArticleContentModel>? moduleContent; List<ArticleContentModel>? moduleContent;
ModuleBlocked? moduleBlocked;
// moduleBottom // moduleBottom
@@ -137,6 +138,11 @@ class ItemModulesModel {
?.map((i) => ArticleContentModel.fromJson(i)) ?.map((i) => ArticleContentModel.fromJson(i))
.toList(); .toList();
break; break;
case 'MODULE_TYPE_BLOCKED':
moduleBlocked = i['module_blocked'] == null
? null
: ModuleBlocked.fromJson(i['module_blocked']);
break;
case 'MODULE_TYPE_EXTEND': case 'MODULE_TYPE_EXTEND':
moduleExtend = (i['module_extend']['items'] as List?) moduleExtend = (i['module_extend']['items'] as List?)
?.map((i) => ModuleTag.fromJson(i)) ?.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<String, dynamic> 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<String, dynamic> json) {
handleType = json['handle_type'];
icon = json['icon'];
jumpUrl = json['jump_url'];
text = json['text'];
}
}
class BgImg {
String? imgDark;
String? imgDay;
BgImg.fromJson(Map<String, dynamic> json) {
imgDark = json['img_dark'];
imgDay = json['img_day'];
}
}
class Basic { class Basic {
String? commentIdStr; String? commentIdStr;
int? commentType; int? commentType;

View File

@@ -119,7 +119,7 @@ class ArticleController extends ReplyController<MainListReply> {
} }
// stats // stats
Future<bool> getArticleInfo() async { Future<bool> getArticleInfo([bool isGetCover = false]) async {
final res = await DynamicsHttp.articleInfo(cvId: commentId); final res = await DynamicsHttp.articleInfo(cvId: commentId);
if (res['status']) { if (res['status']) {
summary summary
@@ -140,6 +140,9 @@ class ArticleController extends ReplyController<MainListReply> {
); );
return true; return true;
} }
if (isGetCover) {
SmartDialog.showToast(res['msg']);
}
return false; return false;
} }

View File

@@ -338,29 +338,48 @@ class _ArticlePageState extends State<ArticlePage>
() { () {
if (_articleCtr.isLoaded.value) { if (_articleCtr.isLoaded.value) {
late Widget content; late Widget content;
if (_articleCtr.opus == null) { 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 {
debugPrint('json page'); debugPrint('json page');
content = OpusContent( content = OpusContent(
opus: _articleCtr.opus!, opus: _articleCtr.opus!,
callback: _getImageCallback, callback: _getImageCallback,
maxWidth: maxWidth, 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 = int? pubTime =
@@ -615,12 +634,13 @@ class _ArticlePageState extends State<ArticlePage>
), ),
), ),
if (_articleCtr.commentType == 12 && if (_articleCtr.commentType == 12 &&
_articleCtr.stats.value != null) _articleCtr.stats.value != null &&
_articleCtr.opusData?.modules.moduleBlocked == null)
PopupMenuItem( PopupMenuItem(
onTap: () async { onTap: () async {
try { try {
if (_articleCtr.summary.cover == null) { if (_articleCtr.summary.cover == null) {
if (!await _articleCtr.getArticleInfo()) { if (!await _articleCtr.getArticleInfo(true)) {
return; return;
} }
} }

View File

@@ -10,120 +10,129 @@ import 'package:html/dom.dart' as dom;
Widget htmlRender({ Widget htmlRender({
required BuildContext context, required BuildContext context,
required dom.Element element, dom.Element? element,
String? html,
int? imgCount, int? imgCount,
List<String>? imgList, List<String>? imgList,
required double maxWidth, required double maxWidth,
Function(List<String>, int)? callback, Function(List<String>, int)? callback,
}) { }) {
debugPrint('htmlRender'); debugPrint('htmlRender');
return SelectionArea( final extensions = [
child: Html.fromElement( TagExtension(
documentElement: element, tagsToExtend: <String>{'img'},
onLinkTap: (String? url, Map<String, String> buildContext, attributes) {}, builder: (ExtensionContext extensionContext) {
extensions: [ try {
TagExtension( final Map<String, dynamic> attributes = extensionContext.attributes;
tagsToExtend: <String>{'img'}, final List<dynamic> key = attributes.keys.toList();
builder: (ExtensionContext extensionContext) { String imgUrl = key.contains('src')
try { ? attributes['src'] as String
final Map<String, dynamic> attributes = extensionContext.attributes; : attributes['data-src'] as String;
final List<dynamic> key = attributes.keys.toList(); imgUrl = imgUrl.contains('@') ? imgUrl.split('@').first : imgUrl;
String imgUrl = key.contains('src') final bool isEmote = imgUrl.contains('/emote/');
? attributes['src'] as String final bool isMall = imgUrl.contains('/mall/');
: attributes['data-src'] as String; if (isMall) {
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');
return const SizedBox.shrink(); return const SizedBox.shrink();
} }
},
), String? clazz = attributes['class'];
], String? height = RegExp(r'max-height:(\d+)px')
style: { .firstMatch('${attributes['style']}')
'html': Style( ?.group(1);
fontSize: FontSize(16), if (clazz?.contains('cut-off') == true || height != null) {
lineHeight: LineHeight.percent(160), return CachedNetworkImage(
letterSpacing: 0.3, width: maxWidth,
), height: height != null ? double.parse(height) : null,
'body': Style(margin: Margins.zero, padding: HtmlPaddings.zero), imageUrl: Utils.thumbnailImgUrl(imgUrl),
'a': Style( fit: BoxFit.contain,
color: Theme.of(context).colorScheme.primary, );
textDecoration: TextDecoration.none, }
), return Hero(
'br': Style( tag: imgUrl,
lineHeight: LineHeight.percent(-1), child: GestureDetector(
), onTap: () {
'p': Style( if (callback != null) {
margin: Margins.only(bottom: 4), callback([imgUrl], 0);
), } else {
'span': Style( context.imageView(
fontSize: FontSize.large, imgList: [SourceModel(url: imgUrl)],
height: Height(1.8), );
), }
'div': Style(height: Height.auto()), },
'li > p': Style( child: NetworkImgLayer(
display: Display.inline, width: isEmote ? 22 : maxWidth,
), height: isEmote ? 22 : null,
'li': Style( src: imgUrl,
padding: HtmlPaddings.only(bottom: 4), ),
textAlign: TextAlign.justify, ),
), );
'img': Style(margin: Margins.only(top: 4, bottom: 4)), } catch (err) {
'h1,h2': Style( debugPrint('错误的HTML: $element');
fontSize: FontSize.xLarge, return const SizedBox.shrink();
fontWeight: FontWeight.bold, }
margin: Margins.only(bottom: 8), },
), ),
'h3,h4,h5': Style( ];
fontSize: FontSize(16), final style = {
fontWeight: FontWeight.bold, 'html': Style(
margin: Margins.only(bottom: 4), fontSize: FontSize(16),
), lineHeight: LineHeight.percent(160),
'figcaption': Style( letterSpacing: 0.3,
fontSize: FontSize.large, ),
textAlign: TextAlign.center, 'body': Style(margin: Margins.zero, padding: HtmlPaddings.zero),
), 'a': Style(
'strong': Style(fontWeight: FontWeight.bold), color: Theme.of(context).colorScheme.primary,
'figure': Style( textDecoration: TextDecoration.none,
margin: Margins.zero, ),
), '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,
),
);
} }

View File

@@ -4,12 +4,14 @@ import 'package:PiliPlus/common/widgets/interactiveviewer_gallery/interactivevie
import 'package:PiliPlus/common/widgets/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/network_img_layer.dart';
import 'package:PiliPlus/models/dynamics/article_content_model.dart' import 'package:PiliPlus/models/dynamics/article_content_model.dart'
show ArticleContentModel, Style, Word; show ArticleContentModel, Style, Word;
import 'package:PiliPlus/models/dynamics/result.dart';
import 'package:PiliPlus/utils/app_scheme.dart'; import 'package:PiliPlus/utils/app_scheme.dart';
import 'package:PiliPlus/utils/extension.dart'; import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/utils.dart'; import 'package:PiliPlus/utils/utils.dart';
import 'package:cached_network_image/cached_network_image.dart'; import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
import 'package:re_highlight/languages/all.dart'; import 'package:re_highlight/languages/all.dart';
import 'package:re_highlight/re_highlight.dart'; import 'package:re_highlight/re_highlight.dart';
@@ -168,7 +170,7 @@ class OpusContent extends StatelessWidget {
}).toList(), }).toList(),
), ),
); );
case 6 when (element.linkCard?.card?.ugc != null): case 6:
return Material( return Material(
shape: const RoundedRectangleBorder( shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8)), borderRadius: BorderRadius.all(Radius.circular(8)),
@@ -186,32 +188,46 @@ class OpusContent extends StatelessWidget {
borderRadius: const BorderRadius.all(Radius.circular(8)), borderRadius: const BorderRadius.all(Radius.circular(8)),
child: Padding( child: Padding(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
child: Row( child: switch (element.linkCard?.card?.type) {
children: [ 'LINK_CARD_TYPE_UGC' => Row(
NetworkImgLayer( children: [
radius: 6, NetworkImgLayer(
width: 65 * StyleString.aspectRatio, radius: 6,
height: 65, width: 65 * StyleString.aspectRatio,
src: element.linkCard!.card!.ugc!.cover, height: 65,
), src: element.linkCard!.card!.ugc!.cover,
const SizedBox(width: 10), ),
Expanded( const SizedBox(width: 10),
child: Column( Expanded(
crossAxisAlignment: CrossAxisAlignment.start, child: Column(
children: [ crossAxisAlignment: CrossAxisAlignment.start,
Text(element.linkCard!.card!.ugc!.title!), children: [
Text( Text(element.linkCard!.card!.ugc!.title!),
element.linkCard!.card!.ugc!.descSecond!, Text(
style: TextStyle( element.linkCard!.card!.ugc!.descSecond!,
fontSize: 13, style: TextStyle(
color: colorScheme.outline, 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 ?? ''),
],
),
),
],
],
),
),
],
),
);
}