/// 聊天首页模板 library; import 'package:flutter/material.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; import 'package:loopin/IM/controller/chat_controller.dart'; import 'package:loopin/IM/global_badge.dart'; import 'package:loopin/IM/im_service.dart'; import 'package:loopin/api/shop_api.dart'; import 'package:loopin/components/network_or_asset_image.dart'; import 'package:loopin/components/scan_util.dart'; import 'package:loopin/models/conversation_type.dart'; import 'package:loopin/models/conversation_view_model.dart'; import 'package:loopin/pages/chat/menu/add_friend.dart'; import 'package:loopin/service/http.dart'; import 'package:loopin/utils/index.dart'; import 'package:loopin/utils/parse_message_summary.dart'; import 'package:loopin/utils/scan_code_type.dart'; // 导入外部枚举 import 'package:shirne_dialog/shirne_dialog.dart'; import 'package:tencent_cloud_chat_sdk/enum/receive_message_opt.dart'; import '../../behavior/custom_scroll_behavior.dart'; import '../../styles/index.dart'; class ChatPage extends StatefulWidget { const ChatPage({super.key}); @override State createState() => ChatPageState(); } class ChatPageState extends State { late final ChatController controller; @override void initState() { super.initState(); controller = Get.find(); } void deletConv(context, ConversationViewModel item) async { final res = await ImService.instance.deleteConversation(conversationID: item.conversation.conversationID); if (res.success) { Navigator.of(context).pop(); controller.chatList.remove(item); } } // 长按坐标点 double posDX = 0.0; double posDY = 0.0; late RxInt followed = 0.obs; // 是否关注 // 下拉刷新 Future handleRefresh() async { await Future.delayed(Duration(seconds: 1)); setState(() {}); } // 处理扫码结果 void handleScanResult(String code) { print('扫码结果11111111111111111111111:$code'); // 使用外部枚举的方法检查扫码类型 final scanType = ScanCodeType.fromCode(code); if (scanType != null) { final value = code.substring(scanType.prefix.length + 1); // 获取后缀值 _processScanCode(scanType, value); } else { // 未知类型的码 Get.snackbar('未知的二维码类型', '无法识别此二维码: $code'); } } // 处理不同类型的扫码结果 void _processScanCode(ScanCodeType type, String value) { switch (type) { case ScanCodeType.verification: _handleVerificationCode(value); break; case ScanCodeType.friend: _handleFriendCode(value); break; case ScanCodeType.promotion: _handlePromotionCode(value); break; } } // 处理核销码 void _handleVerificationCode(String value) async { print('处理核销码: $value'); // 带着核销码,跳转到商家的商品详情页面,引导商家去手动点击核销按钮 Get.toNamed('/sellerOrder/detail', arguments: {'writeOffCodeId': value}); } // 处理好友码 void _handleFriendCode(String value) { print('处理好友码: $value'); // 模仿抖音,去个人页面手动点击关注 Get.toNamed('/vloger', arguments: {'memberId': value}); } // 检测当前用户是否关注博主 checkFollowType(memberId) async { /// 0:不是好友也没有关注 /// 1:你关注了对方(单向) /// 2:对方关注了你(单向) /// 3:互相关注(双向好友) final res = await ImService.instance.checkFollowType(userIDList: [memberId]); if (res.success) { final followType = res.data?.first.followType ?? 0; logger.i(res.data?.first.toJson()); followed.value = followType; logger.i(followed.value); } } // 处理推广码,先调用IM互相关注逻辑,成功后在调用绑定关系接口 void _handlePromotionCode(String value) async { await checkFollowType(value); // 检查当前用户是否关注了团长 if(followed.value == 0 || followed.value == 2){ final res = await ImService.instance.followUser(userIDList: [value]); if (res.success) { followed.value = followed.value == 0 ? 1 : 3; final res = await Http.post(ShopApi.bindSpreadCodeId, data: {"socialCode": value}); if (res != null && res['code'] == 200) { MyDialog.toast('推广码绑定成功', icon: const Icon(Icons.check_circle), style: ToastStyle(backgroundColor: Colors.green.withAlpha(200))); Get.toNamed('/vloger', arguments: {'memberId': value}); } } }else{ Get.toNamed('/vloger', arguments: {'memberId': value}); } } // 长按菜单 void showContextMenu(BuildContext context, ConversationViewModel item) { return; bool isLeft = posDX > MediaQuery.of(context).size.width / 2 ? false : true; bool isTop = posDY > MediaQuery.of(context).size.height / 2 ? false : true; showDialog( context: context, barrierColor: Colors.black12, // 遮罩透明 builder: (context) { return Stack( children: [ Positioned( top: isTop ? posDY : posDY - 135, left: isLeft ? posDX : posDX - 135, width: 135, child: Material( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15.0)), color: Colors.white, elevation: 2.0, clipBehavior: Clip.hardEdge, child: Column( children: [ ListTile( title: const Text( '设为免打扰', style: TextStyle(color: Colors.black87, fontSize: 14.0), ), dense: true, onTap: () {}, ), ListTile( title: const Text( '置顶消息', style: TextStyle(color: Colors.black87, fontSize: 14.0), ), dense: true, onTap: () {}, ), ListTile( title: const Text( '不显示该消息', style: TextStyle(color: Colors.black87, fontSize: 14.0), ), dense: true, onTap: () {}, ), ListTile( title: const Text( '删除', style: TextStyle(color: Colors.black87, fontSize: 14.0), ), dense: true, onTap: () { deletConv(context, item); }, ) ], ), ), ) ], ); }, ); } @override Widget build(BuildContext context) { return Scaffold( // backgroundColor: Colors.grey[50], appBar: AppBar( forceMaterialTransparency: true, title: Row( spacing: 8.0, children: [ Text('消息'), Container( padding: EdgeInsets.symmetric(horizontal: 6.0, vertical: 4.0), decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(20.0), boxShadow: [ BoxShadow( color: Colors.black.withAlpha(20), offset: Offset(0.0, 1.0), blurRadius: 2.0, spreadRadius: 0.0, ), ]), child: InkWell( onTap: () async { if (Get.find().totalUnread > 0) { final res = await ImService.instance.clearConversationUnreadCount(conversationID: ''); if (res.success) { MyDialog.toast('操作成功', icon: const Icon(Icons.check_circle), style: ToastStyle(backgroundColor: Colors.green.withAlpha(200))); } else { MyDialog.toast(res.desc, icon: Icon(Icons.warning), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200))); } } }, child: Row( spacing: 3.0, children: [ Icon( Icons.cleaning_services_sharp, size: 14.0, ), Text( '清除未读', style: TextStyle(fontSize: 12.0), ) ], ), )), ], ), actions: [ /// 先不做搜索功能了后面再说 // IconButton( // icon: const Icon(Icons.search), // onPressed: () {}, // ), IconButton( icon: const Icon(Icons.add_circle_outline), onPressed: () async { final paddingTop = MediaQuery.of(Get.context!).padding.top; final selected = await showMenu( context: Get.context!, position: RelativeRect.fromLTRB( double.infinity, kToolbarHeight + paddingTop - 12, 8, double.infinity, ), color: FStyle.primaryColor, elevation: 8, items: [ PopupMenuItem( value: 'group', child: Row( children: [ Icon(Icons.group, color: Colors.white, size: 18), SizedBox(width: 8), Text( '发起群聊', style: TextStyle(color: Colors.white), ), ], ), ), PopupMenuItem( value: 'friend', child: Row( children: [ Icon(Icons.person_add, color: Colors.white, size: 18), SizedBox(width: 8), Text( '添加朋友', style: TextStyle(color: Colors.white), ), ], ), ), PopupMenuItem( value: 'scan', child: Row( children: [ Icon(Icons.qr_code_scanner, color: Colors.white, size: 18), SizedBox(width: 8), Text( '扫一扫', style: TextStyle(color: Colors.white), ), ], ), ), ], ); if (selected != null) { switch (selected) { case 'group': logger.w('点击了发起群聊'); Get.toNamed('/group'); break; case 'friend': logger.w('点击了添加朋友'); Get.to(() => AddFriend()); break; case 'scan': logger.w('点击了扫一扫'); ScanUtil.openScanner(onResult: handleScanResult); break; } } }, ), ], ), body: ScrollConfiguration( behavior: CustomScrollBehavior().copyWith(scrollbars: false), child: Column( children: [ Container( margin: EdgeInsets.symmetric(horizontal: 15.0, vertical: 10.0), padding: EdgeInsets.all(10.0), decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(15.0), boxShadow: [ BoxShadow( color: Colors.black.withAlpha(10), offset: Offset(0.0, 1.0), blurRadius: 2.0, spreadRadius: 0.0, ), ]), child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ GestureDetector( onTap: () { // 去群聊列表 Get.toNamed('/groupList'); }, child: Column( children: [ SvgPicture.asset( 'assets/images/svg/order.svg', height: 36.0, width: 36.0, ), Text('群聊'), ], ), ), GestureDetector( onTap: () { //互关 Get.toNamed('/eachFlow'); }, child: Column( children: [ SvgPicture.asset( 'assets/images/svg/kefu.svg', height: 36.0, width: 36.0, ), Text('互关'), ], ), ), GestureDetector( onTap: () { // 粉丝 Get.toNamed('/fans'); }, child: Column( children: [ SvgPicture.asset( 'assets/images/svg/comment.svg', height: 36.0, width: 36.0, ), Text('粉丝'), ], ), ), GestureDetector( onTap: () { // 关注 Get.toNamed('/flow'); }, child: Column( children: [ SvgPicture.asset( 'assets/images/svg/comment.svg', height: 36.0, width: 36.0, ), Text('关注'), ], ), ), ], ), ), Expanded( child: Obx( () { final chatList = controller.chatList; return ListView.builder( shrinkWrap: true, physics: BouncingScrollPhysics(), itemCount: chatList.length, itemBuilder: (context, index) { // logger.w(chatList[index].conversation.conversationGroupList); // logger.w(chatList[index].isCustomAdmin); // logger.w(chatList[index].conversation.recvOpt); final item = chatList[index]; final bool quiet = [ReceiveMsgOptType.kTIMRecvMsgOpt_Not_Notify_Except_At].contains(chatList[index].conversation.recvOpt ?? 0) ? true : false; // 是否设置了免打扰 final isNoFriend = chatList[index].conversation.conversationGroupList?.contains(ConversationType.noFriend.name) ?? false; final isAdmin = chatList[index].isCustomAdmin != null && (chatList[index].isCustomAdmin?.isNotEmpty ?? false) && chatList[index].isCustomAdmin != '0'; final placeholderAsset = chatList[index].conversation.type == 2 ? 'assets/images/group.png' : 'assets/images/avatar/default.png'; // logger.e(chatList[index].isCustomAdmin); return Slidable( key: ValueKey(chatList[index].conversation.conversationID), endActionPane: ActionPane( motion: const DrawerMotion(), // 可以改成 StretchMotion 或 ScrollMotion ,DrawerMotion children: [ SlidableAction( onPressed: (_) { // }, backgroundColor: Colors.blue, foregroundColor: Colors.white, icon: Icons.delete, label: '已读', ), if (!(isAdmin || isNoFriend)) SlidableAction( onPressed: (_) { // }, backgroundColor: FStyle.primaryColor, foregroundColor: Colors.white, icon: quiet ? Icons.notifications_off : Icons.notifications, label: quiet ? '取消' : '免打扰', ), ], ), child: Ink( // color: chatList[index]['topMost'] == null ? Colors.white : Colors.grey[100], //置顶颜色 child: InkWell( key: ValueKey(chatList[index].conversation.conversationID), splashColor: Colors.grey[200], child: Container( padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 10.0), child: Row( spacing: 10.0, children: [ // 头图 ClipOval( child: NetworkOrAssetImage( imageUrl: chatList[index].faceUrl, width: 50, height: 50, placeholderAsset: placeholderAsset, ), ), // 消息 Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 昵称 Text( chatList[index].conversation.showName ?? '未知', maxLines: 1, softWrap: false, overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: (isAdmin || isNoFriend) ? 20 : 16, fontWeight: (isAdmin || isNoFriend) ? FontWeight.bold : FontWeight.normal), ), const SizedBox(height: 2.0), // 消息内容 Text( chatList[index].conversation.lastMessage != null ? parseMessageSummary(chatList[index].conversation.lastMessage!) : '', style: const TextStyle(color: Colors.grey, fontSize: 13.0), overflow: TextOverflow.ellipsis, maxLines: 1, softWrap: false, ), ], ), ), // 右侧 Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ Visibility( visible: !(isAdmin || isNoFriend), child: Text( // 转成日期字符串显示 // DateTime.fromMillisecondsSinceEpoch( // (chatList[index].conversation.lastMessage!.timestamp ?? 0) * 1000, // ).toLocal().toString().substring(0, 16), // 简单截取年月日时分 Utils.formatTime( chatList[index].conversation.lastMessage!.timestamp ?? DateTime.now().millisecondsSinceEpoch ~/ 1000), style: const TextStyle(color: Colors.grey, fontSize: 12.0), ), ), const SizedBox(height: 5.0), // 数字角标 // class ReceiveMsgOptType { // 在线正常接收消息,离线时会进行 APNs 推送 // static const int kTIMRecvMsgOpt_Receive = 0; // 不会接收到消息,离线不会有推送通知 // static const int kTIMRecvMsgOpt_Not_Receive = 1; // 在线正常接收消息,离线不会有推送通知 // static const int kTIMRecvMsgOpt_Not_Notify = 2; // 在线接收消息,离线只接收 at 消息的推送 // static const int kTIMRecvMsgOpt_Not_Notify_Except_At = 3; // 在线和离线都只接收@消息 // static const int kTIMRecvMsgOpt_Not_Receive_Except_At = 4; // } // 现阶段只允许设置为0和3 Visibility( visible: (chatList[index].conversation.unreadCount ?? 0) > 0, child: FStyle.badge(chatList[index].conversation.unreadCount ?? 0, color: quiet ? Colors.grey : Colors.red), ), ], ), Visibility( visible: (isAdmin || isNoFriend), child: const Icon( Icons.arrow_forward_ios, color: Colors.blueGrey, size: 14.0, ), ), ], ), ), onTap: () { if (conversationTypeFromString(chatList[index].isCustomAdmin) != null) { // 跳转对应的通知消息页 logger.e(chatList[index].isCustomAdmin); Get.toNamed('/${chatList[index].isCustomAdmin}', arguments: chatList[index].conversation); } else if (chatList[index].conversation.conversationGroupList!.contains(ConversationType.noFriend.name)) { // 跳转陌生人消息页面 logger.e(chatList[index].conversation.conversationGroupList); Get.toNamed('/noFriend'); } else if (chatList[index].conversation.type == 2) { // 跳转群聊 type=0非法,1=单聊,2=群聊 Get.toNamed('/chatGroup', arguments: chatList[index].conversation); } else { // 会话id查询会话详情 Get.toNamed('/chat', arguments: chatList[index].conversation); } }, onTapDown: (TapDownDetails details) { posDX = details.globalPosition.dx; posDY = details.globalPosition.dy; }, onLongPress: () { showContextMenu(context, chatList[index]); }, ), ), ); }, ); }, ), ), ], ), ), ); } }