2025-09-06 14:57:47 +08:00
|
|
|
// 创建群聊
|
2025-09-03 11:25:31 +08:00
|
|
|
import 'package:easy_refresh/easy_refresh.dart';
|
|
|
|
import 'package:flutter/material.dart';
|
2025-09-06 14:57:47 +08:00
|
|
|
import 'package:get/get.dart';
|
|
|
|
import 'package:loopin/IM/controller/im_user_info_controller.dart';
|
2025-09-13 17:01:01 +08:00
|
|
|
import 'package:loopin/IM/im_message.dart';
|
2025-09-06 14:57:47 +08:00
|
|
|
import 'package:loopin/IM/im_service.dart';
|
|
|
|
import 'package:loopin/components/network_or_asset_image.dart';
|
|
|
|
import 'package:loopin/styles/index.dart';
|
|
|
|
import 'package:tencent_cloud_chat_sdk/enum/group_member_role_enum.dart';
|
|
|
|
import 'package:tencent_cloud_chat_sdk/enum/group_type.dart';
|
2025-09-13 17:01:01 +08:00
|
|
|
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart';
|
2025-09-06 14:57:47 +08:00
|
|
|
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_member.dart';
|
|
|
|
import 'package:tencent_cloud_chat_sdk/models/v2_tim_user_full_info.dart';
|
2025-09-03 11:25:31 +08:00
|
|
|
|
|
|
|
class StartGroupChatPage extends StatefulWidget {
|
|
|
|
const StartGroupChatPage({super.key});
|
|
|
|
|
|
|
|
@override
|
|
|
|
State<StartGroupChatPage> createState() => _StartGroupChatPageState();
|
|
|
|
}
|
|
|
|
|
|
|
|
class _StartGroupChatPageState extends State<StartGroupChatPage> {
|
|
|
|
final TextEditingController _searchController = TextEditingController();
|
|
|
|
|
2025-09-06 14:57:47 +08:00
|
|
|
List<V2TimUserFullInfo> dataList = [];
|
|
|
|
List<V2TimUserFullInfo> filteredList = [];
|
2025-09-03 11:25:31 +08:00
|
|
|
Set<String> selectedIds = {}; // 已选中的用户 id
|
2025-09-06 14:57:47 +08:00
|
|
|
String page = '';
|
2025-09-03 11:25:31 +08:00
|
|
|
bool hasMore = true;
|
2025-09-06 14:57:47 +08:00
|
|
|
bool isLoading = true;
|
2025-09-13 17:01:01 +08:00
|
|
|
bool makeGroup = true;
|
2025-09-03 11:25:31 +08:00
|
|
|
|
|
|
|
@override
|
|
|
|
void initState() {
|
|
|
|
super.initState();
|
|
|
|
_loadData(reset: true);
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void dispose() {
|
|
|
|
_searchController.dispose();
|
|
|
|
super.dispose();
|
|
|
|
}
|
|
|
|
|
2025-09-13 17:01:01 +08:00
|
|
|
// 分页获取关注列表数据
|
2025-09-03 11:25:31 +08:00
|
|
|
Future<void> _loadData({bool reset = false}) async {
|
|
|
|
if (reset) {
|
2025-09-06 14:57:47 +08:00
|
|
|
page = '';
|
2025-09-03 11:25:31 +08:00
|
|
|
hasMore = true;
|
|
|
|
dataList.clear();
|
|
|
|
}
|
2025-09-06 14:57:47 +08:00
|
|
|
final res = await ImService.instance.getMutualFollowersList(
|
|
|
|
nextCursor: page,
|
2025-09-03 11:25:31 +08:00
|
|
|
);
|
2025-09-06 14:57:47 +08:00
|
|
|
if (res.success && res.data != null) {
|
|
|
|
final userInfoList = res.data!.userFullInfoList ?? [];
|
|
|
|
final isFinished = res.data!.nextCursor == null || res.data!.nextCursor!.isEmpty;
|
|
|
|
logger.w('获取成功:${res.data!.nextCursor},是否还有更多:$isFinished');
|
2025-09-03 11:25:31 +08:00
|
|
|
|
2025-09-06 14:57:47 +08:00
|
|
|
if (isFinished) {
|
|
|
|
setState(() {
|
|
|
|
hasMore = false;
|
|
|
|
});
|
|
|
|
// 加载没数据了
|
|
|
|
page = '';
|
|
|
|
} else {
|
|
|
|
page = res.data!.nextCursor ?? '';
|
|
|
|
}
|
|
|
|
logger.i('获取数据成功:$userInfoList');
|
|
|
|
setState(() {
|
|
|
|
dataList.addAll(userInfoList);
|
|
|
|
_applySearch(_searchController.text);
|
|
|
|
});
|
2025-09-03 11:25:31 +08:00
|
|
|
} else {
|
2025-09-06 14:57:47 +08:00
|
|
|
logger.e('获取数据失败:${res.desc}');
|
2025-09-03 11:25:31 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void _applySearch(String query) {
|
|
|
|
setState(() {
|
|
|
|
if (query.isEmpty) {
|
|
|
|
filteredList = List.from(dataList);
|
|
|
|
} else {
|
2025-09-06 14:57:47 +08:00
|
|
|
filteredList = dataList.where((item) => (item.nickName ?? '').contains(query.trim())).toList();
|
2025-09-03 11:25:31 +08:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
void _toggleSelection(String id) {
|
|
|
|
setState(() {
|
|
|
|
if (selectedIds.contains(id)) {
|
|
|
|
selectedIds.remove(id);
|
|
|
|
} else {
|
|
|
|
selectedIds.add(id);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2025-09-06 14:57:47 +08:00
|
|
|
/// 创建群聊
|
|
|
|
void createGroup(Set<String> selectedIds) async {
|
2025-09-13 17:01:01 +08:00
|
|
|
logger.w(makeGroup);
|
|
|
|
if (makeGroup == false) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
setState(() {
|
|
|
|
makeGroup = false;
|
|
|
|
});
|
2025-09-06 14:57:47 +08:00
|
|
|
// dataList是原始数据List<V2TimUserFullInfo> dataList = [];
|
|
|
|
// 通过dataList和selectedIds来构建memberList
|
|
|
|
final ctl = Get.find<ImUserInfoController>();
|
|
|
|
final memberList = dataList
|
|
|
|
.where((user) => selectedIds.contains(user.userID))
|
|
|
|
.map((user) => V2TimGroupMember(
|
|
|
|
userID: user.userID!,
|
|
|
|
role: GroupMemberRoleTypeEnum.V2TIM_GROUP_MEMBER_ROLE_MEMBER, // 加群的成员角色(默认普通成员)
|
|
|
|
))
|
|
|
|
.toList();
|
|
|
|
final self = V2TimGroupMember(
|
|
|
|
userID: ctl.userID.value,
|
|
|
|
role: GroupMemberRoleTypeEnum.V2TIM_GROUP_MEMBER_ROLE_OWNER, // 建群的人为群主
|
|
|
|
);
|
|
|
|
memberList.insert(0, self);
|
|
|
|
final groupName = buildGroupName(selectedIds);
|
2025-09-13 17:01:01 +08:00
|
|
|
try {
|
|
|
|
final res = await ImService.instance.createGroup(groupType: GroupType.Work, groupName: groupName, memberList: memberList);
|
|
|
|
if (res.success) {
|
|
|
|
final groupID = res.data;
|
|
|
|
logger.w(groupID);
|
|
|
|
// final V2TimConversation conv = V2TimConversation(conversationID: 'group_$groupID');
|
|
|
|
// conv.showName = groupName;
|
|
|
|
final msgRes = await IMMessage().createTextMessage(text: '加入了群聊:$groupName');
|
|
|
|
if (msgRes.success && msgRes.data?.messageInfo != null) {
|
|
|
|
final groupRes = await IMMessage().sendMessage(
|
|
|
|
msg: msgRes.data!.messageInfo!,
|
|
|
|
groupID: groupID,
|
|
|
|
isExcludedFromUnreadCount: true,
|
|
|
|
isPush: false,
|
|
|
|
groupName: groupName,
|
|
|
|
cloudCustomData: 'tips',
|
|
|
|
);
|
|
|
|
if (groupRes.success) {
|
|
|
|
final convRes = await ImService.instance.getConversation(conversationID: 'group_$groupID');
|
|
|
|
if (convRes.success) {
|
|
|
|
setState(() {
|
|
|
|
makeGroup = true;
|
|
|
|
});
|
|
|
|
final V2TimConversation conv = convRes.data;
|
|
|
|
logger.e(conv.toJson());
|
|
|
|
await Get.toNamed('/chatGroup', arguments: conv);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
logger.e(e);
|
|
|
|
//修改按钮可操作状态
|
|
|
|
setState(() {
|
|
|
|
makeGroup = true;
|
|
|
|
});
|
2025-09-06 14:57:47 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 构建默认群名称
|
|
|
|
String buildGroupName(Set<String> selectedIds) {
|
|
|
|
int maxLength = 20;
|
|
|
|
// 根据 selectedIds 找到对应的昵称
|
|
|
|
final memberNicknames = selectedIds.map((id) {
|
|
|
|
final user = dataList.firstWhere(
|
|
|
|
(u) => u.userID == id,
|
|
|
|
orElse: () => V2TimUserFullInfo(userID: id, nickName: id),
|
|
|
|
);
|
|
|
|
return user.nickName?.isNotEmpty == true ? user.nickName! : user.userID;
|
|
|
|
}).toList();
|
|
|
|
final ctl = Get.find<ImUserInfoController>();
|
|
|
|
// 插入群主的信息
|
|
|
|
memberNicknames.insert(0, ctl.nickname.value);
|
|
|
|
|
|
|
|
// 拼接成群名
|
|
|
|
String name = memberNicknames.join("、");
|
|
|
|
|
|
|
|
// 如果超过长度限制,截断
|
|
|
|
if (name.length > maxLength) {
|
|
|
|
name = "${name.substring(0, maxLength - 3)}...";
|
|
|
|
}
|
|
|
|
|
|
|
|
return name;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 空状态提示
|
|
|
|
Widget _emptyTip(String text) {
|
|
|
|
return Center(
|
|
|
|
child: Column(
|
|
|
|
mainAxisSize: MainAxisSize.min,
|
|
|
|
children: [
|
|
|
|
Image.asset('assets/images/empty.png', width: 100),
|
|
|
|
const SizedBox(height: 8),
|
|
|
|
Text(
|
|
|
|
text,
|
|
|
|
style: const TextStyle(color: Colors.grey, fontSize: 13),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2025-09-03 11:25:31 +08:00
|
|
|
Widget _buildCard({
|
|
|
|
required IconData icon,
|
|
|
|
required String title,
|
|
|
|
required VoidCallback onTap,
|
|
|
|
}) {
|
|
|
|
return Card(
|
|
|
|
color: Colors.white,
|
|
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
|
|
|
child: ListTile(
|
|
|
|
leading: Icon(icon, color: Colors.black),
|
|
|
|
title: Text(title, style: const TextStyle(fontSize: 16)),
|
|
|
|
trailing: const Icon(Icons.arrow_forward_ios, size: 16),
|
|
|
|
onTap: onTap,
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
return Scaffold(
|
|
|
|
backgroundColor: Colors.white,
|
|
|
|
appBar: AppBar(
|
|
|
|
centerTitle: true,
|
|
|
|
forceMaterialTransparency: true,
|
|
|
|
bottom: PreferredSize(
|
|
|
|
preferredSize: const Size.fromHeight(1.0),
|
|
|
|
child: Container(
|
|
|
|
color: Colors.grey[300],
|
|
|
|
height: 1.0,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
title: const Text(
|
|
|
|
"创建群聊",
|
|
|
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
body: Column(
|
|
|
|
children: [
|
|
|
|
// 上半部分 - 两个卡片
|
2025-09-13 17:01:01 +08:00
|
|
|
// Padding(
|
|
|
|
// padding: const EdgeInsets.all(8.0),
|
|
|
|
// child: Column(
|
|
|
|
// children: [
|
|
|
|
// _buildCard(
|
|
|
|
// icon: Icons.public,
|
|
|
|
// title: "创建公开群",
|
|
|
|
// onTap: () {
|
|
|
|
// logger.w("跳转到 创建公开群");
|
|
|
|
// },
|
|
|
|
// ),
|
|
|
|
// _buildCard(
|
|
|
|
// icon: Icons.group,
|
|
|
|
// title: "创建好友群",
|
|
|
|
// onTap: () {
|
|
|
|
// logger.w("跳转到 创建好友群");
|
|
|
|
// },
|
|
|
|
// ),
|
|
|
|
// ],
|
|
|
|
// ),
|
|
|
|
// ),
|
2025-09-03 11:25:31 +08:00
|
|
|
|
2025-09-13 17:01:01 +08:00
|
|
|
// const Divider(),
|
2025-09-03 11:25:31 +08:00
|
|
|
|
2025-09-06 14:57:47 +08:00
|
|
|
// 下半部分
|
2025-09-03 11:25:31 +08:00
|
|
|
Expanded(
|
|
|
|
child: Column(
|
|
|
|
children: [
|
|
|
|
// 搜索框
|
|
|
|
Padding(
|
|
|
|
padding: const EdgeInsets.all(8.0),
|
|
|
|
child: TextField(
|
|
|
|
controller: _searchController,
|
|
|
|
decoration: InputDecoration(
|
|
|
|
hintText: "搜索用户昵称",
|
|
|
|
prefixIcon: const Icon(Icons.search),
|
|
|
|
border: OutlineInputBorder(
|
|
|
|
borderRadius: BorderRadius.circular(8),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
onChanged: _applySearch,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
|
|
|
|
// 列表
|
|
|
|
Expanded(
|
|
|
|
child: EasyRefresh(
|
2025-09-06 14:57:47 +08:00
|
|
|
header: ClassicHeader(
|
|
|
|
dragText: '下拉刷新',
|
|
|
|
armedText: '释放刷新',
|
|
|
|
readyText: '加载中...',
|
|
|
|
processingText: '加载中...',
|
|
|
|
processedText: '加载完成',
|
|
|
|
failedText: '加载失败,请重试',
|
|
|
|
messageText: '最后更新于 %T',
|
|
|
|
),
|
|
|
|
footer: ClassicFooter(
|
|
|
|
dragText: '加载更多',
|
|
|
|
armedText: '释放加载',
|
|
|
|
readyText: '加载中...',
|
|
|
|
processingText: '加载中...',
|
|
|
|
processedText: hasMore ? '加载完成' : '没有更多了~',
|
|
|
|
failedText: '加载失败,请重试',
|
|
|
|
messageText: '最后更新于 %T',
|
|
|
|
),
|
2025-09-03 11:25:31 +08:00
|
|
|
onRefresh: () async => _loadData(reset: true),
|
|
|
|
onLoad: () async {
|
|
|
|
if (hasMore) await _loadData();
|
|
|
|
},
|
2025-09-06 14:57:47 +08:00
|
|
|
child: filteredList.isEmpty
|
|
|
|
? _emptyTip('暂无数据')
|
|
|
|
: ListView.builder(
|
|
|
|
itemCount: filteredList.length,
|
|
|
|
itemBuilder: (context, index) {
|
|
|
|
final item = filteredList[index];
|
|
|
|
final id = item.userID as String;
|
|
|
|
final isSelected = selectedIds.contains(id);
|
|
|
|
|
|
|
|
return ListTile(
|
|
|
|
leading: ClipOval(
|
|
|
|
// child: Text((item.nickName ?? '未知').substring(0, 1)),
|
|
|
|
child: NetworkOrAssetImage(
|
|
|
|
imageUrl: item.faceUrl,
|
|
|
|
width: 48,
|
|
|
|
height: 48,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
title: Text(item.nickName ?? '未知'),
|
|
|
|
trailing: Checkbox(
|
|
|
|
value: isSelected,
|
|
|
|
shape: const CircleBorder(),
|
|
|
|
onChanged: (_) => _toggleSelection(id),
|
|
|
|
),
|
|
|
|
onTap: () => _toggleSelection(id),
|
|
|
|
);
|
|
|
|
},
|
2025-09-03 11:25:31 +08:00
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
|
|
|
|
// 底部按钮
|
|
|
|
bottomNavigationBar: SafeArea(
|
2025-09-06 14:57:47 +08:00
|
|
|
child: Padding(
|
|
|
|
padding: const EdgeInsets.all(12.0),
|
|
|
|
child: TweenAnimationBuilder<Color?>(
|
|
|
|
duration: const Duration(milliseconds: 300),
|
|
|
|
tween: ColorTween(
|
|
|
|
begin: Colors.grey,
|
2025-09-13 17:01:01 +08:00
|
|
|
end: (selectedIds.isNotEmpty && makeGroup == true) ? FStyle.primaryColor : Colors.grey,
|
2025-09-03 11:25:31 +08:00
|
|
|
),
|
2025-09-06 14:57:47 +08:00
|
|
|
builder: (context, bgColor, _) {
|
|
|
|
return ElevatedButton(
|
2025-09-13 17:01:01 +08:00
|
|
|
onPressed: (selectedIds.isEmpty && makeGroup == false)
|
2025-09-06 14:57:47 +08:00
|
|
|
? null
|
|
|
|
: () {
|
|
|
|
logger.w("选择了用户:$selectedIds");
|
|
|
|
createGroup(selectedIds);
|
|
|
|
},
|
|
|
|
style: ElevatedButton.styleFrom(
|
|
|
|
minimumSize: const Size.fromHeight(50),
|
|
|
|
backgroundColor: bgColor,
|
|
|
|
shape: const StadiumBorder(), // 胶囊形状
|
|
|
|
),
|
|
|
|
child: AnimatedDefaultTextStyle(
|
|
|
|
duration: const Duration(milliseconds: 300),
|
|
|
|
style: TextStyle(
|
|
|
|
fontSize: 16,
|
|
|
|
color: selectedIds.isEmpty ? Colors.black : Colors.white,
|
|
|
|
),
|
|
|
|
child: const Text("发起聊天"),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
},
|
2025-09-03 11:25:31 +08:00
|
|
|
),
|
2025-09-06 14:57:47 +08:00
|
|
|
)),
|
2025-09-03 11:25:31 +08:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|