11 changed files with 987 additions and 527 deletions
Unified View
Diff Options
-
384lib/controller/discover/room_controller.dart
-
116lib/model/discover/task_data.dart
-
3lib/network/api_urls.dart
-
6lib/network/user_api.dart
-
33lib/network/user_api.g.dart
-
81lib/widget/live/audience_item.dart
-
43lib/widget/live/disconnect_mic_dialog.dart
-
189lib/widget/live/kick_list.dart
-
313lib/widget/live/live_room_invitation_list.dart
-
181lib/widget/live/live_room_notice_chat_panel.dart
-
165lib/widget/live/live_room_user_header.dart
@ -0,0 +1,116 @@ |
|||||
|
class TaskData { |
||||
|
String? userTaskCompleteId; |
||||
|
int? taskGroup; |
||||
|
int? taskType; |
||||
|
int? stageCode; |
||||
|
String? taskName; |
||||
|
String? taskDesc; |
||||
|
String? taskStartDate; |
||||
|
String? taskEndDate; |
||||
|
String? rewardValue; |
||||
|
bool? completeStatus; |
||||
|
int? rewardReceiveStatus; |
||||
|
String? completeTime; |
||||
|
List<SubList>? subList; |
||||
|
|
||||
|
TaskData( |
||||
|
{this.userTaskCompleteId, |
||||
|
this.taskGroup, |
||||
|
this.taskType, |
||||
|
this.stageCode, |
||||
|
this.taskName, |
||||
|
this.taskDesc, |
||||
|
this.taskStartDate, |
||||
|
this.taskEndDate, |
||||
|
this.rewardValue, |
||||
|
this.completeStatus, |
||||
|
this.rewardReceiveStatus, |
||||
|
this.completeTime, |
||||
|
this.subList}); |
||||
|
|
||||
|
TaskData.fromJson(Map<String, dynamic> json) { |
||||
|
userTaskCompleteId = json['userTaskCompleteId']; |
||||
|
taskGroup = json['taskGroup']; |
||||
|
taskType = json['taskType']; |
||||
|
stageCode = json['stageCode']; |
||||
|
taskName = json['taskName']; |
||||
|
taskDesc = json['taskDesc']; |
||||
|
taskStartDate = json['taskStartDate']; |
||||
|
taskEndDate = json['taskEndDate']; |
||||
|
rewardValue = json['rewardValue']; |
||||
|
completeStatus = json['completeStatus']; |
||||
|
rewardReceiveStatus = json['rewardReceiveStatus']; |
||||
|
completeTime = json['completeTime']; |
||||
|
if (json['subList'] != null) { |
||||
|
subList = <SubList>[]; |
||||
|
json['subList'].forEach((v) { |
||||
|
subList!.add(new SubList.fromJson(v)); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Map<String, dynamic> toJson() { |
||||
|
final Map<String, dynamic> data = new Map<String, dynamic>(); |
||||
|
data['userTaskCompleteId'] = this.userTaskCompleteId; |
||||
|
data['taskGroup'] = this.taskGroup; |
||||
|
data['taskType'] = this.taskType; |
||||
|
data['stageCode'] = this.stageCode; |
||||
|
data['taskName'] = this.taskName; |
||||
|
data['taskDesc'] = this.taskDesc; |
||||
|
data['taskStartDate'] = this.taskStartDate; |
||||
|
data['taskEndDate'] = this.taskEndDate; |
||||
|
data['rewardValue'] = this.rewardValue; |
||||
|
data['completeStatus'] = this.completeStatus; |
||||
|
data['rewardReceiveStatus'] = this.rewardReceiveStatus; |
||||
|
data['completeTime'] = this.completeTime; |
||||
|
if (this.subList != null) { |
||||
|
data['subList'] = this.subList!.map((v) => v.toJson()).toList(); |
||||
|
} |
||||
|
return data; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
class SubList { |
||||
|
String? subTaskName; |
||||
|
int? subTaskType; |
||||
|
String? subTaskDesc; |
||||
|
int? requiredCount; |
||||
|
int? completeCount; |
||||
|
bool? completeStatus; |
||||
|
String? completeTime; |
||||
|
int? sort; |
||||
|
|
||||
|
SubList( |
||||
|
{this.subTaskName, |
||||
|
this.subTaskType, |
||||
|
this.subTaskDesc, |
||||
|
this.requiredCount, |
||||
|
this.completeCount, |
||||
|
this.completeStatus, |
||||
|
this.completeTime, |
||||
|
this.sort}); |
||||
|
|
||||
|
SubList.fromJson(Map<String, dynamic> json) { |
||||
|
subTaskName = json['subTaskName']; |
||||
|
subTaskType = json['subTaskType']; |
||||
|
subTaskDesc = json['subTaskDesc']; |
||||
|
requiredCount = json['requiredCount']; |
||||
|
completeCount = json['completeCount']; |
||||
|
completeStatus = json['completeStatus']; |
||||
|
completeTime = json['completeTime']; |
||||
|
sort = json['sort']; |
||||
|
} |
||||
|
|
||||
|
Map<String, dynamic> toJson() { |
||||
|
final Map<String, dynamic> data = new Map<String, dynamic>(); |
||||
|
data['subTaskName'] = this.subTaskName; |
||||
|
data['subTaskType'] = this.subTaskType; |
||||
|
data['subTaskDesc'] = this.subTaskDesc; |
||||
|
data['requiredCount'] = this.requiredCount; |
||||
|
data['completeCount'] = this.completeCount; |
||||
|
data['completeStatus'] = this.completeStatus; |
||||
|
data['completeTime'] = this.completeTime; |
||||
|
data['sort'] = this.sort; |
||||
|
return data; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,81 @@ |
|||||
|
|
||||
|
|
||||
|
import 'package:cached_network_image/cached_network_image.dart'; |
||||
|
import 'package:flutter/material.dart'; |
||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart'; |
||||
|
|
||||
|
import '../../model/discover/audience_list_data.dart'; |
||||
|
|
||||
|
class AudienceItem extends StatefulWidget { |
||||
|
final Records item; |
||||
|
final List<String> selectUserId; |
||||
|
final Function(String) selectChange; |
||||
|
const AudienceItem({super.key, required this.item, required this.selectUserId, required this.selectChange}); |
||||
|
|
||||
|
@override |
||||
|
State<AudienceItem> createState() => _AudienceItemState(); |
||||
|
} |
||||
|
|
||||
|
class _AudienceItemState extends State<AudienceItem> { |
||||
|
@override |
||||
|
Widget build(BuildContext context) { |
||||
|
return Container( |
||||
|
margin: EdgeInsets.symmetric(vertical: 10.w), |
||||
|
child: Row( |
||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween, |
||||
|
children: [ |
||||
|
Row( |
||||
|
children: [ |
||||
|
ClipRRect( |
||||
|
borderRadius: BorderRadius.all(Radius.circular(40.w)), |
||||
|
child: CachedNetworkImage( |
||||
|
imageUrl: "${widget.item.profilePhoto ?? ""}?x-oss-process=image/format,webp/resize,w_120", |
||||
|
|
||||
|
width: 40.w, |
||||
|
height: 40.w, |
||||
|
imageBuilder: (context, imageProvider) => Container( |
||||
|
decoration: BoxDecoration( |
||||
|
image: DecorationImage( |
||||
|
image: imageProvider, |
||||
|
fit: BoxFit.cover, |
||||
|
), |
||||
|
), |
||||
|
), |
||||
|
), |
||||
|
), |
||||
|
SizedBox(width: 5.w,), |
||||
|
Column( |
||||
|
children: [ |
||||
|
Text( |
||||
|
widget.item.nickName ?? "", |
||||
|
style: TextStyle( |
||||
|
fontSize: 12.w |
||||
|
), |
||||
|
), |
||||
|
Text( |
||||
|
"${widget.item.age ?? ""}岁", |
||||
|
style: TextStyle( |
||||
|
fontSize: 12.w, |
||||
|
color: const Color.fromRGBO(121, 121, 121, 1) |
||||
|
), |
||||
|
), |
||||
|
], |
||||
|
) |
||||
|
], |
||||
|
), |
||||
|
|
||||
|
Checkbox( |
||||
|
value: widget.selectUserId.contains(widget.item.userId), |
||||
|
onChanged: (value) { |
||||
|
widget.selectChange(widget.item.userId ?? ""); |
||||
|
}, |
||||
|
activeColor: const Color.fromRGBO(117, 98, 249, 1), |
||||
|
side: const BorderSide(color: Colors.grey), |
||||
|
shape: const CircleBorder(), |
||||
|
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, |
||||
|
), |
||||
|
], |
||||
|
), |
||||
|
); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,189 @@ |
|||||
|
import 'package:dating_touchme_app/controller/discover/room_controller.dart'; |
||||
|
import 'package:dating_touchme_app/extension/ex_widget.dart'; |
||||
|
import 'package:easy_refresh/easy_refresh.dart'; |
||||
|
import 'package:flutter/material.dart'; |
||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart'; |
||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; |
||||
|
import 'package:get/get.dart'; |
||||
|
|
||||
|
import 'audience_item.dart'; |
||||
|
|
||||
|
class KickList extends StatefulWidget { |
||||
|
const KickList({super.key}); |
||||
|
|
||||
|
@override |
||||
|
State<KickList> createState() => _KickListState(); |
||||
|
} |
||||
|
|
||||
|
class _KickListState extends State<KickList> { |
||||
|
|
||||
|
final RoomController _roomController = Get.find<RoomController>(); |
||||
|
|
||||
|
|
||||
|
|
||||
|
List<String> selectUserId = []; |
||||
|
|
||||
|
selectChange(String userId){ |
||||
|
if (selectUserId.contains(userId)) { |
||||
|
selectUserId.remove(userId); |
||||
|
} else { |
||||
|
selectUserId.add(userId); |
||||
|
} |
||||
|
setState(() { |
||||
|
|
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
@override |
||||
|
Widget build(BuildContext context) { |
||||
|
return Material( |
||||
|
borderRadius: BorderRadius.vertical(top: Radius.circular(9.w)), |
||||
|
color: Colors.white, |
||||
|
child: Container( |
||||
|
width: 375.w, |
||||
|
height: 300.w, |
||||
|
padding: EdgeInsets.all(10.w), |
||||
|
child: Column( |
||||
|
children: [ |
||||
|
Text( |
||||
|
"在线用户", |
||||
|
style: TextStyle( |
||||
|
fontSize: 16.w, |
||||
|
fontWeight: FontWeight.w700 |
||||
|
), |
||||
|
), |
||||
|
Expanded( |
||||
|
child: EasyRefresh( |
||||
|
|
||||
|
controller: _roomController.listRefreshController, |
||||
|
header: const ClassicHeader( |
||||
|
dragText: '下拉刷新', |
||||
|
armedText: '释放刷新', |
||||
|
readyText: '刷新中...', |
||||
|
processingText: '刷新中...', |
||||
|
processedText: '刷新完成', |
||||
|
failedText: '刷新失败', |
||||
|
noMoreText: '没有更多数据', |
||||
|
showMessage: false |
||||
|
), |
||||
|
footer: ClassicFooter( |
||||
|
dragText: '上拉加载', |
||||
|
armedText: '释放加载', |
||||
|
readyText: '加载中...', |
||||
|
processingText: '加载中...', |
||||
|
processedText: '加载完成', |
||||
|
failedText: '加载失败', |
||||
|
noMoreText: '没有更多数据', |
||||
|
showMessage: false |
||||
|
), |
||||
|
// 下拉刷新 |
||||
|
onRefresh: () async { |
||||
|
print('推荐列表下拉刷新被触发'); |
||||
|
_roomController.page.value = 1; |
||||
|
_roomController.audienceList.clear(); |
||||
|
await _roomController.getAudienceList(); |
||||
|
_roomController.listRefreshController.finishRefresh(IndicatorResult.success); |
||||
|
_roomController.listRefreshController.finishLoad(IndicatorResult.none); |
||||
|
}, |
||||
|
// 上拉加载更多 |
||||
|
onLoad: () async { |
||||
|
print('推荐列表上拉加载被触发, hasMore: '); |
||||
|
_roomController.page.value += 1; |
||||
|
await _roomController.getAudienceList(); |
||||
|
}, |
||||
|
child: ListView.separated( |
||||
|
// 关键:始终允许滚动,即使内容不足 |
||||
|
// 移除顶部 padding,让刷新指示器可以正确显示在 AppBar 下方 |
||||
|
padding: EdgeInsets.only(left: 12, right: 12), |
||||
|
itemBuilder: (context, index) { |
||||
|
// 空数据状态 |
||||
|
if (_roomController.audienceList.isEmpty && index == 0) { |
||||
|
return Center( |
||||
|
child: Column( |
||||
|
mainAxisAlignment: MainAxisAlignment.center, |
||||
|
children: [ |
||||
|
Text('暂无数据'), |
||||
|
], |
||||
|
), |
||||
|
); |
||||
|
} |
||||
|
// 数据项 |
||||
|
final item = _roomController.audienceList[index]; |
||||
|
return AudienceItem(item: item, selectUserId: selectUserId, selectChange: selectChange,); |
||||
|
}, |
||||
|
separatorBuilder: (context, index) { |
||||
|
// 空状态或加载状态时不显示分隔符 |
||||
|
if (_roomController.audienceList.isEmpty) { |
||||
|
return const SizedBox.shrink(); |
||||
|
} |
||||
|
return const SizedBox(height: 12); |
||||
|
}, |
||||
|
// 至少显示一个 item(用于显示加载或空状态) |
||||
|
itemCount: _roomController.audienceList.isEmpty ? 1 : _roomController.audienceList.length, |
||||
|
), |
||||
|
), |
||||
|
), |
||||
|
Row( |
||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween, |
||||
|
children: [ |
||||
|
Container( |
||||
|
width: 170.w, |
||||
|
height: 42.w, |
||||
|
decoration: BoxDecoration( |
||||
|
borderRadius: BorderRadius.all(Radius.circular(42.w)), |
||||
|
color: const Color.fromRGBO(237, 237, 237, 1) |
||||
|
), |
||||
|
child: Center( |
||||
|
child: Text( |
||||
|
"取消", |
||||
|
style: TextStyle( |
||||
|
fontSize: 14.w, |
||||
|
fontWeight: FontWeight.w500 |
||||
|
), |
||||
|
), |
||||
|
), |
||||
|
).onTap((){ |
||||
|
// 隐藏键盘 |
||||
|
FocusScope.of(context).unfocus(); |
||||
|
// 隐藏 overlay |
||||
|
SmartDialog.dismiss(); |
||||
|
_roomController.setDialogDismiss(false); |
||||
|
}), |
||||
|
Container( |
||||
|
width: 170.w, |
||||
|
height: 42.w, |
||||
|
decoration: BoxDecoration( |
||||
|
borderRadius: BorderRadius.all(Radius.circular(42.w)), |
||||
|
color: const Color.fromRGBO(117, 98, 249, 1) |
||||
|
), |
||||
|
child: Center( |
||||
|
child: Text( |
||||
|
"确认", |
||||
|
style: TextStyle( |
||||
|
fontSize: 14.w, |
||||
|
color: Colors.white, |
||||
|
fontWeight: FontWeight.w500 |
||||
|
), |
||||
|
), |
||||
|
), |
||||
|
).onTap(() async { |
||||
|
final selectedA = _roomController.audienceList.where((item) => selectUserId.contains(item.userId)).toList(); |
||||
|
final List<String> dList = |
||||
|
selectedA.map((item) => item.userManagementId.toString()).toList(); |
||||
|
await _roomController.kickUser(dList); |
||||
|
|
||||
|
// 隐藏键盘 |
||||
|
FocusScope.of(context).unfocus(); |
||||
|
// 隐藏 overlay |
||||
|
SmartDialog.dismiss(); |
||||
|
_roomController.setDialogDismiss(false); |
||||
|
selectUserId.clear(); |
||||
|
}) |
||||
|
], |
||||
|
) |
||||
|
], |
||||
|
), |
||||
|
), |
||||
|
); |
||||
|
} |
||||
|
} |
||||
Write
Preview
Loading…
Cancel
Save