flutter/lib/pages/chat/notify/interaction.dart

399 lines
16 KiB
Dart
Raw Normal View History

2025-09-17 15:32:18 +08:00
/// 互动通知
library;
import 'dart:convert';
2025-09-22 14:41:47 +08:00
import 'package:easy_refresh/easy_refresh.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:loopin/IM/im_service.dart';
import 'package:loopin/behavior/custom_scroll_behavior.dart';
2025-09-22 14:41:47 +08:00
import 'package:loopin/components/empty_tip.dart';
import 'package:loopin/components/network_or_asset_image.dart';
import 'package:loopin/models/conversation_type.dart';
import 'package:loopin/models/notify_message.type.dart';
import 'package:loopin/styles/index.dart';
import 'package:loopin/utils/index.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message.dart';
// interactionComment, //互动->评论
// interactionAt, //互动->视频评论中的@
// interactionLike, //互动->点赞
// interactionReply, //互动->评论回复
class Interaction extends StatefulWidget {
const Interaction({super.key});
@override
State<Interaction> createState() => InteractionState();
}
class InteractionState extends State<Interaction> with SingleTickerProviderStateMixin {
bool isLoading = false; // 是否在加载中
2025-09-22 14:41:47 +08:00
RxBool hasMore = true.obs; // 是否还有更多数据
// final RxBool _throttleFlag = false.obs; // 滚动节流锁
// final ScrollController chatController = ScrollController();
String page = '';
///-------------------
V2TimConversation? conv;
RxList<V2TimMessage> msgList = <V2TimMessage>[].obs;
final RxString selectedMessageType = '全部'.obs;
final List<Map<String, dynamic>> messageFilters = [
{'value': 'all', 'label': '全部', 'icon': Icons.all_inclusive},
{'value': NotifyMessageTypeConstants.interactionLike, 'label': '点赞', 'icon': Icons.favorite_border},
{'value': NotifyMessageTypeConstants.interactionComment, 'label': '评论', 'icon': Icons.comment},
{'value': NotifyMessageTypeConstants.interactionAt, 'label': '@我', 'icon': Icons.alternate_email},
{'value': NotifyMessageTypeConstants.interactionReply, 'label': '回复', 'icon': Icons.reply},
];
RxList demoList = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15].obs;
@override
void initState() {
super.initState();
if (Get.arguments != null && Get.arguments is V2TimConversation) {
// 如果有参数
conv = Get.arguments as V2TimConversation;
final lastmsg = conv?.lastMessage;
if (lastmsg != null) {
msgList.add(lastmsg);
}
}
2025-09-22 14:41:47 +08:00
// chatController.addListener(() {
// if (_throttleFlag.value) return;
// if (chatController.position.pixels >= chatController.position.maxScrollExtent - 50) {
// _throttleFlag.value = true;
// getMsgData().then((_) {
// // 解锁
// Future.delayed(Duration(milliseconds: 1000), () {
// _throttleFlag.value = false;
// });
// });
// }
// });
}
// 分页获取全部数据
Future<void> getMsgData() async {
// 获取最旧一条消息作为游标
V2TimMessage? lastRealMsg;
lastRealMsg = msgList.last;
final res = await ImService.instance.getHistoryMessageList(
userID: ConversationType.interaction.name, // userID为固定的interaction
lastMsg: lastRealMsg,
);
if (res.success && res.data != null) {
msgList.addAll(res.data!);
if (res.data!.isEmpty) {
2025-09-22 14:41:47 +08:00
hasMore.value = false;
}
logger.i('聊天数据加载成功');
} else {
logger.e('聊天数据加载失败:${res.desc}');
}
}
// 下拉刷新
Future<void> handleRefresh() async {
await Future.delayed(Duration(seconds: 5));
setState(() {});
}
// 获取不同的消息分类数据
void _filterMessages(String filterType) async {
logger.e(filterType);
final res = await ImService.instance.searchLocalMessages(
page: page,
conversationID: 'c2c_${ConversationType.interaction.name}',
keywordList: ['action', filterType],
);
logger.e(res.data!.toLogString());
if (res.success && res.data != null) {
final resultList = res.data?.messageSearchResultItems ?? [];
if (resultList.isNotEmpty) {
for (var item in resultList) {
logger.e(item.toJson());
msgList.addAll(item.messageList ?? []);
}
} else {
logger.e('数据为空${res.desc}');
}
}
}
// 下拉选择
void _showFilterMenu(BuildContext context) {
final double screenWidth = MediaQuery.of(context).size.width;
final double statusBarHeight = MediaQuery.of(context).padding.top;
showMenu(
context: context,
position: RelativeRect.fromLTRB(
0,
kToolbarHeight + statusBarHeight + 1,
0,
0,
),
elevation: 8,
color: Colors.white,
constraints: BoxConstraints(
minWidth: screenWidth,
maxWidth: screenWidth,
),
useRootNavigator: true, //移除默认的内边距
items: messageFilters.map((filter) {
return PopupMenuItem<String>(
value: filter['value'],
padding: EdgeInsets.zero, // 移除菜单项的内边距
child: Container(
width: screenWidth,
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 16),
decoration: BoxDecoration(
border: Border(
bottom: filter['value'] != messageFilters.last['value'] ? BorderSide(color: Colors.grey[200]!) : BorderSide.none,
),
),
child: Row(
children: [
Icon(filter['icon'], size: 22, color: Colors.grey[700]),
SizedBox(width: 16),
Expanded(
child: Text(
filter['label'],
style: TextStyle(
fontSize: 16,
color: selectedMessageType.value == filter['label'] ? Colors.orange : Colors.black,
),
),
),
if (selectedMessageType.value == filter['label']) Icon(Icons.check, size: 20, color: Colors.orange),
],
),
),
);
}).toList(),
).then((value) {
if (value != null) {
final selectedFilter = messageFilters.firstWhere((f) => f['value'] == value);
selectedMessageType.value = selectedFilter['label'];
_filterMessages(value);
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey[50],
appBar: AppBar(
centerTitle: true,
forceMaterialTransparency: true,
bottom: PreferredSize(
preferredSize: Size.fromHeight(1.0),
child: Container(
color: Colors.grey[300],
height: 1.0,
),
),
title: Obx(
() => GestureDetector(
onTap: () => _showFilterMenu(context),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const SizedBox(width: 4),
Text(
selectedMessageType.value,
style: TextStyle(
color: Colors.black,
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
Icon(Icons.arrow_drop_down, color: Colors.black, size: 20),
],
),
),
),
),
actions: [],
),
body: ScrollConfiguration(
behavior: CustomScrollBehavior().copyWith(scrollbars: false),
child: Column(
children: [
Expanded(
2025-09-22 14:41:47 +08:00
child: EasyRefresh.builder(
callLoadOverOffset: 20, //触底距离
callRefreshOverOffset: 20, // 下拉距离
header: ClassicHeader(
dragText: '下拉刷新',
armedText: '释放刷新',
readyText: '加载中...',
processingText: '加载中...',
processedText: '加载完成',
failedText: '加载失败,请重试',
messageText: '最后更新于 %T',
),
footer: ClassicFooter(
dragText: '加载更多',
armedText: '释放加载',
readyText: '加载中...',
processingText: '加载中...',
processedText: '加载完成',
noMoreText: '没有更多了~',
failedText: '加载失败,请重试',
messageText: '最后更新于 %T',
),
onRefresh: () async {
await handleRefresh();
},
onLoad: () async {
if (hasMore.value) {
await getMsgData();
return hasMore.value ? IndicatorResult.success : IndicatorResult.noMore;
}
},
childBuilder: (context, physics) {
return Obx(
() {
// if (msgList.isEmpty) return EmptyTip();
if (demoList.isEmpty) return EmptyTip();
return ListView.builder(
// controller: chatController,
shrinkWrap: true,
physics: physics,
// itemCount: msgList.length,
itemCount: demoList.length,
itemBuilder: (context, index) {
//检测cloudCustomData
// interactionComment, //互动->评论 评论
// interactionAt, //互动->视频评论中的@ 评论
// interactionLike, //互动->点赞 视频(暂时不涉及评论点赞)
// interactionReply, //互动->评论回复 评论
2025-09-22 14:41:47 +08:00
//----
// final element =msgList[index].customElem!;
// final cloudCustomData = msgList[index].cloudCustomData;
// final desc = msgList[index].customElem!.desc!;
// final jsonData = msgList[index].customElem!.data;
2025-09-22 14:41:47 +08:00
// ----测试数据
final jsonData = '{"faceUrl":"","nickName":"测试昵称","userID":"213213"}';
final item = jsonDecode(jsonData); // 数据
final desc = '测试desc';
final cloudCustomData = 'interactionLike';
V2TimMessage element = V2TimMessage(elemType: 2, isRead: index > 2 ? true : false);
return Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 10.0),
child: Row(
spacing: 10.0,
children: <Widget>[
// 头像
InkWell(
onTap: () async {
// 点击头像转到对方主页
// 先获取视频详情
2025-09-22 14:41:47 +08:00
// 如果cloudCustomData是interactionCommentinteractionAtinteractionReply,传参时带上评论id这三个是评论相关的
//
// final res = await Http.get('${VideoApi.detail}/${item['vlogID']}');
2025-09-22 14:41:47 +08:00
// 此人存在才做跳转
// Get.toNamed('/vloger', arguments: res['data']);
Get.toNamed('/vloger');
},
2025-09-22 14:41:47 +08:00
child: ClipOval(
child: NetworkOrAssetImage(
imageUrl: item['faceUrl'],
width: 50,
height: 50,
),
),
),
// 消息
Expanded(
child: InkWell(
onTap: () {
// 点击头像转到对方主页
// 先获取视频详情
// 如果cloudCustomData是interactionCommentinteractionAtinteractionReply,传参时带上评论id
//
// final res = await Http.get('${VideoApi.detail}/${item['vlogID']}');
// Get.toNamed('/vloger', arguments: res['data']);
Get.toNamed('/vloger');
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// 昵称
Text(
item['nickName'] ?? '未知',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.normal,
),
),
2025-09-22 14:41:47 +08:00
const SizedBox(height: 2.0),
// 描述内容
Text(
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(color: Colors.grey, fontSize: 12.0),
// desc,
'很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容',
),
2025-09-22 14:41:47 +08:00
Text(
Utils.formatTime(element.timestamp ?? DateTime.now().millisecondsSinceEpoch ~/ 1000),
style: TextStyle(
color: Colors.grey[600],
fontSize: 12,
),
),
],
),
),
),
2025-09-22 14:41:47 +08:00
// 右侧
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Visibility(
visible: true,
// 视频首图
child: NetworkOrAssetImage(
imageUrl: item['firstFrameImg'],
placeholderAsset: 'assets/images/bk.jpg',
width: 40,
height: 60,
),
),
2025-09-22 14:41:47 +08:00
const SizedBox(width: 5.0),
// 角标
Visibility(
visible: !(element.isRead ?? true),
child: FStyle.badge(0, isdot: true),
),
],
),
],
),
);
},
);
},
);
},
),
),
],
),
),
);
}
}