319 lines
12 KiB
Dart
319 lines
12 KiB
Dart
import 'dart:math';
|
|
|
|
import 'package:flutter/cupertino.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:hgdj/assets_tool/images.dart';
|
|
import 'package:hgdj/extension/extensions.dart';
|
|
import 'package:hgdj/hj_model/banner/comment_top_banner_model.dart';
|
|
import 'package:hgdj/hj_model/comment/comment_model.dart';
|
|
import 'package:hgdj/hj_model/comment/reply_model.dart';
|
|
import 'package:hgdj/hj_model/video_model.dart';
|
|
import 'package:hgdj/hj_page/comment/cell/reply_comment_item.dart';
|
|
import 'package:hgdj/hj_utils/api_service/comment_service.dart';
|
|
import 'package:hgdj/hj_utils/api_service/common_service.dart';
|
|
import 'package:hgdj/hj_utils/date_time_util.dart';
|
|
import 'package:hgdj/hj_utils/screen.dart';
|
|
import 'package:hgdj/tools_base/debug_log.dart';
|
|
import 'package:hgdj/tools_base/widget/image_browser_page.dart';
|
|
import 'package:like_button/like_button.dart';
|
|
|
|
import '../../../tools_base/widget/net_image_widget.dart';
|
|
import '../../home/widget/user_avatar.dart';
|
|
import '../../home/widget/user_name_view.dart';
|
|
import '../widget/comment_rich_text.dart';
|
|
import '../widget/comment_top_banner.dart';
|
|
|
|
/// 一条评论:头像 + 昵称 + 正文(可带图) + 时间/点赞/回复 + 折叠的回复列表
|
|
class CommentItem extends StatefulWidget {
|
|
final CommentModel comment;
|
|
final String objId;
|
|
final List<CommentTopBannerModel>? topBanners; // 置顶评论下方配置 Banner
|
|
final Function(CommentModel, {ReplyModel? reply})? replyHandler;
|
|
|
|
const CommentItem(
|
|
this.comment, {
|
|
super.key,
|
|
required this.objId,
|
|
this.replyHandler,
|
|
this.topBanners,
|
|
});
|
|
|
|
@override
|
|
State<CommentItem> createState() => _CommentItemState();
|
|
}
|
|
|
|
class _CommentItemState extends State<CommentItem> {
|
|
CommentModel get comment => widget.comment;
|
|
|
|
late final isLikedNof = ValueNotifier(comment.isLike ?? false);
|
|
bool _isLoadingMore = false;
|
|
bool _isExpand = false;
|
|
int _page = 1;
|
|
|
|
@override
|
|
void dispose() {
|
|
isLikedNof.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return GestureDetector(
|
|
onTap: () {},
|
|
child: Stack(
|
|
clipBehavior: Clip.none,
|
|
children: [
|
|
Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
UserAvatar(
|
|
size: 40,
|
|
model: Publisher()
|
|
..uid = comment.userID
|
|
..portrait = comment.userPortrait
|
|
..superUser = (comment.superUser ?? false),
|
|
showVip: false,
|
|
), //头像
|
|
12.sizeBoxW,
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
UserNameView(
|
|
name: comment.userName,
|
|
isVip: (comment.vipLevel ?? 0) > 0,
|
|
nameColor: Color(0xff9A9A9A),
|
|
),
|
|
if ((comment.vipLevel ?? 0) > 0) ...[
|
|
4.sizeBoxW,
|
|
Image.asset("vip_v.webp".commentPath,
|
|
width: 16),
|
|
]
|
|
],
|
|
),
|
|
8.sizeBoxH,
|
|
CommentRichText(model: comment),
|
|
if (comment.image?.isNotEmpty == true)
|
|
GestureDetector(
|
|
onTap: () {
|
|
ImageBrowserPage.open([comment.image ?? '']);
|
|
},
|
|
child: Container(
|
|
margin: EdgeInsets.only(top: 6),
|
|
alignment: Alignment.centerLeft,
|
|
constraints: BoxConstraints(maxWidth: 150),
|
|
child: NetworkImageLoader(
|
|
imageUrl: comment.image ?? ""),
|
|
),
|
|
),
|
|
// 置顶评论配置 Banner(多图轮播,支持 GIF / 内外链)
|
|
if (widget.topBanners?.isNotEmpty == true)
|
|
CommentTopBanner(banners: widget.topBanners!),
|
|
8.sizeBoxH,
|
|
Row(
|
|
children: [
|
|
Text(
|
|
comment.createdAt?.utcToAgo() ?? "",
|
|
style: TextStyle(
|
|
color: Color(0x73FFFFFF),
|
|
fontSize: 12,
|
|
),
|
|
),
|
|
const Spacer(),
|
|
if (!comment.isOfficial &&
|
|
!comment.isHideLikeOrComment) ...[
|
|
ValueListenableBuilder(
|
|
valueListenable: isLikedNof,
|
|
builder: (context, value, child) => Row(
|
|
children: [
|
|
LikeButton(
|
|
isLiked: value,
|
|
onTap: (_) => _toggleLike(),
|
|
size: 20,
|
|
likeBuilder: (isLiked) {
|
|
return Image.asset(isLiked
|
|
? 'like_red.webp'.commentPath
|
|
: 'like_white.webp'.commentPath);
|
|
},
|
|
),
|
|
Text(
|
|
comment.likeCount?.countOr("0") ?? "0",
|
|
style: TextStyle(
|
|
color: Color(0xE5FFFFFF),
|
|
fontSize: 12,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
12.sizeBoxW,
|
|
GestureDetector(
|
|
onTap: () =>
|
|
widget.replyHandler?.call(comment),
|
|
child: Image.asset('msg_icon.png'.commentPath,
|
|
width: 20),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
_replyList(),
|
|
// 展开更多评论
|
|
if ((comment.replies?.length ?? 0) > 0)
|
|
_moreReplyBar(),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
18.sizeBoxH,
|
|
const Divider(height: 1, color: Color(0x0fffffff)),
|
|
18.sizeBoxH,
|
|
],
|
|
),
|
|
if ((comment.isGodComment ?? false))
|
|
Positioned(
|
|
right: 8,
|
|
top: -10,
|
|
child: Transform.rotate(
|
|
angle: pi / 60,
|
|
child: Image.asset(
|
|
'god_comment.webp'.commentPath,
|
|
width: 72,
|
|
height: 60,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
));
|
|
}
|
|
|
|
/// 展开 / 收起那一行:短横线 + 文案 + 箭头
|
|
Widget _replyBarRow(String text, {bool isUp = false}) {
|
|
final chevron = Image.asset("chevron_down.webp".commonImgPath, width: 16);
|
|
return Row(
|
|
children: [
|
|
Container(width: 20, height: 1, color: const Color(0xff989898)),
|
|
4.sizeBoxW,
|
|
Text(text,
|
|
style: const TextStyle(
|
|
fontSize: 12,
|
|
color: Color(0xff989898),
|
|
fontWeight: FontWeight.w500)),
|
|
2.sizeBoxW,
|
|
isUp ? Transform.rotate(angle: pi, child: chevron) : chevron,
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _moreReplyBar() {
|
|
final replyCount = comment.replies?.length ?? 0;
|
|
if (replyCount > 1 && _isExpand) {
|
|
//收起:padding 在点击区里面,跟改动前一致
|
|
return GestureDetector(
|
|
onTap: () => setState(() => _isExpand = false),
|
|
child: Padding(
|
|
padding: const EdgeInsets.only(top: 12),
|
|
child: _replyBarRow("收起${comment.commCount ?? 0}条回复", isUp: true),
|
|
),
|
|
);
|
|
}
|
|
final diff = (comment.commCount ?? 0) - (_isExpand ? replyCount : 1);
|
|
if (diff <= 0) return const SizedBox.shrink();
|
|
if (_isLoadingMore) {
|
|
return const Padding(
|
|
padding: EdgeInsets.only(top: 12, left: 6),
|
|
child: CupertinoActivityIndicator(color: Colors.white, radius: 8),
|
|
);
|
|
}
|
|
//展开:padding 在点击区外面别算进热区;外面这层 Row 也不能省——
|
|
//它给 InkWell 的是无界宽约束,InkWell 才会只包住文字而不是撑满整行
|
|
return Padding(
|
|
padding: const EdgeInsets.only(top: 12),
|
|
child: Row(
|
|
children: [
|
|
InkWell(
|
|
enableFeedback: false,
|
|
onTap: _expandReplies,
|
|
child: _replyBarRow(replyCount > 99 ? '展开更多回复' : '展开$diff条回复'),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
// 展开回复
|
|
Future _expandReplies() async {
|
|
if (comment.replies?.isNotEmpty != true) {
|
|
comment.commCount = 0;
|
|
setState(() {});
|
|
return;
|
|
}
|
|
if (_isLoadingMore) return;
|
|
_isLoadingMore = true;
|
|
setState(() {});
|
|
_isExpand = true;
|
|
try {
|
|
if (comment.hasMoreReply ?? false) {
|
|
final curTime = DateTimeUtil.format2utc(DateTime.now());
|
|
final fastId = comment.replies!.first.id ?? '';
|
|
final result = await CommentService.getReplyList(widget.objId,
|
|
comment.id ?? '', curTime, comment.replyPage ?? _page, 5, fastId);
|
|
comment.hasMoreReply = result?.hasNext ?? false;
|
|
comment.replies?.addAll(result?.list ?? []);
|
|
if (result?.hasNext == true) {
|
|
_page += 1;
|
|
comment.replyPage = (comment.replyPage ?? 0) + 1;
|
|
} else {
|
|
comment.commCount = comment.replies?.length ?? 0;
|
|
}
|
|
}
|
|
} catch (e) {
|
|
debugLog(e);
|
|
}
|
|
_isLoadingMore = false;
|
|
if (mounted) setState(() {});
|
|
}
|
|
|
|
Future<bool> _toggleLike() async {
|
|
final isLike = comment.isLike ?? false;
|
|
bool result;
|
|
if (!isLike) {
|
|
result = await CommonService.sendLike(comment.id ?? '', 'COMMENT');
|
|
if (result) comment.likeCount = (comment.likeCount ?? 0) + 1;
|
|
} else {
|
|
result = await CommonService.cancelLike(comment.id ?? '', 'COMMENT');
|
|
if (result) comment.likeCount = (comment.likeCount ?? 1) - 1;
|
|
}
|
|
if (result) comment.isLike = !isLike;
|
|
isLikedNof.value = comment.isLike!;
|
|
return isLikedNof.value;
|
|
}
|
|
|
|
//没展开时只露第一条
|
|
Widget _replyList() {
|
|
final replies = comment.replies ?? [];
|
|
if (replies.isEmpty) return const SizedBox.shrink();
|
|
final length = (!_isExpand && replies.length > 1) ? 1 : replies.length;
|
|
return Column(
|
|
//stretch 对齐原来 ListView 的行为:子项撑满宽度,不按内容自适应
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: List.generate(
|
|
length,
|
|
(i) => ReplyCommentItem(
|
|
replies[i],
|
|
replyHandler: (reply) =>
|
|
widget.replyHandler?.call(comment, reply: reply),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|