初始化
This commit is contained in:
@@ -0,0 +1,318 @@
|
||||
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),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user