flutter/lib/pages/groupChat/groupDetail.dart

542 lines
21 KiB
Dart
Raw Normal View History

2025-09-13 17:01:01 +08:00
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),
],
),
);
}
}