From 4d7d9abc60d9bd2dca4f9cf03c6488b1e8a571e4 Mon Sep 17 00:00:00 2001 From: bggRGjQaUbCoE Date: Thu, 24 Apr 2025 13:48:57 +0800 Subject: [PATCH] opt: html page Signed-off-by: bggRGjQaUbCoE --- lib/common/widgets/html_render.dart | 15 +- lib/pages/common/reply_controller.dart | 6 - lib/pages/html/view.dart | 1225 ++++++++++++------------ 3 files changed, 617 insertions(+), 629 deletions(-) diff --git a/lib/common/widgets/html_render.dart b/lib/common/widgets/html_render.dart index 86c3b666..bac8f6a5 100644 --- a/lib/common/widgets/html_render.dart +++ b/lib/common/widgets/html_render.dart @@ -6,19 +6,20 @@ 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 'package:html/dom.dart' as dom; Widget htmlRender({ required BuildContext context, - String? htmlContent, + required dom.Element element, int? imgCount, List? imgList, - required double constrainedWidth, + required double maxWidth, Function(List, int)? callback, }) { debugPrint('htmlRender'); return SelectionArea( - child: Html( - data: htmlContent, + child: Html.fromElement( + documentElement: element, onLinkTap: (String? url, Map buildContext, attributes) {}, extensions: [ TagExtension( @@ -43,7 +44,7 @@ Widget htmlRender({ ?.group(1); if (clazz?.contains('cut-off') == true || height != null) { return CachedNetworkImage( - width: constrainedWidth, + width: maxWidth, height: height != null ? double.parse(height) : null, imageUrl: Utils.thumbnailImgUrl(imgUrl), fit: BoxFit.contain, @@ -62,7 +63,7 @@ Widget htmlRender({ } }, child: NetworkImgLayer( - width: isEmote ? 22 : constrainedWidth, + width: isEmote ? 22 : maxWidth, height: isEmote ? 22 : 200, src: imgUrl, ignoreHeight: !isEmote, @@ -70,7 +71,7 @@ Widget htmlRender({ ), ); } catch (err) { - return const SizedBox(); + return const SizedBox.shrink(); } }, ), diff --git a/lib/pages/common/reply_controller.dart b/lib/pages/common/reply_controller.dart index 7a916d2b..87a02532 100644 --- a/lib/pages/common/reply_controller.dart +++ b/lib/pages/common/reply_controller.dart @@ -500,10 +500,4 @@ https://api.bilibili.com/x/v2/reply/reply?oid=$oid&pn=1&ps=20&root=${rpid ?? rep SmartDialog.showToast(res['msg']); } } - - @override - Future onReload() { - scrollController.jumpToTop(); - return super.onReload(); - } } diff --git a/lib/pages/html/view.dart b/lib/pages/html/view.dart index 7f5f3a75..da164af3 100644 --- a/lib/pages/html/view.dart +++ b/lib/pages/html/view.dart @@ -25,6 +25,7 @@ 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'; +import 'package:html/parser.dart' as parser; import '../../utils/grid.dart'; import 'controller.dart'; @@ -229,7 +230,303 @@ class _HtmlRenderPageState extends State Widget build(BuildContext context) { return Scaffold( resizeToAvoidBottomInset: false, - appBar: AppBar( + appBar: _buildAppBar, + body: Stack( + children: [ + SafeArea( + top: false, + bottom: false, + child: Builder( + builder: (context) { + final isPortrait = context.orientation == Orientation.portrait; + double padding = + max(context.width / 2 - Grid.smallCardWidth, 0); + if (isPortrait) { + return LayoutBuilder(builder: (context, constraints) { + final maxWidth = constraints.maxWidth - 2 * padding - 24; + return Padding( + padding: EdgeInsets.symmetric(horizontal: padding), + child: CustomScrollView( + controller: _htmlRenderCtr.scrollController, + physics: const AlwaysScrollableScrollPhysics(), + slivers: [ + _buildHeader, + _buildContent(maxWidth), + SliverToBoxAdapter( + child: Divider( + thickness: 8, + color: Theme.of(context) + .dividerColor + .withOpacity(0.05), + ), + ), + _buildReplyHeader, + Obx(() => _buildReplyList( + _htmlRenderCtr.loadingState.value)), + ], + ), + ); + }); + } else { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + flex: _ratio[0].toInt(), + child: LayoutBuilder( + builder: (context, constraints) { + final maxWidth = + constraints.maxWidth - padding / 4 - 24; + return CustomScrollView( + controller: _htmlRenderCtr.scrollController, + physics: const AlwaysScrollableScrollPhysics(), + slivers: [ + SliverPadding( + padding: EdgeInsets.only(left: padding / 4), + sliver: _buildHeader, + ), + SliverPadding( + padding: EdgeInsets.only( + left: padding / 4, + bottom: + MediaQuery.paddingOf(context).bottom + + 80, + ), + sliver: _buildContent(maxWidth), + ), + ], + ); + }, + ), + ), + VerticalDivider( + thickness: 8, + color: Theme.of(context).dividerColor.withOpacity(0.05), + ), + Expanded( + flex: _ratio[1].toInt(), + child: Scaffold( + key: _key, + backgroundColor: Colors.transparent, + body: refreshIndicator( + onRefresh: () async { + await _htmlRenderCtr.onRefresh(); + }, + child: Padding( + padding: EdgeInsets.only(right: padding / 4), + child: CustomScrollView( + controller: _htmlRenderCtr.scrollController, + physics: const AlwaysScrollableScrollPhysics(), + slivers: [ + _buildReplyHeader, + Obx(() => _buildReplyList( + _htmlRenderCtr.loadingState.value)), + ], + ), + ), + ), + ), + ), + ], + ); + } + }, + ), + ), + _buildBottom, + ], + ), + ); + } + + Widget _buildContent(double maxWidth) => SliverPadding( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + sliver: Obx( + () { + if (_htmlRenderCtr.loaded.value) { + if (_htmlRenderCtr.response['isJsonContent'] == true) { + return articleContent( + context: context, + list: _htmlRenderCtr.response['content'], + 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(); + }, + ), + ); + + Widget _buildReplyList(LoadingState?> loadingState) { + return switch (loadingState) { + Loading() => SliverList.builder( + itemCount: 5, + itemBuilder: (context, index) { + return const VideoReplySkeleton(); + }, + ), + Success() => loadingState.response?.isNotEmpty == true + ? SliverList.builder( + itemCount: loadingState.response!.length + 1, + itemBuilder: (context, index) { + if (index == loadingState.response!.length) { + _htmlRenderCtr.onLoadMore(); + return Container( + alignment: Alignment.center, + margin: EdgeInsets.only( + bottom: MediaQuery.of(context).padding.bottom), + height: 125, + child: Text( + _htmlRenderCtr.isEnd.not + ? '加载中...' + : loadingState.response!.isEmpty + ? '还没有评论' + : '没有更多了', + style: TextStyle( + fontSize: 12, + color: Theme.of(context).colorScheme.outline, + ), + ), + ); + } else { + return ReplyItemGrpc( + replyItem: loadingState.response![index], + replyLevel: '1', + replyReply: (replyItem, id) => + replyReply(context, replyItem, id), + onReply: () { + _htmlRenderCtr.onReply( + context, + replyItem: loadingState.response![index], + index: index, + ); + }, + onDelete: (subIndex) => + _htmlRenderCtr.onRemove(index, subIndex), + upMid: _htmlRenderCtr.upMid, + callback: _getImageCallback, + onCheckReply: (item) => + _htmlRenderCtr.onCheckReply(context, item), + onToggleTop: (isUpTop, rpid) => _htmlRenderCtr.onToggleTop( + index, + _htmlRenderCtr.oid, + _htmlRenderCtr.type, + isUpTop, + rpid, + ), + ); + } + }, + ) + : HttpError( + onReload: _htmlRenderCtr.onReload, + ), + Error() => HttpError( + errMsg: loadingState.errMsg, + onReload: _htmlRenderCtr.onReload, + ), + LoadingState() => throw UnimplementedError(), + }; + } + + Widget get _buildReplyHeader { + return SliverToBoxAdapter( + child: Container( + height: 45, + padding: const EdgeInsets.only(left: 12, right: 6), + child: Row( + children: [ + const Text('回复'), + const Spacer(), + SizedBox( + height: 35, + child: TextButton.icon( + onPressed: () => _htmlRenderCtr.queryBySort(), + icon: const Icon(Icons.sort, size: 16), + label: Obx( + () => Text( + _htmlRenderCtr.sortType.value.label, + style: const TextStyle(fontSize: 13), + ), + ), + ), + ) + ], + ), + ), + ); + } + + 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), actions: [ const SizedBox(width: 4), @@ -340,640 +637,336 @@ class _HtmlRenderPageState extends State ), const SizedBox(width: 6) ], - ), - body: SafeArea( - top: false, - bottom: false, - child: Stack( - children: [ - OrientationBuilder( - builder: (context, orientation) { - double padding = - max(context.width / 2 - Grid.smallCardWidth, 0); - return Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - flex: _ratio[0].toInt(), - child: CustomScrollView( - controller: _htmlRenderCtr.scrollController, - physics: const AlwaysScrollableScrollPhysics(), - slivers: [ - SliverPadding( - padding: orientation == Orientation.portrait - ? EdgeInsets.symmetric(horizontal: padding) - : EdgeInsets.only(left: padding / 4), - sliver: SliverToBoxAdapter( - child: Obx( - () => _htmlRenderCtr.loaded.value - ? _buildHeader - : const SizedBox(), - ), - ), - ), - SliverPadding( - padding: orientation == Orientation.portrait - ? EdgeInsets.symmetric(horizontal: padding) - : EdgeInsets.only( - left: padding / 4, - bottom: - MediaQuery.paddingOf(context).bottom + - 80, - ), - sliver: _buildContent, - ), - if (orientation == Orientation.portrait) ...[ - SliverPadding( - padding: - EdgeInsets.symmetric(horizontal: padding), - sliver: SliverToBoxAdapter( - child: Divider( - thickness: 8, - color: Theme.of(context) - .dividerColor - .withOpacity(0.05), - ), - ), - ), - SliverPadding( - padding: - EdgeInsets.symmetric(horizontal: padding), - sliver: SliverToBoxAdapter(child: replyHeader()), - ), - SliverPadding( - padding: - EdgeInsets.symmetric(horizontal: padding), - sliver: Obx( - () => replyList( - _htmlRenderCtr.loadingState.value), - ), - ), - ], - ], - ), - ), - if (orientation == Orientation.landscape) ...[ - VerticalDivider( - thickness: 8, - color: Theme.of(context).dividerColor.withOpacity(0.05), - ), - Expanded( - flex: _ratio[1].toInt(), - child: Scaffold( - key: _key, - backgroundColor: Colors.transparent, - body: refreshIndicator( - onRefresh: () async { - await _htmlRenderCtr.onRefresh(); - }, - child: CustomScrollView( - controller: _htmlRenderCtr.scrollController, - physics: const AlwaysScrollableScrollPhysics(), - slivers: [ - SliverPadding( - padding: EdgeInsets.only(right: padding / 4), - sliver: SliverToBoxAdapter( - child: replyHeader(), - ), - ), - SliverPadding( - padding: EdgeInsets.only(right: padding / 4), - sliver: Obx( - () => replyList( - _htmlRenderCtr.loadingState.value), - ), - ), - ], - ), - ), + ); + + Widget get _buildBottom => Positioned( + left: 0, + bottom: 0, + right: 0, + child: SlideTransition( + position: Tween( + begin: const Offset(0, 1), + end: const Offset(0, 0), + ).animate(CurvedAnimation( + parent: fabAnimationCtr, + curve: Curves.easeInOut, + )), + child: Builder( + builder: (context) { + Widget button() => FloatingActionButton( + heroTag: null, + onPressed: () { + feedBack(); + _htmlRenderCtr.onReply( + context, + oid: _htmlRenderCtr.oid.value, + replyType: ReplyType.values[type], + ); + }, + tooltip: '评论动态', + child: const Icon(Icons.reply), + ); + return _htmlRenderCtr.showDynActionBar.not + ? Align( + alignment: Alignment.bottomRight, + child: Padding( + padding: EdgeInsets.only( + right: 14, + bottom: MediaQuery.of(context).padding.bottom + 14, ), + child: button(), ), - ], - ], - ); - }, - ), - Positioned( - left: 0, - right: 0, - bottom: 0, - child: SlideTransition( - position: Tween( - begin: const Offset(0, 1), - end: const Offset(0, 0), - ).animate(CurvedAnimation( - parent: fabAnimationCtr, - curve: Curves.easeInOut, - )), - child: Builder( - builder: (context) { - Widget button() => FloatingActionButton( - heroTag: null, - onPressed: () { - feedBack(); - _htmlRenderCtr.onReply( - context, - oid: _htmlRenderCtr.oid.value, - replyType: ReplyType.values[type], - ); - }, - tooltip: '评论动态', - child: const Icon(Icons.reply), - ); - return _htmlRenderCtr.showDynActionBar.not - ? Align( - alignment: Alignment.bottomRight, - child: Padding( - padding: EdgeInsets.only( - right: 14, - bottom: - MediaQuery.of(context).padding.bottom + 14, - ), - child: button(), + ) + : Obx( + () => Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Padding( + padding: EdgeInsets.only( + right: 14, + bottom: 14 + + (_htmlRenderCtr.item.value.idStr != null + ? 0 + : MediaQuery.of(context).padding.bottom), ), - ) - : Obx( - () => Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Padding( - padding: EdgeInsets.only( - right: 14, - bottom: 14 + - (_htmlRenderCtr.item.value.idStr != null - ? 0 - : MediaQuery.of(context) - .padding - .bottom), + child: button(), + ), + _htmlRenderCtr.item.value.idStr != null + ? Container( + decoration: BoxDecoration( + color: + Theme.of(context).colorScheme.surface, + border: Border( + top: BorderSide( + color: Theme.of(context) + .colorScheme + .outline + .withOpacity(0.08), + ), + ), ), - child: button(), - ), - _htmlRenderCtr.item.value.idStr != null - ? Container( - decoration: BoxDecoration( - color: Theme.of(context) - .colorScheme - .surface, - border: Border( - top: BorderSide( + padding: EdgeInsets.only( + bottom: + MediaQuery.paddingOf(context).bottom), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceAround, + children: [ + Expanded( + child: Builder( + builder: (btnContext) => + TextButton.icon( + onPressed: () { + showModalBottomSheet( + context: context, + isScrollControlled: true, + useSafeArea: true, + builder: (context) => + RepostPanel( + item: + _htmlRenderCtr.item.value, + callback: () { + int count = int.tryParse( + _htmlRenderCtr + .item + .value + .modules + ?.moduleStat + ?.forward + ?.count ?? + '0') ?? + 0; + _htmlRenderCtr + .item + .value + .modules + ?.moduleStat + ?.forward! + .count = + (count + 1).toString(); + if (btnContext.mounted) { + (btnContext as Element?) + ?.markNeedsBuild(); + } + }, + ), + ); + }, + icon: Icon( + FontAwesomeIcons.shareFromSquare, + size: 16, color: Theme.of(context) .colorScheme - .outline - .withOpacity(0.08), + .outline, + semanticLabel: "转发", + ), + style: TextButton.styleFrom( + padding: + const EdgeInsets.fromLTRB( + 15, 0, 15, 0), + foregroundColor: Theme.of(context) + .colorScheme + .outline, + ), + label: Text( + _htmlRenderCtr + .item + .value + .modules + ?.moduleStat + ?.forward! + .count != + null + ? Utils.numFormat( + _htmlRenderCtr + .item + .value + .modules + ?.moduleStat + ?.forward! + .count) + : '转发', ), ), ), - padding: EdgeInsets.only( - bottom: - MediaQuery.paddingOf(context) - .bottom), - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceAround, - children: [ - Expanded( - child: Builder( - builder: (btnContext) => - TextButton.icon( - onPressed: () { - showModalBottomSheet( - context: context, - isScrollControlled: true, - useSafeArea: true, - builder: (context) => - RepostPanel( - item: _htmlRenderCtr - .item.value, - callback: () { - int count = int.tryParse( - _htmlRenderCtr - .item - .value - .modules - ?.moduleStat - ?.forward - ?.count ?? - '0') ?? - 0; - _htmlRenderCtr - .item - .value - .modules - ?.moduleStat - ?.forward! - .count = - (count + 1) - .toString(); - if (btnContext - .mounted) { - (btnContext - as Element?) - ?.markNeedsBuild(); - } - }, - ), - ); - }, - icon: Icon( - FontAwesomeIcons - .shareFromSquare, - size: 16, - color: Theme.of(context) - .colorScheme - .outline, - semanticLabel: "转发", - ), - style: TextButton.styleFrom( - padding: const EdgeInsets - .fromLTRB(15, 0, 15, 0), - foregroundColor: - Theme.of(context) - .colorScheme - .outline, - ), - label: Text( - _htmlRenderCtr - .item - .value - .modules - ?.moduleStat - ?.forward! - .count != - null - ? Utils.numFormat( - _htmlRenderCtr - .item - .value - .modules - ?.moduleStat - ?.forward! - .count) - : '转发', - ), - ), - ), - ), - Expanded( - child: TextButton.icon( - onPressed: () { - Utils.shareText( - '${HttpString.dynamicShareBaseUrl}/${_htmlRenderCtr.item.value.idStr}'); - }, - icon: Icon( - FontAwesomeIcons.shareNodes, - size: 16, - color: Theme.of(context) + ), + Expanded( + child: TextButton.icon( + onPressed: () { + Utils.shareText( + '${HttpString.dynamicShareBaseUrl}/${_htmlRenderCtr.item.value.idStr}'); + }, + icon: Icon( + FontAwesomeIcons.shareNodes, + size: 16, + color: Theme.of(context) + .colorScheme + .outline, + semanticLabel: "分享", + ), + style: TextButton.styleFrom( + padding: const EdgeInsets.fromLTRB( + 15, 0, 15, 0), + foregroundColor: Theme.of(context) + .colorScheme + .outline, + ), + label: const Text('分享'), + ), + ), + if (_htmlRenderCtr.favStat['status']) + Expanded( + child: TextButton.icon( + onPressed: () { + _htmlRenderCtr.onFav(); + }, + icon: Icon( + _htmlRenderCtr.favStat['isFav'] == + true + ? FontAwesomeIcons.solidStar + : FontAwesomeIcons.star, + size: 16, + color: _htmlRenderCtr + .favStat['isFav'] == + true + ? Theme.of(context) + .colorScheme + .primary + : Theme.of(context) .colorScheme .outline, - semanticLabel: "分享", - ), - style: TextButton.styleFrom( - padding: - const EdgeInsets.fromLTRB( - 15, 0, 15, 0), - foregroundColor: - Theme.of(context) + semanticLabel: "收藏", + ), + style: TextButton.styleFrom( + padding: + const EdgeInsets.fromLTRB( + 15, 0, 15, 0), + foregroundColor: Theme.of(context) + .colorScheme + .outline, + ), + label: Text(_htmlRenderCtr + .favStat['favNum'] + .toString()), + ), + ), + Expanded( + child: Builder( + builder: (context) => TextButton.icon( + onPressed: () => + RequestUtils.onLikeDynamic( + _htmlRenderCtr.item.value, + () { + if (context.mounted) { + (context as Element?) + ?.markNeedsBuild(); + } + }, + ), + icon: Icon( + _htmlRenderCtr + .item + .value + .modules + ?.moduleStat + ?.like + ?.status == + true + ? FontAwesomeIcons + .solidThumbsUp + : FontAwesomeIcons.thumbsUp, + size: 16, + color: _htmlRenderCtr + .item + .value + .modules + ?.moduleStat + ?.like + ?.status == + true + ? Theme.of(context) + .colorScheme + .primary + : Theme.of(context) + .colorScheme + .outline, + semanticLabel: _htmlRenderCtr + .item + .value + .modules + ?.moduleStat + ?.like + ?.status == + true + ? "已赞" + : "点赞", + ), + style: TextButton.styleFrom( + padding: + const EdgeInsets.fromLTRB( + 15, 0, 15, 0), + foregroundColor: Theme.of(context) + .colorScheme + .outline, + ), + label: AnimatedSwitcher( + duration: const Duration( + milliseconds: 400), + transitionBuilder: (Widget child, + Animation animation) { + return ScaleTransition( + scale: animation, + child: child); + }, + child: Text( + _htmlRenderCtr + .item + .value + .modules + ?.moduleStat + ?.like + ?.count != + null + ? Utils.numFormat( + _htmlRenderCtr + .item + .value + .modules! + .moduleStat! + .like! + .count) + : '点赞', + style: TextStyle( + color: _htmlRenderCtr + .item + .value + .modules + ?.moduleStat + ?.like + ?.status == + true + ? Theme.of(context) + .colorScheme + .primary + : Theme.of(context) .colorScheme .outline, ), - label: const Text('分享'), ), ), - if (_htmlRenderCtr - .favStat['status']) - Expanded( - child: TextButton.icon( - onPressed: () { - _htmlRenderCtr.onFav(); - }, - icon: Icon( - _htmlRenderCtr.favStat[ - 'isFav'] == - true - ? FontAwesomeIcons - .solidStar - : FontAwesomeIcons.star, - size: 16, - color: - _htmlRenderCtr.favStat[ - 'isFav'] == - true - ? Theme.of(context) - .colorScheme - .primary - : Theme.of(context) - .colorScheme - .outline, - semanticLabel: "收藏", - ), - style: TextButton.styleFrom( - padding: const EdgeInsets - .fromLTRB(15, 0, 15, 0), - foregroundColor: - Theme.of(context) - .colorScheme - .outline, - ), - label: Text(_htmlRenderCtr - .favStat['favNum'] - .toString()), - ), - ), - Expanded( - child: Builder( - builder: (context) => - TextButton.icon( - onPressed: () => RequestUtils - .onLikeDynamic( - _htmlRenderCtr.item.value, - () { - if (context.mounted) { - (context as Element?) - ?.markNeedsBuild(); - } - }, - ), - icon: Icon( - _htmlRenderCtr - .item - .value - .modules - ?.moduleStat - ?.like - ?.status == - true - ? FontAwesomeIcons - .solidThumbsUp - : FontAwesomeIcons - .thumbsUp, - size: 16, - color: _htmlRenderCtr - .item - .value - .modules - ?.moduleStat - ?.like - ?.status == - true - ? Theme.of(context) - .colorScheme - .primary - : Theme.of(context) - .colorScheme - .outline, - semanticLabel: - _htmlRenderCtr - .item - .value - .modules - ?.moduleStat - ?.like - ?.status == - true - ? "已赞" - : "点赞", - ), - style: TextButton.styleFrom( - padding: const EdgeInsets - .fromLTRB(15, 0, 15, 0), - foregroundColor: - Theme.of(context) - .colorScheme - .outline, - ), - label: AnimatedSwitcher( - duration: const Duration( - milliseconds: 400), - transitionBuilder: - (Widget child, - Animation - animation) { - return ScaleTransition( - scale: animation, - child: child); - }, - child: Text( - _htmlRenderCtr - .item - .value - .modules - ?.moduleStat - ?.like - ?.count != - null - ? Utils.numFormat( - _htmlRenderCtr - .item - .value - .modules! - .moduleStat! - .like! - .count) - : '点赞', - style: TextStyle( - color: _htmlRenderCtr - .item - .value - .modules - ?.moduleStat - ?.like - ?.status == - true - ? Theme.of(context) - .colorScheme - .primary - : Theme.of(context) - .colorScheme - .outline, - ), - ), - ), - ), - ), - ), - ], + ), ), - ) - : const SizedBox.shrink(), - ], - ), - ); - }, - ), - ), - ), - ], - ), - ), - ); - } - - Widget replyList(LoadingState?> loadingState) { - return switch (loadingState) { - Loading() => SliverList.builder( - itemCount: 5, - itemBuilder: (context, index) { - return const VideoReplySkeleton(); - }, - ), - Success() => loadingState.response?.isNotEmpty == true - ? SliverList.builder( - itemCount: loadingState.response!.length + 1, - itemBuilder: (context, index) { - if (index == loadingState.response!.length) { - _htmlRenderCtr.onLoadMore(); - return Container( - alignment: Alignment.center, - margin: EdgeInsets.only( - bottom: MediaQuery.of(context).padding.bottom), - height: 125, - child: Text( - _htmlRenderCtr.isEnd.not - ? '加载中...' - : loadingState.response!.isEmpty - ? '还没有评论' - : '没有更多了', - style: TextStyle( - fontSize: 12, - color: Theme.of(context).colorScheme.outline, + ), + ], + ), + ) + : const SizedBox.shrink(), + ], ), - ), - ); - } else { - return ReplyItemGrpc( - replyItem: loadingState.response![index], - replyLevel: '1', - replyReply: (replyItem, id) => - replyReply(context, replyItem, id), - onReply: () { - _htmlRenderCtr.onReply( - context, - replyItem: loadingState.response![index], - index: index, - ); - }, - onDelete: (subIndex) => - _htmlRenderCtr.onRemove(index, subIndex), - upMid: _htmlRenderCtr.upMid, - callback: _getImageCallback, - onCheckReply: (item) => - _htmlRenderCtr.onCheckReply(context, item), - onToggleTop: (isUpTop, rpid) => _htmlRenderCtr.onToggleTop( - index, - _htmlRenderCtr.oid, - _htmlRenderCtr.type, - isUpTop, - rpid, - ), - ); - } - }, - ) - : HttpError( - onReload: _htmlRenderCtr.onReload, - ), - Error() => HttpError( - errMsg: loadingState.errMsg, - onReload: _htmlRenderCtr.onReload, - ), - LoadingState() => throw UnimplementedError(), - }; - } - - Container replyHeader() { - return Container( - height: 45, - padding: const EdgeInsets.only(left: 12, right: 6), - child: Row( - children: [ - const Text('回复'), - const Spacer(), - SizedBox( - height: 35, - child: TextButton.icon( - onPressed: () => _htmlRenderCtr.queryBySort(), - icon: const Icon(Icons.sort, size: 16), - label: Obx( - () => Text( - _htmlRenderCtr.sortType.value.label, - style: const TextStyle(fontSize: 13), - ), - ), - ), - ) - ], - ), - ); - } - - Widget get _buildHeader => 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(), - ], + ); + }, ), ), ); - - Widget get _buildContent => SliverPadding( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), - sliver: Obx( - () => _htmlRenderCtr.loaded.value - ? _htmlRenderCtr.response['isJsonContent'] == true - ? SliverLayoutBuilder( - builder: (context, constraints) => articleContent( - context: context, - list: _htmlRenderCtr.response['content'], - callback: _getImageCallback, - maxWidth: constraints.crossAxisExtent, - ), - ) - : SliverToBoxAdapter( - child: LayoutBuilder( - builder: (context, constraints) => htmlRender( - context: context, - htmlContent: _htmlRenderCtr.response['content'], - constrainedWidth: constraints.maxWidth, - callback: _getImageCallback, - ), - ), - ) - : SliverToBoxAdapter(child: const SizedBox()), - ), - ); }