comment_page.dart 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. import 'package:common_utils/common_utils.dart';
  2. import 'package:easy_refresh/easy_refresh.dart';
  3. import 'package:flutter/cupertino.dart';
  4. import 'package:flutter_riverpod/flutter_riverpod.dart';
  5. import 'package:flutter_screenutil/flutter_screenutil.dart';
  6. import 'package:news_app/main.dart';
  7. import 'package:news_app/model/new_comment_model.dart';
  8. import 'package:news_app/model/user_model.dart';
  9. import 'package:news_app/util/keyboard_util.dart';
  10. import 'package:news_app/util/log.util.dart';
  11. import 'package:news_app/widget/empty_2_widget.dart';
  12. import '../../provider/common_provider.dart';
  13. import 'comment_input_bar_widget.dart';
  14. import 'comment_item_widget.dart';
  15. /// @author: bo.zeng
  16. /// @email: cnhbwds@gmail.com
  17. /// @date: 2025 2025/4/17 11:15
  18. /// @description:
  19. enum CommentType { video, topic, activity, news, activityVideo }
  20. final commentProvider =
  21. NotifierProvider.family<CommentNotifier, NewCommentModel, CommentType>(
  22. () => CommentNotifier(),
  23. );
  24. final keyboardProvider = StateProvider<bool>((ref) => false);
  25. class CommentPage extends ConsumerStatefulWidget {
  26. final CommentType type;
  27. final String articleId;
  28. const CommentPage({super.key, required this.type, required this.articleId});
  29. @override
  30. ConsumerState<ConsumerStatefulWidget> createState() => _CommentPageSate();
  31. }
  32. class _CommentPageSate extends ConsumerState<CommentPage>
  33. with WidgetsBindingObserver {
  34. final FocusNode _focusNode = FocusNode();
  35. int _pageSize = 1;
  36. @override
  37. void initState() {
  38. super.initState();
  39. final notifier = ref.read(commentProvider(widget.type).notifier);
  40. WidgetsBinding.instance.addObserver(this);
  41. notifier.fetchComment(page: _pageSize, articleId: widget.articleId);
  42. ref.listenManual(commentProvider(widget.type), (previous, next) {
  43. _currentTotal = next.total ?? 0;
  44. });
  45. }
  46. @override
  47. void didChangeMetrics() {
  48. super.didChangeMetrics();
  49. Future.delayed(Duration(milliseconds: 500), () {
  50. if (!mounted) return;
  51. final bottomInset = View.of(context).viewInsets.bottom;
  52. if (bottomInset > 0) {
  53. ref.read(keyboardProvider.notifier).state = true;
  54. } else {
  55. ref.read(keyboardProvider.notifier).state = false;
  56. _focusNode.unfocus();
  57. }
  58. });
  59. }
  60. @override
  61. void dispose() {
  62. super.dispose();
  63. WidgetsBinding.instance.removeObserver(this);
  64. if (KeyboardUtils.isKeyboardVisible(_focusNode)) {
  65. KeyboardUtils.hideKeyboard(_focusNode);
  66. }
  67. _focusNode.dispose();
  68. }
  69. int commentIndex = 0;
  70. String replyName = '';
  71. void replyComment(int index, String reply) {
  72. KeyboardUtils.showKeyboard(context, _focusNode);
  73. consoleLog("index:$index reply:$reply");
  74. commentIndex = index;
  75. replyName = reply;
  76. }
  77. void longTapAction(int index) {
  78. showCupertinoDialog(
  79. context: context,
  80. builder:
  81. (context) => CupertinoAlertDialog(
  82. title: const Text("举报该评论"),
  83. content: const Text("您确定要执行此操作吗?"),
  84. actions: [
  85. CupertinoDialogAction(
  86. child: const Text("取消"),
  87. onPressed: () => Navigator.pop(context),
  88. ),
  89. CupertinoDialogAction(
  90. isDestructiveAction: true, // 红色警示按钮
  91. child: const Text("确定"),
  92. onPressed: () {
  93. reportComment(index);
  94. Navigator.pop(context);
  95. // 执行删除操作
  96. },
  97. ),
  98. ],
  99. ),
  100. );
  101. }
  102. void reportComment(int index) async {
  103. final comments = ref.watch(commentProvider(widget.type));
  104. final model = comments.records![index];
  105. consoleLog(
  106. "${model.fromUser?.memberId},${model.commentId},${widget.type.toString()}",
  107. );
  108. KeyboardUtils.hideKeyboard(_focusNode);
  109. final notifier = ref.read(commentProvider(widget.type).notifier);
  110. var typeStr = "article";
  111. if (widget.type == CommentType.activity) {
  112. typeStr = "activity";
  113. } else if (widget.type == CommentType.video) {
  114. typeStr = "video";
  115. } else if (widget.type == CommentType.topic) {
  116. typeStr = "topic";
  117. }
  118. await notifier.reportComment(typeStr, model.contentId ?? '');
  119. _pageSize = 1;
  120. notifier.fetchComment(page: _pageSize, articleId: widget.articleId);
  121. }
  122. void sendComment(String content, List<String> images) {
  123. KeyboardUtils.hideKeyboard(_focusNode);
  124. consoleLog("replyName:$replyName content:$content images:$images");
  125. UserModel user = ref.read(globalUserProvider);
  126. final notifier = ref.read(commentProvider(widget.type).notifier);
  127. if (replyName.isEmpty) {
  128. //新增评论
  129. notifier.addComment(
  130. widget.articleId,
  131. Comment(
  132. fromUser: FromUser(
  133. memberId: "1",
  134. nickname: user.nickName,
  135. avatar: user.avatar,
  136. ),
  137. content: content,
  138. createTime: DateUtil.getNowDateStr(),
  139. resourceUrls: images.isNotEmpty ? images : null,
  140. ),
  141. );
  142. } else {
  143. //回复评论
  144. SubComment subComment = SubComment(
  145. fromUser: FromUser(
  146. memberId: "", //当前户Id
  147. nickname: user.nickName,
  148. avatar: user.avatar,
  149. ),
  150. content: content,
  151. createTime: DateUtil.getNowDateStr(),
  152. );
  153. notifier.addReply(widget.articleId, commentIndex, subComment);
  154. replyName = '';
  155. commentIndex = 0;
  156. }
  157. }
  158. int _currentTotal = 0;
  159. int _getItemLength(NewCommentModel comments) {
  160. int length =
  161. (comments.records != null && comments.records!.isNotEmpty)
  162. ? comments.records!.length
  163. : 0;
  164. return length;
  165. }
  166. @override
  167. Widget build(BuildContext context) {
  168. final comments = ref.watch(commentProvider(widget.type));
  169. double bottomSize =
  170. ref.watch(keyboardProvider) ? KeyboardUtils.keyboardHeight(context) : 0;
  171. return Stack(
  172. children: [
  173. comments.records?.isEmpty == true
  174. ? Empty2widget()
  175. : EasyRefresh.builder(
  176. onRefresh: () async {
  177. return IndicatorResult.none;
  178. },
  179. onLoad: () async {
  180. final newData = ref.read(commentProvider(widget.type));
  181. int total = newData.total ?? 0;
  182. if (_currentTotal >= total) {
  183. return IndicatorResult.noMore;
  184. } else {
  185. _pageSize += 1;
  186. await ref
  187. .read(commentProvider(widget.type).notifier)
  188. .fetchComment(
  189. page: _pageSize,
  190. articleId: widget.articleId,
  191. );
  192. return IndicatorResult.success;
  193. }
  194. },
  195. header: const NotRefreshHeader(), // 空的 header
  196. childBuilder: (context, physics) {
  197. return ListView.separated(
  198. physics: physics,
  199. itemCount: _getItemLength(comments),
  200. padding: EdgeInsets.only(bottom: 120.h),
  201. itemBuilder: (context, index) {
  202. final comment = comments.records;
  203. if (comment == null || index >= comment.length) {
  204. return const SizedBox.shrink(); // 防止越界构建
  205. }
  206. return CommentItemWidget(
  207. replyComment,
  208. longTapAction,
  209. comment: comments.records![index],
  210. commentIndex: index,
  211. );
  212. },
  213. separatorBuilder: (_, __) => SizedBox(height: 20.h),
  214. );
  215. },
  216. ),
  217. Positioned(
  218. bottom: bottomSize,
  219. left: 0,
  220. right: 0,
  221. child: CommentInputBarWidget(
  222. _focusNode,
  223. sendComment,
  224. widget.articleId,
  225. showImageUpload: widget.type == CommentType.topic, // 仅指定话题显示图片上传
  226. ),
  227. ),
  228. ],
  229. );
  230. }
  231. }