From 0036b58aabca4981e60c7cd89a23e0ce49e14774 Mon Sep 17 00:00:00 2001 From: dom Date: Sat, 4 Jul 2026 18:04:46 +0800 Subject: [PATCH] feat: reply vote Signed-off-by: dom --- .../common/dyn/common_dyn_controller.dart | 4 +- lib/pages/common/dyn/common_dyn_page.dart | 129 ++++++++------ lib/pages/common/reply_controller.dart | 7 +- lib/pages/dynamics_detail/view.dart | 4 +- lib/pages/video/reply/controller.dart | 4 +- lib/pages/video/reply/view.dart | 130 +++++++------- .../video/reply/vote/reply_vote_item.dart | 49 ++++++ .../video/reply/vote/reply_vote_mixin.dart | 19 +++ .../video/reply/widgets/reply_item_grpc.dart | 160 ++++++++++-------- lib/utils/extension/theme_ext.dart | 5 + 10 files changed, 319 insertions(+), 192 deletions(-) create mode 100644 lib/pages/video/reply/vote/reply_vote_item.dart create mode 100644 lib/pages/video/reply/vote/reply_vote_mixin.dart diff --git a/lib/pages/common/dyn/common_dyn_controller.dart b/lib/pages/common/dyn/common_dyn_controller.dart index 44f74b9e8..d86e22332 100644 --- a/lib/pages/common/dyn/common_dyn_controller.dart +++ b/lib/pages/common/dyn/common_dyn_controller.dart @@ -2,10 +2,12 @@ import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart'; import 'package:PiliPlus/grpc/reply.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/pages/common/reply_controller.dart'; +import 'package:PiliPlus/pages/video/reply/vote/reply_vote_mixin.dart'; import 'package:PiliPlus/utils/storage_pref.dart'; import 'package:get/get.dart'; -abstract class CommonDynController extends ReplyController { +abstract class CommonDynController extends ReplyController + with ReplyVoteMixin { int get oid; int get replyType; diff --git a/lib/pages/common/dyn/common_dyn_page.dart b/lib/pages/common/dyn/common_dyn_page.dart index 42c1a05be..daceaf310 100644 --- a/lib/pages/common/dyn/common_dyn_page.dart +++ b/lib/pages/common/dyn/common_dyn_page.dart @@ -10,6 +10,7 @@ import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models/common/enum_with_label.dart'; import 'package:PiliPlus/pages/common/dyn/common_dyn_controller.dart'; import 'package:PiliPlus/pages/common/fab_mixin.dart'; +import 'package:PiliPlus/pages/video/reply/vote/reply_vote_item.dart'; import 'package:PiliPlus/pages/video/reply/widgets/reply_item_grpc.dart'; import 'package:PiliPlus/pages/video/reply_reply/view.dart'; import 'package:PiliPlus/utils/extension/num_ext.dart'; @@ -120,62 +121,78 @@ mixin CommonDynPageMixin } Widget replyList(LoadingState?> loadingState) { - return switch (loadingState) { - Loading() => SliverList.builder( - itemCount: 12, - itemBuilder: (context, index) => const VideoReplySkeleton(), - ), - Success(:final response) => - response != null && response.isNotEmpty - ? SliverList.builder( - itemCount: response.length + 1, - itemBuilder: (context, index) { - if (index == response.length) { - controller.onLoadMore(); - return Container( - alignment: Alignment.center, - margin: EdgeInsets.only(bottom: padding.bottom), - height: 125, - child: Text( - controller.isEnd ? '没有更多了' : '加载中...', - style: TextStyle( - fontSize: 12, - color: theme.colorScheme.outline, - ), - ), - ); - } else { - return ReplyItemGrpc( - replyItem: response[index], - replyLevel: 1, - replyReply: (replyItem, id) => - replyReply(context, replyItem, id), - onReply: controller.onReply, - onDelete: (item, subIndex) => - controller.onRemove(index, item, subIndex), - upMid: controller.upMid, - onViewImage: hideFab, - onCheckReply: (item) => - controller.onCheckReply(item, isManual: true), - onToggleTop: (item) => controller.onToggleTop( - item, - index, - controller.oid, - controller.replyType, - ), - ); - } - }, - ) - : HttpError( - errMsg: '还没有评论', - onReload: controller.onReload, - ), - Error(:final errMsg) => HttpError( - errMsg: errMsg, - onReload: controller.onReload, - ), - }; + switch (loadingState) { + case Loading(): + return SliverList.builder( + itemCount: 12, + itemBuilder: (context, index) => const VideoReplySkeleton(), + ); + case Success(:final response): + if (response != null && response.isNotEmpty) { + var count = response.length + 1; + final voteCard = controller.voteCard; + final hasVote = voteCard != null; + if (hasVote) { + count++; + } + return SliverList.builder( + itemCount: count, + itemBuilder: (context, index) { + if (hasVote) { + if (index == 0) { + return buildVoteCard(context, theme.colorScheme, voteCard); + } else { + index--; + } + } + if (index == response.length) { + controller.onLoadMore(); + return Container( + alignment: Alignment.center, + margin: EdgeInsets.only(bottom: padding.bottom), + height: 125, + child: Text( + controller.isEnd ? '没有更多了' : '加载中...', + style: TextStyle( + fontSize: 12, + color: theme.colorScheme.outline, + ), + ), + ); + } else { + return ReplyItemGrpc( + replyItem: response[index], + replyLevel: 1, + replyReply: (replyItem, id) => + replyReply(context, replyItem, id), + onReply: controller.onReply, + onDelete: (item, subIndex) => + controller.onRemove(index, item, subIndex), + upMid: controller.upMid, + onViewImage: hideFab, + onCheckReply: (item) => + controller.onCheckReply(item, isManual: true), + onToggleTop: (item) => controller.onToggleTop( + item, + index, + controller.oid, + controller.replyType, + ), + ); + } + }, + ); + } + return HttpError( + errMsg: '还没有评论', + onReload: controller.onReload, + ); + case Error(:final errMsg): + return HttpError( + errMsg: errMsg, + onReload: controller.onReload, + ); + } } void replyReply(BuildContext context, ReplyInfo replyItem, int? id) { diff --git a/lib/pages/common/reply_controller.dart b/lib/pages/common/reply_controller.dart index d5e8c9241..3fd49f9d9 100644 --- a/lib/pages/common/reply_controller.dart +++ b/lib/pages/common/reply_controller.dart @@ -57,16 +57,15 @@ abstract class ReplyController extends CommonListController { } @override - bool customHandleResponse(bool isRefresh, Success response) { - MainListReply data = response.response; + bool customHandleResponse(bool isRefresh, Success response) { + final data = response.response as MainListReply; cursorNext = data.cursor.next; paginationReply = data.paginationReply; count.value = data.subjectControl.count.toInt(); if (isRefresh) { subjectControl = data.subjectControl; upMid ??= data.subjectControl.upMid; - hasUpTop = data.hasUpTop(); - if (data.hasUpTop()) { + if (hasUpTop = data.hasUpTop()) { data.replies.insert(0, data.upTop); } if (subjectControl?.title == ReplySortType.select.title) { diff --git a/lib/pages/dynamics_detail/view.dart b/lib/pages/dynamics_detail/view.dart index 3beba2eda..1c4a9e47e 100644 --- a/lib/pages/dynamics_detail/view.dart +++ b/lib/pages/dynamics_detail/view.dart @@ -566,9 +566,7 @@ class _DynamicDetailPageState color: theme.colorScheme.surface, border: Border( top: BorderSide( - color: theme.colorScheme.outline.withValues( - alpha: 0.08, - ), + color: theme.colorScheme.outline.withValues(alpha: 0.08), ), ), ), diff --git a/lib/pages/video/reply/controller.dart b/lib/pages/video/reply/controller.dart index 9746848f7..9badec4be 100644 --- a/lib/pages/video/reply/controller.dart +++ b/lib/pages/video/reply/controller.dart @@ -5,10 +5,12 @@ import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models/common/video/video_type.dart'; import 'package:PiliPlus/pages/common/reply_controller.dart'; import 'package:PiliPlus/pages/video/controller.dart'; +import 'package:PiliPlus/pages/video/reply/vote/reply_vote_mixin.dart'; import 'package:PiliPlus/utils/id_utils.dart'; import 'package:get/get.dart'; -class VideoReplyController extends ReplyController { +class VideoReplyController extends ReplyController + with ReplyVoteMixin { VideoReplyController({ required this.aid, required this.videoType, diff --git a/lib/pages/video/reply/view.dart b/lib/pages/video/reply/view.dart index a5ed1f6b3..e32f8c194 100644 --- a/lib/pages/video/reply/view.dart +++ b/lib/pages/video/reply/view.dart @@ -8,6 +8,7 @@ import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart' import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/pages/common/fab_mixin.dart'; import 'package:PiliPlus/pages/video/reply/controller.dart'; +import 'package:PiliPlus/pages/video/reply/vote/reply_vote_item.dart'; import 'package:PiliPlus/pages/video/reply/widgets/reply_item_grpc.dart'; import 'package:PiliPlus/pages/video/reply_reply/view.dart'; import 'package:PiliPlus/utils/feed_back.dart'; @@ -125,9 +126,7 @@ class _VideoReplyPanelState extends State }), ), ), - Obx( - () => _buildBody(_videoReplyController.loadingState.value), - ), + Obx(() => _buildBody(_videoReplyController.loadingState.value)), ], ), Positioned( @@ -170,62 +169,75 @@ class _VideoReplyPanelState extends State } Widget _buildBody(LoadingState?> loadingState) { - return switch (loadingState) { - Loading() => SliverList.builder( - itemBuilder: (context, index) => const VideoReplySkeleton(), - itemCount: 5, - ), - Success(:final response) => - response != null && response.isNotEmpty - ? SliverList.builder( - itemBuilder: (context, index) { - if (index == response.length) { - _videoReplyController.onLoadMore(); - return Container( - height: 125, - alignment: .center, - margin: .only(bottom: bottom), - child: Text( - _videoReplyController.isEnd ? '没有更多了' : '加载中...', - textAlign: .center, - style: TextStyle( - fontSize: 12, - color: colorScheme.outline, - ), - ), - ); - } else { - return ReplyItemGrpc( - replyItem: response[index], - replyLevel: widget.replyLevel, - replyReply: replyReply, - onReply: _videoReplyController.onReply, - onDelete: (item, subIndex) => - _videoReplyController.onRemove(index, item, subIndex), - upMid: _videoReplyController.upMid, - getTag: () => heroTag, - onCheckReply: (item) => _videoReplyController - .onCheckReply(item, isManual: true), - onToggleTop: (item) => _videoReplyController.onToggleTop( - item, - index, - _videoReplyController.aid, - _videoReplyController.videoType.replyType, - ), - ); - } - }, - itemCount: response.length + 1, - ) - : HttpError( - errMsg: '还没有评论', - onReload: _videoReplyController.onReload, - ), - Error(:final errMsg) => HttpError( - errMsg: errMsg, - onReload: _videoReplyController.onReload, - ), - }; + switch (loadingState) { + case Loading(): + return SliverList.builder( + itemBuilder: (context, index) => const VideoReplySkeleton(), + itemCount: 5, + ); + case Success(:final response): + if (response != null && response.isNotEmpty) { + var count = response.length + 1; + final voteCard = _videoReplyController.voteCard; + final hasVote = voteCard != null; + if (hasVote) { + count++; + } + return SliverList.builder( + itemBuilder: (context, index) { + if (hasVote) { + if (index == 0) { + return buildVoteCard(context, colorScheme, voteCard); + } else { + index--; + } + } + if (index == response.length) { + _videoReplyController.onLoadMore(); + return Container( + height: 125, + alignment: .center, + margin: .only(bottom: bottom), + child: Text( + _videoReplyController.isEnd ? '没有更多了' : '加载中...', + textAlign: .center, + style: TextStyle(fontSize: 12, color: colorScheme.outline), + ), + ); + } else { + return ReplyItemGrpc( + replyItem: response[index], + replyLevel: widget.replyLevel, + replyReply: replyReply, + onReply: _videoReplyController.onReply, + onDelete: (item, subIndex) => + _videoReplyController.onRemove(index, item, subIndex), + upMid: _videoReplyController.upMid, + getTag: () => heroTag, + onCheckReply: (item) => + _videoReplyController.onCheckReply(item, isManual: true), + onToggleTop: (item) => _videoReplyController.onToggleTop( + item, + index, + _videoReplyController.aid, + _videoReplyController.videoType.replyType, + ), + ); + } + }, + itemCount: count, + ); + } + return HttpError( + errMsg: '还没有评论', + onReload: _videoReplyController.onReload, + ); + case Error(:final errMsg): + return HttpError( + errMsg: errMsg, + onReload: _videoReplyController.onReload, + ); + } } // 展示二级回复 diff --git a/lib/pages/video/reply/vote/reply_vote_item.dart b/lib/pages/video/reply/vote/reply_vote_item.dart new file mode 100644 index 000000000..44ffb3be1 --- /dev/null +++ b/lib/pages/video/reply/vote/reply_vote_item.dart @@ -0,0 +1,49 @@ +import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart' + show VoteCard; +import 'package:PiliPlus/pages/dynamics/widgets/vote.dart'; +import 'package:PiliPlus/utils/num_utils.dart'; +import 'package:flutter/material.dart'; + +Widget buildVoteCard( + BuildContext context, + ColorScheme colorScheme, + VoteCard voteCard, +) { + return InkWell( + onTap: () => showVoteDialog(context, voteCard.voteId.toInt()), + child: Padding( + padding: const .symmetric(horizontal: 12, vertical: 6), + child: Row( + spacing: 10, + children: [ + Container( + decoration: BoxDecoration( + color: colorScheme.onInverseSurface, + borderRadius: const .all(.circular(8)), + ), + width: 60, + height: 42, + child: Icon( + Icons.bar_chart_rounded, + color: colorScheme.onSurfaceVariant, + ), + ), + Expanded( + child: Column( + crossAxisAlignment: .start, + children: [ + Text(voteCard.title, maxLines: 1, overflow: .ellipsis), + Text( + '${NumUtils.numFormat(voteCard.count.toInt())}人参与', + maxLines: 1, + overflow: .ellipsis, + style: TextStyle(fontSize: 13, color: colorScheme.outline), + ), + ], + ), + ), + ], + ), + ), + ); +} diff --git a/lib/pages/video/reply/vote/reply_vote_mixin.dart b/lib/pages/video/reply/vote/reply_vote_mixin.dart new file mode 100644 index 000000000..947f7bcb4 --- /dev/null +++ b/lib/pages/video/reply/vote/reply_vote_mixin.dart @@ -0,0 +1,19 @@ +import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart' + show MainListReply, VoteCard, ReplyInfo; +import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/pages/common/common_list_controller.dart'; + +mixin ReplyVoteMixin on CommonListController { + VoteCard? voteCard; + + @override + bool customHandleResponse(bool isRefresh, Success response) { + if (isRefresh) { + final res = response.response; + if (res.hasVoteCard()) { + voteCard = res.voteCard; + } + } + return super.customHandleResponse(isRefresh, response); + } +} diff --git a/lib/pages/video/reply/widgets/reply_item_grpc.dart b/lib/pages/video/reply/widgets/reply_item_grpc.dart index 7da217e0c..e26a0aa93 100644 --- a/lib/pages/video/reply/widgets/reply_item_grpc.dart +++ b/lib/pages/video/reply/widgets/reply_item_grpc.dart @@ -14,7 +14,7 @@ import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/image_grid/image_grid_view.dart'; import 'package:PiliPlus/common/widgets/pendant_avatar.dart'; import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart' - show ReplyInfo, ReplyControl, Content, Url; + show ReplyInfo, ReplyControl, Content, Url, ReplyControl_VoteOption; import 'package:PiliPlus/grpc/reply.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/reply.dart'; @@ -92,7 +92,7 @@ class ReplyItemGrpc extends StatelessWidget { @override Widget build(BuildContext context) { - final ThemeData theme = Theme.of(context); + final colorScheme = ColorScheme.of(context); void showMore() => showModalBottomSheet( context: context, @@ -113,7 +113,7 @@ class ReplyItemGrpc extends StatelessWidget { Widget child = Padding( padding: const .fromLTRB(12, 14, 8, 5), - child: _buildContent(context, theme), + child: _buildContent(context, colorScheme), ); if (needDivider) { child = Column( @@ -124,7 +124,7 @@ class ReplyItemGrpc extends StatelessWidget { indent: 55, endIndent: 15, height: 0.3, - color: theme.colorScheme.outline.withValues(alpha: 0.08), + color: colorScheme.outline.withValues(alpha: 0.08), ), ], ); @@ -140,7 +140,7 @@ class ReplyItemGrpc extends StatelessWidget { ); } - Widget _buildHeader(BuildContext context, ThemeData theme) { + Widget _buildHeader(BuildContext context, ColorScheme colorScheme) { final member = replyItem.member; Widget header = GestureDetector( onTap: () { @@ -178,8 +178,8 @@ class ReplyItemGrpc extends StatelessWidget { overflow: .ellipsis, style: TextStyle( color: (member.vipStatus > 0 && member.vipType == 2) - ? theme.colorScheme.vipColor - : theme.colorScheme.outline, + ? colorScheme.vipColor + : colorScheme.outline, fontSize: 13, ), ), @@ -227,16 +227,16 @@ class ReplyItemGrpc extends StatelessWidget { replyItem.ctime.toInt(), ), style: TextStyle( - fontSize: theme.textTheme.labelSmall!.fontSize, - color: theme.colorScheme.outline, + fontSize: 11, + color: colorScheme.outline, ), ), if (replyItem.replyControl.hasLocation()) Text( ' • ${replyItem.replyControl.location}', style: TextStyle( - fontSize: theme.textTheme.labelSmall!.fontSize, - color: theme.colorScheme.outline, + fontSize: 11, + color: colorScheme.outline, ), ), ], @@ -293,23 +293,56 @@ class ReplyItemGrpc extends StatelessWidget { return header; } - Widget _buildContent(BuildContext context, ThemeData theme) { + Widget _buildVoteOption( + ColorScheme colorScheme, + ReplyControl_VoteOption voteOption, + ) { + return Text.rich( + TextSpan( + children: [ + switch (voteOption.labelKind) { + .RED => TextSpan( + text: '红队 ', + style: TextStyle(color: colorScheme.vipColor), + ), + .BLUE => TextSpan( + text: '蓝队 ', + style: TextStyle(color: colorScheme.blue), + ), + _ => TextSpan( + text: '投票 ', + style: TextStyle(color: colorScheme.outline), + ), + }, + TextSpan(text: voteOption.desc), + ], + ), + style: TextStyle( + fontSize: 12, + color: colorScheme.onSurfaceVariant, + ), + ); + } + + Widget _buildContent(BuildContext context, ColorScheme colorScheme) { final replyControl = replyItem.replyControl; final padding = EdgeInsets.only(left: replyLevel == 0 ? 6 : 45, right: 6); return Column( mainAxisSize: .min, crossAxisAlignment: .start, children: [ - _buildHeader(context, theme), + _buildHeader(context, colorScheme), const SizedBox(height: 10), + if (replyControl.hasVoteOption()) + Padding( + padding: padding, + child: _buildVoteOption(colorScheme, replyControl.voteOption), + ), Padding( padding: padding, child: custom_text.Text.rich( - primary: theme.colorScheme.primary, - style: TextStyle( - height: 1.75, - fontSize: theme.textTheme.bodyMedium!.fontSize, - ), + primary: colorScheme.primary, + style: const TextStyle(height: 1.75, fontSize: 14), maxLines: replyLevel == 1 ? replyLengthLimit : null, TextSpan( children: [ @@ -329,7 +362,7 @@ class ReplyItemGrpc extends StatelessWidget { ], _buildMessage( context, - theme, + colorScheme, replyControl.showTranslation ? replyItem.translatedContent : replyItem.content, @@ -359,12 +392,12 @@ class ReplyItemGrpc extends StatelessWidget { ], if (replyLevel != 0) ...[ const SizedBox(height: 4), - buttonAction(context, theme, replyControl), + buttonAction(context, colorScheme, replyControl), ], if (replyLevel == 1 && replyItem.count > Int64.ZERO) ...[ Padding( padding: const EdgeInsets.only(top: 5, bottom: 12), - child: replyItemRow(context, theme, replyItem.replies), + child: replyItemRow(context, colorScheme, replyItem.replies), ), ], ], @@ -373,15 +406,15 @@ class ReplyItemGrpc extends StatelessWidget { Widget _buildTranslateBtn( BuildContext context, - ThemeData theme, + ColorScheme colorScheme, ReplyControl replyControl, TextStyle textStyle, ButtonStyle buttonStyle, ) { late bool isProcessing = false; final color = replyControl.showTranslation - ? theme.colorScheme.primary - : theme.colorScheme.outline.withValues(alpha: 0.8); + ? colorScheme.primary + : colorScheme.outline.withValues(alpha: 0.8); return SizedBox( height: 32, child: TextButton( @@ -439,14 +472,14 @@ class ReplyItemGrpc extends StatelessWidget { Widget buttonAction( BuildContext context, - ThemeData theme, + ColorScheme colorScheme, ReplyControl replyControl, ) { final textStyle = TextStyle( height: 1, + fontSize: 12, fontWeight: .normal, - color: theme.colorScheme.outline, - fontSize: theme.textTheme.labelMedium!.fontSize, + color: colorScheme.outline, ); const buttonStyle = ButtonStyle( visualDensity: .compact, @@ -492,7 +525,7 @@ class ReplyItemGrpc extends StatelessWidget { Icon( Icons.reply, size: 18, - color: theme.colorScheme.outline.withValues(alpha: 0.8), + color: colorScheme.outline.withValues(alpha: 0.8), ), Text('回复', style: textStyle), ], @@ -504,7 +537,7 @@ class ReplyItemGrpc extends StatelessWidget { .TRANSLATION_SWITCH_SHOW_TRANSLATION) ...[ _buildTranslateBtn( context, - theme, + colorScheme, replyControl, textStyle, buttonStyle, @@ -515,7 +548,7 @@ class ReplyItemGrpc extends StatelessWidget { dialogBtn != null ? replyControl.cardLabels.first.textContent : replyControl.cardLabels.map((e) => e.textContent).join(' '), - style: textStyle.copyWith(color: theme.colorScheme.secondary), + style: textStyle.copyWith(color: colorScheme.secondary), ), const SizedBox(width: 2), ], @@ -529,7 +562,7 @@ class ReplyItemGrpc extends StatelessWidget { Widget replyItemRow( BuildContext context, - ThemeData theme, + ColorScheme colorScheme, List replies, ) { final extraRow = replies.length < replyItem.count.toInt(); @@ -537,7 +570,7 @@ class ReplyItemGrpc extends StatelessWidget { return Padding( padding: const EdgeInsets.only(left: 42, right: 4), child: Material( - color: theme.colorScheme.onInverseSurface, + color: colorScheme.onInverseSurface, borderRadius: const BorderRadius.all(Radius.circular(6)), clipBehavior: Clip.hardEdge, animationDuration: Duration.zero, @@ -584,11 +617,9 @@ class ReplyItemGrpc extends StatelessWidget { padding: padding, child: Text.rich( style: TextStyle( - fontSize: theme.textTheme.bodyMedium!.fontSize, - color: theme.colorScheme.onSurface.withValues( - alpha: 0.85, - ), height: 1.6, + fontSize: 14, + color: colorScheme.onSurface.withValues(alpha: 0.85), ), overflow: TextOverflow.ellipsis, maxLines: 2, @@ -596,9 +627,7 @@ class ReplyItemGrpc extends StatelessWidget { children: [ TextSpan( text: childReply.member.name, - style: TextStyle( - color: theme.colorScheme.primary, - ), + style: TextStyle(color: colorScheme.primary), recognizer: NoDeadlineTapGestureRecognizer() ..onTap = () { feedBack(); @@ -630,7 +659,7 @@ class ReplyItemGrpc extends StatelessWidget { ), _buildMessage( context, - theme, + colorScheme, childReply.content, childReply.replyControl, ), @@ -649,15 +678,13 @@ class ReplyItemGrpc extends StatelessWidget { : const EdgeInsets.fromLTRB(8, 5, 8, 8), child: Text.rich( TextSpan( - style: TextStyle( - fontSize: theme.textTheme.labelMedium!.fontSize, - ), + style: const TextStyle(fontSize: 12), children: [ if (replyItem.replyControl.upReply) TextSpan( text: 'UP主等人 ', style: TextStyle( - color: theme.colorScheme.onSurface.withValues( + color: colorScheme.onSurface.withValues( alpha: 0.85, ), ), @@ -665,7 +692,7 @@ class ReplyItemGrpc extends StatelessWidget { TextSpan( text: '共${replyItem.count}条回复', style: TextStyle( - color: theme.colorScheme.primary, + color: colorScheme.primary, ), ), ], @@ -681,7 +708,7 @@ class ReplyItemGrpc extends StatelessWidget { InlineSpan _buildMessage( BuildContext context, - ThemeData theme, + ColorScheme colorScheme, Content content, ReplyControl replyControl, ) { @@ -727,16 +754,14 @@ class ReplyItemGrpc extends StatelessWidget { child: CachedNetworkImage( height: 19, memCacheHeight: 19.cacheSize(context), - color: theme.colorScheme.primary, + color: colorScheme.primary, imageUrl: ImageUtils.thumbnailUrl(url.prefixIcon), placeholder: (_, _) => const SizedBox.shrink(), ), ), TextSpan( text: isCv ? '[笔记] ' : url.title, - style: TextStyle( - color: theme.colorScheme.primary, - ), + style: TextStyle(color: colorScheme.primary), recognizer: NoDeadlineTapGestureRecognizer() ..onTap = () { if (url.appUrlSchema.isEmpty) { @@ -815,7 +840,7 @@ class ReplyItemGrpc extends StatelessWidget { spanChildren.add( TextSpan( text: matchStr, - style: TextStyle(color: theme.colorScheme.primary), + style: TextStyle(color: colorScheme.primary), recognizer: NoDeadlineTapGestureRecognizer() ..onTap = () => Get.toNamed('/member?mid=${content.atNameToMid[name]}'), @@ -825,7 +850,7 @@ class ReplyItemGrpc extends StatelessWidget { spanChildren.add( TextSpan( text: '投票: ${content.vote.title}', - style: TextStyle(color: theme.colorScheme.primary), + style: TextStyle(color: colorScheme.primary), recognizer: NoDeadlineTapGestureRecognizer() ..onTap = () => showVoteDialog(context, content.vote.id.toInt()), @@ -847,9 +872,7 @@ class ReplyItemGrpc extends StatelessWidget { spanChildren.add( TextSpan( text: isValid ? ' $matchStr ' : matchStr, - style: isValid - ? TextStyle(color: theme.colorScheme.primary) - : null, + style: isValid ? TextStyle(color: colorScheme.primary) : null, recognizer: isValid ? (NoDeadlineTapGestureRecognizer() ..onTap = () { @@ -881,7 +904,7 @@ class ReplyItemGrpc extends StatelessWidget { spanChildren.add( TextSpan( text: matchStr, - style: TextStyle(color: theme.colorScheme.primary), + style: TextStyle(color: colorScheme.primary), recognizer: NoDeadlineTapGestureRecognizer() ..onTap = () { Get.toNamed( @@ -895,7 +918,7 @@ class ReplyItemGrpc extends StatelessWidget { spanChildren.add( TextSpan( text: matchStr, - style: TextStyle(color: theme.colorScheme.primary), + style: TextStyle(color: colorScheme.primary), recognizer: NoDeadlineTapGestureRecognizer() ..onTap = () => PageUtils.handleWebview(matchStr), ), @@ -929,7 +952,7 @@ class ReplyItemGrpc extends StatelessWidget { final hasClickUrl = content.richText.note.hasClickUrl(); if (hasClickUrl || content.richText.opus.hasOpusId()) { - color = theme.colorScheme.primary; + color = colorScheme.primary; recognizer = NoDeadlineTapGestureRecognizer() ..onTap = () => hasClickUrl ? PiliScheme.routePushFromUrl(content.richText.note.clickUrl) @@ -941,7 +964,7 @@ class ReplyItemGrpc extends StatelessWidget { }, ); } else { - color = theme.colorScheme.secondary; + color = colorScheme.secondary; } spanChildren.insert( 0, @@ -965,11 +988,12 @@ class ReplyItemGrpc extends StatelessWidget { late String message = item.content.message; final ownerMid = Int64(Accounts.main.mid); final theme = Theme.of(context); - final errorColor = theme.colorScheme.error; + final colorScheme = theme.colorScheme; + final errorColor = colorScheme.error; final style = theme.textTheme.titleSmall!; return Padding( - padding: EdgeInsets.only( + padding: .only( bottom: MediaQuery.viewPaddingOf(context).bottom + 20, ), child: Column( @@ -985,7 +1009,7 @@ class ReplyItemGrpc extends StatelessWidget { width: 32, height: 3, decoration: BoxDecoration( - color: theme.colorScheme.outline, + color: colorScheme.outline, borderRadius: const BorderRadius.all(Radius.circular(3)), ), ), @@ -1007,7 +1031,7 @@ class ReplyItemGrpc extends StatelessWidget { }, title: Text( 'save to local', - style: style.copyWith(color: theme.colorScheme.primary), + style: style.copyWith(color: colorScheme.primary), ), ), ListTile( @@ -1018,7 +1042,7 @@ class ReplyItemGrpc extends StatelessWidget { }, title: Text( 'remove from local', - style: style.copyWith(color: theme.colorScheme.primary), + style: style.copyWith(color: colorScheme.primary), ), ), ListTile( @@ -1037,7 +1061,7 @@ class ReplyItemGrpc extends StatelessWidget { }, title: Text( 'save to local (x1000)', - style: style.copyWith(color: theme.colorScheme.primary), + style: style.copyWith(color: colorScheme.primary), ), ), ], @@ -1048,7 +1072,7 @@ class ReplyItemGrpc extends StatelessWidget { bool? isDelete = await showDialog( context: context, builder: (context) { - final theme = Theme.of(context); + final colorScheme = ColorScheme.of(context); return AlertDialog( title: const Text('删除评论'), content: Text.rich( @@ -1059,7 +1083,7 @@ class ReplyItemGrpc extends StatelessWidget { TextSpan( text: '@${item.member.name}', style: TextStyle( - color: theme.colorScheme.primary, + color: colorScheme.primary, ), ), const TextSpan(text: ':\n'), @@ -1074,7 +1098,7 @@ class ReplyItemGrpc extends StatelessWidget { child: Text( '取消', style: TextStyle( - color: theme.colorScheme.outline, + color: colorScheme.outline, ), ), ), diff --git a/lib/utils/extension/theme_ext.dart b/lib/utils/extension/theme_ext.dart index b47191990..ab6b1f8ed 100644 --- a/lib/utils/extension/theme_ext.dart +++ b/lib/utils/extension/theme_ext.dart @@ -5,6 +5,9 @@ import 'package:flutter/material.dart' const _pinkLight = Color(0xFFFF6699); const _pinkDark = Color(0xFFD44E7D); +const _blueLight = Color(0xFF008AC5); +const _blueDark = Color(0xFF2C9CC8); + extension ThemeDataExt on ThemeData { bool get isLight => brightness.isLight; @@ -14,6 +17,8 @@ extension ThemeDataExt on ThemeData { extension ColorSchemeExt on ColorScheme { Color get vipColor => brightness.isLight ? _pinkLight : _pinkDark; + Color get blue => brightness.isLight ? _blueLight : _blueDark; + Color get btnColor => brightness.isLight ? _pinkLight : const Color(0xFF8F0030);