flutter/lib/pages/groupChat/groupDetail.dart
2025-09-17 15:32:18 +08:00

542 lines
21 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:loopin/IM/controller/chat_detail_controller.dart';
import 'package:loopin/IM/im_service.dart';
import 'package:loopin/api/common_api.dart';
import 'package:loopin/components/my_confirm.dart';
import 'package:loopin/components/my_toast.dart';
import 'package:loopin/components/network_or_asset_image.dart';
import 'package:loopin/pages/groupChat/components/invite_action_sheet.dart';
import 'package:loopin/pages/groupChat/components/member_action_sheet.dart';
import 'package:loopin/pages/groupChat/components/set_group_info.dart';
import 'package:loopin/pages/groupChat/controller/group_detail_controller.dart';
import 'package:loopin/service/http.dart';
import 'package:loopin/utils/index.dart';
import 'package:loopin/utils/permissions.dart';
import 'package:shirne_dialog/shirne_dialog.dart';
import 'package:tencent_cloud_chat_sdk/enum/group_type.dart';
import 'package:tencent_cloud_chat_sdk/enum/receive_message_opt.dart';
import 'package:tencent_cloud_chat_sdk/enum/receive_message_opt_enum.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_info.dart';
import 'package:wechat_assets_picker/wechat_assets_picker.dart';
class Groupdetail extends StatefulWidget {
const Groupdetail({super.key});
// final String groupID;
// const Groupdetail({super.key, required this.groupID});
@override
State<Groupdetail> createState() => GroupdetailState();
}
class GroupdetailState extends State<Groupdetail> {
late final GroupDetailController controller;
@override
void initState() {
super.initState();
// 跳转前先put
controller = Get.find<GroupDetailController>();
// controller = Get.put(GroupDetailController(groupID: widget.groupID));
}
///设置群头像
void pickFaceUrl(BuildContext context) async {
final hasPer = await Permissions.requestPhotoPermission();
if (!hasPer) {
Permissions.showPermissionDialog('相册');
return;
}
final pickedAssets = await AssetPicker.pickAssets(
context,
pickerConfig: AssetPickerConfig(
textDelegate: const AssetPickerTextDelegate(),
pathNameBuilder: (AssetPathEntity album) {
return Utils.translateAlbumName(album);
},
maxAssets: 1,
requestType: RequestType.image,
filterOptions: FilterOptionGroup(
imageOption: const FilterOption(),
),
),
);
if (pickedAssets != null && pickedAssets.isNotEmpty) {
final asset = pickedAssets.first;
final file = await asset.file; // 获取实际文件
if (file != null) {
final fileSizeInBytes = await file.length();
final sizeInMB = fileSizeInBytes / (1024 * 1024);
if (sizeInMB > 20) {
MyDialog.toast('图片大小不能超过20MB', icon: const Icon(Icons.check_circle), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200)));
} else {
logger.w("图片合法,大小:$sizeInMB MB");
//走upload(file)上传图片拿到url地址
final istance = MyDialog.loading('上传中', duration: Duration(minutes: 1));
final res = await Http.upload(CommonApi.uploadFile, filePath: file.path);
final imgUrl = res['data']['url'];
logger.e(imgUrl);
// 设置群头像
await controller.setGroupInfo(
changedInfo: V2TimGroupInfo(
groupID: controller.info.value!.groupID,
groupType: GroupType.Work,
faceUrl: imgUrl,
),
);
istance.close();
}
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey[50],
appBar: AppBar(
centerTitle: true,
forceMaterialTransparency: true,
title: const Text(
"群资料",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () {
Get.back(result: controller.info.value?.groupName ?? '');
},
),
bottom: PreferredSize(
preferredSize: const Size.fromHeight(1),
child: Container(height: 1, color: Colors.grey[300]),
),
),
body: ListView(
children: [
// 介绍
ListTile(
// 群头像
leading: Obx(
() => GestureDetector(
onTap: () {
// 编辑头像
if (controller.isOwner.value) {
//
pickFaceUrl(context);
}
},
child: ClipOval(
child: NetworkOrAssetImage(
imageUrl: controller.info.value?.faceUrl ?? '',
placeholderAsset: 'assets/images/group.png',
height: 60,
width: 60,
),
),
),
),
// 群名称
title: Obx(
() => GestureDetector(
onTap: () {
// 去setinfo页
logger.w('点了名称');
if (controller.isOwner.value) {
Get.to(
() => SetGroupInfoPage(
appBarTitle: "修改群名称",
fieldLabel: "群名称",
maxLines: 1,
maxLength: 20,
initialValue: controller.info.value?.groupName ?? "",
onSubmit: (value) async {
// 修改群名称
await controller.setGroupInfo(
changedInfo: V2TimGroupInfo(
groupID: controller.info.value!.groupID,
groupType: GroupType.Work,
groupName: value,
),
);
},
),
);
}
},
child: Text(
Utils.handleText(controller.info.value?.groupName, '', "未命名群聊"),
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
),
// 群简介
subtitle: Obx(
() => GestureDetector(
onTap: () {
// 去setinfo页
logger.w('点了简介绍');
if (controller.isOwner.value) {
Get.to(
() => SetGroupInfoPage(
appBarTitle: "修改群简介",
fieldLabel: "群简介",
maxLines: 5,
maxLength: 100,
initialValue: controller.info.value?.introduction ?? "",
onSubmit: (value) async {
// 修改群名称
await controller.setGroupInfo(
changedInfo: V2TimGroupInfo(
groupID: controller.info.value!.groupID,
groupType: GroupType.Work,
introduction: value,
),
);
},
),
);
}
},
child: Text(
Utils.handleText(controller.info.value?.introduction, '', "暂无群介绍"),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(color: Colors.grey),
),
),
),
trailing: const Icon(Icons.chevron_right),
),
const Divider(height: 1),
// 群成员
ListTile(
title: Obx(
() => Text("群成员(${controller.info.value?.memberCount ?? 0}/${controller.info.value?.memberMaxCount ?? 0}"),
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
Obx(
() => Text("查看${controller.info.value?.memberCount ?? 0}个群成员", style: const TextStyle(color: Colors.grey, fontSize: 14)),
),
const Icon(Icons.chevron_right),
],
),
onTap: () {
// 群成员列表
showModalBottomSheet(
context: context,
barrierColor: Colors.white,
isScrollControlled: true,
useSafeArea: true,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
),
builder: (context) {
return MemberActionSheet(
showButton: false,
groupID: controller.groupID,
title: '群成员',
showSelf: true,
onAction: (userIDs) {
//
},
);
},
);
},
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
child: Wrap(
spacing: 16,
runSpacing: 12,
children: [
Obx(
() {
return Wrap(
spacing: 16,
runSpacing: 12,
children: controller.memberList.take(8).map((m) {
// 点击成员头像
return GestureDetector(
onTap: () {
final currentUserID = controller.selfInfo.value?.userID ?? '';
if (m.userID != currentUserID) {
Get.toNamed('/vloger', arguments: {'memberId': m.userID});
}
},
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ClipOval(
child: NetworkOrAssetImage(
imageUrl: m.faceUrl,
height: 50,
width: 50,
),
),
const SizedBox(height: 4),
SizedBox(
width: 50,
child: Text(
m.nickName ?? '未知昵称',
maxLines: 1,
softWrap: false,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontSize: 12),
),
),
],
),
);
}).toList(),
);
},
),
// 邀请 +
GestureDetector(
onTap: () {
//检测群人数上限,
logger.w('当前人数${controller.info.value?.memberCount ?? 0}---上限人数${controller.info.value?.memberMaxCount ?? 0}');
if ((controller.info.value?.memberCount ?? 0) < (controller.info.value?.memberMaxCount ?? 0)) {
// 群人数未达到上限,可以继续加人
showModalBottomSheet(
context: context,
barrierColor: Colors.white,
isScrollControlled: true,
useSafeArea: true,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
),
builder: (context) {
return InviteActionSheet(
groupID: controller.groupID,
title: '邀请新成员',
onAction: (userIDs) {
logger.w("准备添加群成员: $userIDs");
controller.inviteUserToGroup(userList: userIDs);
},
);
},
);
} else {
MyToast().tip(title: '群人数已达上限', position: 'top');
}
},
child: Column(
children: [
CircleAvatar(
radius: 25,
child: const Icon(Icons.add),
),
const SizedBox(height: 4),
const Text("邀请", style: TextStyle(fontSize: 12)),
],
),
),
// 移除 -
Obx(
() => controller.isOwner.value
? GestureDetector(
onTap: () {
showModalBottomSheet(
context: context,
barrierColor: Colors.white,
isScrollControlled: true,
useSafeArea: true,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
),
builder: (context) {
return MemberActionSheet(
groupID: controller.groupID,
title: '移出成员',
onAction: (userIDs) {
logger.w("准备移除群成员: $userIDs");
controller.kickGroupMember(userIDs);
},
);
},
);
},
child: Column(
children: [
const CircleAvatar(
radius: 25,
child: Icon(Icons.remove),
),
const SizedBox(height: 4),
const Text("移除", style: TextStyle(fontSize: 12)),
],
),
)
: SizedBox.shrink(),
)
],
),
),
const Divider(height: 1),
// 群公告
ListTile(
title: const Text("群公告"),
subtitle: Obx(
() => Text(
Utils.handleText(controller.info.value?.notification, '', "暂无公告"),
maxLines: 3,
overflow: TextOverflow.ellipsis,
),
),
trailing: const Icon(Icons.chevron_right),
onTap: () {
logger.w('点击了群公告');
if (controller.isOwner.value) {
Get.to(
() => SetGroupInfoPage(
appBarTitle: "修改群公告",
fieldLabel: "群公告",
maxLines: 8,
maxLength: 200,
initialValue: controller.info.value?.notification ?? "",
onSubmit: (value) async {
// 修改群公告
await controller.setGroupInfo(
changedInfo: V2TimGroupInfo(
groupID: controller.info.value!.groupID,
groupType: GroupType.Work,
notification: value,
),
);
},
),
);
}
},
),
const Divider(height: 1),
// 我的本群昵称
ListTile(
title: const Text("我的本群昵称"),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
Obx(
() {
return Text(
Utils.handleText(controller.selfInfo.value?.nameCard, controller.selfInfo.value?.nickName, '未设置昵称'),
style: TextStyle(fontSize: 14),
);
},
),
const Icon(Icons.chevron_right),
],
),
onTap: () async {
Get.to(
() => SetGroupInfoPage(
appBarTitle: "修改群昵称",
fieldLabel: "群昵称",
maxLines: 1,
maxLength: 12,
initialValue: controller.selfInfo.value?.nameCard ?? "",
onSubmit: (value) {
// 修改自己的群昵称
controller.setSelfInfo(nameCard: value);
},
),
);
},
),
const Divider(height: 1),
// 消息免打扰
Obx(
() => SwitchListTile(
title: Text("消息免打扰"),
value: controller.info.value?.recvOpt == ReceiveMsgOptType.kTIMRecvMsgOpt_Not_Notify_Except_At, // 暂时只提供0和3
activeColor: Colors.green,
inactiveThumbColor: Colors.grey,
inactiveTrackColor: Colors.white,
onChanged: (v) async {
if (controller.info.value != null) {
logger.w(v);
logger.e(controller.info.value?.recvOpt);
// v=true -> 开启免打扰 = 3
final res = await ImService.instance.setGroupReceiveMessageOpt(
groupID: controller.groupID,
opt: v ? ReceiveMsgOptEnum.V2TIM_RECEIVE_NOT_NOTIFY_MESSAGE_EXCEPT_AT : ReceiveMsgOptEnum.V2TIM_RECEIVE_MESSAGE, // 关闭免打扰 = 0
);
if (res.success) {
controller.info.value!.recvOpt =
v ? ReceiveMsgOptType.kTIMRecvMsgOpt_Not_Notify_Except_At : ReceiveMsgOptType.kTIMRecvMsgOpt_Receive; // 关闭免打扰 = 0 开启=3
controller.info.refresh();
} else {
MyToast().tip(title: '网络繁忙,请稍后再试', position: 'top');
}
}
// 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;
// }
},
),
),
const Divider(height: 1),
// 举报
// ListTile(
// title: const Text("举报"),
// trailing: const Icon(Icons.chevron_right),
// onTap: () {
// //
// },
// ),
// 退出群聊
const SizedBox(height: 20),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
minimumSize: const Size(double.infinity, 48),
),
onPressed: () async {
// 二次确认
final confirmed = await ConfirmDialog.show(
title: "提示",
content: "确认要退出群聊吗?",
);
if (confirmed == true) {
await controller.quitGroup();
Get.find<ChatDetailController>().toolFlag.value = false; // 禁用工具栏
Get.back(); // 返回上一个页面
}
},
child: const Text("退出群聊", style: TextStyle(color: Colors.white)),
),
),
const SizedBox(height: 30),
],
),
);
}
}