305 lines
11 KiB
Dart
305 lines
11 KiB
Dart
import 'package:easy_refresh/easy_refresh.dart';
|
||
import 'package:flutter/material.dart';
|
||
import 'package:get/get.dart';
|
||
import 'package:loopin/IM/im_service.dart';
|
||
import 'package:loopin/pages/groupChat/controller/group_detail_controller.dart';
|
||
import 'package:loopin/styles/index.dart';
|
||
import 'package:tencent_cloud_chat_sdk/enum/group_member_filter_enum.dart';
|
||
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_member_full_info.dart';
|
||
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_member_search_param.dart';
|
||
|
||
class MemberActionSheet extends StatefulWidget {
|
||
final String groupID;
|
||
final Function(List<String> selected) onAction;
|
||
final String title;
|
||
final String actionLabel;
|
||
final bool showButton;
|
||
final bool showSelf;
|
||
|
||
const MemberActionSheet({
|
||
super.key,
|
||
required this.groupID,
|
||
required this.onAction,
|
||
this.title = "",
|
||
this.actionLabel = "确认",
|
||
this.showButton = true,
|
||
this.showSelf = false,
|
||
});
|
||
|
||
@override
|
||
State<MemberActionSheet> createState() => _MemberActionSheetState();
|
||
}
|
||
|
||
class _MemberActionSheetState extends State<MemberActionSheet> {
|
||
String _query = "";
|
||
final Set<String> _selectedIDs = {}; // 选中的 userID 集合
|
||
late List<V2TimGroupMemberFullInfo> members = [];
|
||
late List<V2TimGroupMemberFullInfo> searchMemberList = [];
|
||
String nextSeq = '0';
|
||
String searchCursor = '';
|
||
bool hasMore = false; // 普通列表用
|
||
bool loading = false;
|
||
bool isFinished = false; // 搜索列表用
|
||
bool loading2 = false; //搜索
|
||
|
||
//
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
if (widget.showSelf) {
|
||
final self = Get.find<GroupDetailController>().selfInfo.value!;
|
||
members.insert(0, self);
|
||
}
|
||
getMemberData();
|
||
}
|
||
|
||
//群成员
|
||
Future<void> getMemberData() async {
|
||
if (loading) return;
|
||
loading = true;
|
||
final res = await ImService.instance.getGroupMemberList(
|
||
groupID: widget.groupID,
|
||
filter: GroupMemberFilterTypeEnum.V2TIM_GROUP_MEMBER_FILTER_COMMON,
|
||
nextSeq: nextSeq,
|
||
count: 100,
|
||
);
|
||
if (res.success && res.data != null) {
|
||
logger.e(res.data!.nextSeq);
|
||
nextSeq = res.data!.nextSeq ?? '0';
|
||
final mem = res.data!.memberInfoList ?? [];
|
||
setState(() {
|
||
members.addAll(mem);
|
||
hasMore = res.data!.nextSeq == '0' ? false : true;
|
||
loading = false;
|
||
});
|
||
}
|
||
}
|
||
|
||
///搜索群成员
|
||
Future<void> searchMember({bool loadMore = false}) async {
|
||
if (loading2) return;
|
||
if (_query.isEmpty) {
|
||
setState(() {
|
||
_query = '';
|
||
});
|
||
return;
|
||
}
|
||
loading2 = true;
|
||
final param = V2TimGroupMemberSearchParam(
|
||
keywordList: [_query],
|
||
groupIDList: [widget.groupID],
|
||
isSearchMemberUserID: true,
|
||
isSearchMemberNickName: true,
|
||
isSearchMemberRemark: true,
|
||
isSearchMemberNameCard: true,
|
||
keywordListMatchType: V2TimGroupMemberSearchParam.V2TIM_KEYWORD_LIST_MATCH_TYPE_OR,
|
||
searchCount: 100,
|
||
searchCursor: searchCursor,
|
||
);
|
||
final res = await ImService.instance.searchGroupMembers(param: param);
|
||
if (res.success && res.data != null) {
|
||
isFinished = res.data!.isFinished ?? true;
|
||
final searchCursor = res.data!.nextCursor ?? '';
|
||
final data = res.data!.groupMemberSearchResultItems;
|
||
// 搜索结果群的成员
|
||
if (data != null && data[widget.groupID] != null) {
|
||
List<V2TimGroupMemberFullInfo> list = (data[widget.groupID] as List<dynamic>).cast<V2TimGroupMemberFullInfo>();
|
||
if (!widget.showSelf) {
|
||
// 过滤自己
|
||
final self = Get.find<GroupDetailController>().selfInfo.value!;
|
||
list = list.where((item) => item.userID != self.userID).toList();
|
||
}
|
||
if (loadMore) {
|
||
searchMemberList.addAll(list);
|
||
} else {
|
||
searchMemberList = list;
|
||
}
|
||
}
|
||
logger.w(hasMore);
|
||
logger.w(searchCursor);
|
||
logger.w(data);
|
||
}
|
||
setState(() {
|
||
loading2 = false;
|
||
});
|
||
}
|
||
|
||
String handleText(String? text, String defaultValue) {
|
||
if (text == null || text.trim().isEmpty) return defaultValue;
|
||
return text;
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
List filteredMembers;
|
||
if (_query.isEmpty) {
|
||
filteredMembers = members;
|
||
} else {
|
||
filteredMembers = searchMemberList;
|
||
}
|
||
return SafeArea(
|
||
child: GestureDetector(
|
||
behavior: HitTestBehavior.translucent, // 点击空白区域也能触发
|
||
onTap: () {
|
||
FocusScope.of(context).unfocus();
|
||
},
|
||
child: 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: Text(
|
||
widget.title,
|
||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||
),
|
||
leading: IconButton(
|
||
icon: const Icon(Icons.arrow_back),
|
||
onPressed: () {
|
||
Navigator.pop(context);
|
||
},
|
||
),
|
||
),
|
||
body: Column(
|
||
children: [
|
||
SizedBox(height: 10),
|
||
// 搜索框
|
||
Padding(
|
||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||
child: TextField(
|
||
decoration: InputDecoration(
|
||
prefixIcon: const Icon(Icons.search),
|
||
hintText: "搜索成员",
|
||
contentPadding: const EdgeInsets.symmetric(vertical: 8),
|
||
border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)),
|
||
),
|
||
onChanged: (value) {
|
||
setState(() {
|
||
_query = value.trim();
|
||
searchMember();
|
||
});
|
||
},
|
||
),
|
||
),
|
||
|
||
const SizedBox(height: 8),
|
||
|
||
// 成员列表
|
||
Expanded(
|
||
child: EasyRefresh(
|
||
footer: ClassicFooter(
|
||
dragText: '加载更多',
|
||
armedText: '释放加载',
|
||
readyText: '加载中...',
|
||
processingText: '加载中...',
|
||
processedText: '加载完成',
|
||
noMoreText: '没有更多了~',
|
||
failedText: '加载失败,请重试',
|
||
messageText: '最后更新于 %T',
|
||
),
|
||
onLoad: () async {
|
||
//
|
||
if (_query.isNotEmpty && (!isFinished)) {
|
||
await searchMember(loadMore: true);
|
||
return !isFinished ? IndicatorResult.success : IndicatorResult.noMore;
|
||
} else if (hasMore) {
|
||
await getMemberData();
|
||
return hasMore ? IndicatorResult.success : IndicatorResult.noMore;
|
||
}
|
||
},
|
||
child: ListView.builder(
|
||
itemCount: filteredMembers.length,
|
||
itemBuilder: (context, index) {
|
||
final m = filteredMembers[index];
|
||
final id = m.userID;
|
||
final uname = handleText(m.nameCard, '');
|
||
final nickname = handleText(m.nickName, '未知昵称');
|
||
final showName = uname.isEmpty ? nickname : uname;
|
||
return InkWell(
|
||
onTap: () {
|
||
setState(() {
|
||
if (_selectedIDs.contains(id)) {
|
||
_selectedIDs.remove(id);
|
||
} else {
|
||
_selectedIDs.add(id);
|
||
}
|
||
});
|
||
},
|
||
child: Padding(
|
||
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
|
||
child: Row(
|
||
children: [
|
||
// 左侧圆形头像
|
||
CircleAvatar(
|
||
radius: 20,
|
||
backgroundImage: m.faceUrl != null ? NetworkImage(m.faceUrl!) : null,
|
||
child: m.faceUrl == null ? const Icon(Icons.person) : null,
|
||
),
|
||
const SizedBox(width: 12),
|
||
|
||
// 用户名
|
||
Expanded(
|
||
child: Text(
|
||
showName,
|
||
style: const TextStyle(fontSize: 14),
|
||
overflow: TextOverflow.ellipsis,
|
||
),
|
||
),
|
||
if (widget.showButton)
|
||
// 复选框
|
||
Container(
|
||
width: 24,
|
||
height: 24,
|
||
alignment: Alignment.center,
|
||
decoration: BoxDecoration(
|
||
shape: BoxShape.circle,
|
||
border: Border.all(color: _selectedIDs.contains(id) ? FStyle.primaryColor : Colors.grey),
|
||
color: _selectedIDs.contains(id) ? FStyle.primaryColor : Colors.transparent,
|
||
),
|
||
child: _selectedIDs.contains(id) ? const Icon(Icons.check, size: 16, color: Colors.white) : null,
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
},
|
||
),
|
||
),
|
||
),
|
||
// 底部操作按钮
|
||
if (widget.showButton)
|
||
Padding(
|
||
padding: const EdgeInsets.all(16),
|
||
child: ElevatedButton(
|
||
style: ElevatedButton.styleFrom(
|
||
minimumSize: const Size(double.infinity, 48),
|
||
backgroundColor: FStyle.primaryColor,
|
||
),
|
||
onPressed: _selectedIDs.isEmpty
|
||
? null
|
||
: () {
|
||
Navigator.pop(context);
|
||
widget.onAction(_selectedIDs.toList());
|
||
},
|
||
child: Text(
|
||
"${widget.actionLabel}(${_selectedIDs.length})",
|
||
style: TextStyle(
|
||
color: _selectedIDs.isNotEmpty ? Colors.white : Colors.black,
|
||
),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|