flutter/lib/pages/groupChat/index.dart
2025-09-06 14:57:47 +08:00

351 lines
12 KiB
Dart

// 创建群聊
import 'package:easy_refresh/easy_refresh.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:loopin/IM/controller/im_user_info_controller.dart';
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';
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';
import 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_conversation.dart';
class StartGroupChatPage extends StatefulWidget {
const StartGroupChatPage({super.key});
@override
State<StartGroupChatPage> createState() => _StartGroupChatPageState();
}
class _StartGroupChatPageState extends State<StartGroupChatPage> {
final TextEditingController _searchController = TextEditingController();
List<V2TimUserFullInfo> dataList = [];
List<V2TimUserFullInfo> filteredList = [];
Set<String> selectedIds = {}; // 已选中的用户 id
String page = '';
bool hasMore = true;
bool isLoading = true;
@override
void initState() {
super.initState();
_loadData(reset: true);
}
@override
void dispose() {
_searchController.dispose();
super.dispose();
}
// 分页获取陌关注列表数据
Future<void> _loadData({bool reset = false}) async {
if (reset) {
page = '';
hasMore = true;
dataList.clear();
}
final res = await ImService.instance.getMutualFollowersList(
nextCursor: page,
);
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');
if (isFinished) {
setState(() {
hasMore = false;
});
// 加载没数据了
page = '';
} else {
page = res.data!.nextCursor ?? '';
}
logger.i('获取数据成功:$userInfoList');
setState(() {
dataList.addAll(userInfoList);
_applySearch(_searchController.text);
});
} else {
logger.e('获取数据失败:${res.desc}');
}
}
void _applySearch(String query) {
setState(() {
if (query.isEmpty) {
filteredList = List.from(dataList);
} else {
filteredList = dataList.where((item) => (item.nickName ?? '').contains(query.trim())).toList();
}
});
}
void _toggleSelection(String id) {
setState(() {
if (selectedIds.contains(id)) {
selectedIds.remove(id);
} else {
selectedIds.add(id);
}
});
}
/// 创建群聊
void createGroup(Set<String> selectedIds) async {
// 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);
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;
Get.toNamed('/chatGroup', arguments: conv);
}
}
// 构建默认群名称
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),
),
],
),
);
}
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: [
// 上半部分 - 两个卡片
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("跳转到 创建好友群");
// },
// ),
],
),
),
const Divider(),
// 下半部分
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(
header: ClassicHeader(
dragText: '下拉刷新',
armedText: '释放刷新',
readyText: '加载中...',
processingText: '加载中...',
processedText: '加载完成',
failedText: '加载失败,请重试',
messageText: '最后更新于 %T',
),
footer: ClassicFooter(
dragText: '加载更多',
armedText: '释放加载',
readyText: '加载中...',
processingText: '加载中...',
processedText: hasMore ? '加载完成' : '没有更多了~',
failedText: '加载失败,请重试',
messageText: '最后更新于 %T',
),
onRefresh: () async => _loadData(reset: true),
onLoad: () async {
if (hasMore) await _loadData();
},
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),
);
},
),
),
),
],
),
),
],
),
// 底部按钮
bottomNavigationBar: SafeArea(
child: Padding(
padding: const EdgeInsets.all(12.0),
child: TweenAnimationBuilder<Color?>(
duration: const Duration(milliseconds: 300),
tween: ColorTween(
begin: Colors.grey,
end: selectedIds.isEmpty ? Colors.grey : FStyle.primaryColor,
),
builder: (context, bgColor, _) {
return ElevatedButton(
onPressed: selectedIds.isEmpty
? 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("发起聊天"),
),
);
},
),
)),
);
}
}