16 changed files with 833 additions and 214 deletions
Split View
Diff Options
-
2lib/components/home_appbar.dart
-
60lib/controller/discover/search_page_controller.dart
-
232lib/controller/home/home_controller.dart
-
28lib/controller/message/conversation_controller.dart
-
108lib/model/discover/matchmaker_live_data.dart
-
3lib/network/api_urls.dart
-
8lib/network/rtc_api.dart
-
35lib/network/rtc_api.g.dart
-
41lib/pages/discover/discover_page.dart
-
289lib/pages/discover/search_page.dart
-
11lib/pages/home/home_page.dart
-
13lib/pages/home/nearby_tab.dart
-
63lib/pages/home/send_timeline.dart
-
28lib/pages/main/main_page.dart
-
100lib/pages/message/message_page.dart
-
26lib/rtc/rtc_manager.dart
@ -0,0 +1,60 @@ |
|||
|
|||
import 'dart:async'; |
|||
|
|||
import 'package:dating_touchme_app/network/network_service.dart'; |
|||
import 'package:flutter/material.dart'; |
|||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; |
|||
import 'package:get/get.dart'; |
|||
|
|||
import '../../model/discover/matchmaker_live_data.dart'; |
|||
|
|||
class SearchPageController extends GetxController { |
|||
SearchPageController({NetworkService? networkService}) |
|||
: _networkService = networkService ?? Get.find<NetworkService>(); |
|||
|
|||
final NetworkService _networkService; |
|||
|
|||
|
|||
final FocusNode blankFocusNode = FocusNode(); |
|||
final searchController = TextEditingController().obs; |
|||
final name = "".obs; |
|||
|
|||
|
|||
|
|||
final matchmakerList = <Records>[].obs; |
|||
|
|||
Timer? _debounce; |
|||
|
|||
void onTextChanged(String text) { |
|||
// 取消上一次的计时 |
|||
_debounce?.cancel(); |
|||
|
|||
_debounce = Timer(const Duration(milliseconds: 500), () { |
|||
name.value = text; |
|||
searchData(); |
|||
}); |
|||
} |
|||
|
|||
searchData() async { |
|||
try{ |
|||
final response = await _networkService.rtcApi.userPageLiveMatchmaker( |
|||
|
|||
searchKey: name.value |
|||
); |
|||
if (response.data.isSuccess && response.data.data != null) { |
|||
final data = response.data.data?.records ?? []; |
|||
|
|||
matchmakerList.value = data.toList(); |
|||
|
|||
} else { |
|||
|
|||
// 响应失败,抛出异常 |
|||
throw Exception(response.data.message ?? '获取数据失败'); |
|||
} |
|||
} catch(e) { |
|||
print('红娘搜索失败: $e'); |
|||
SmartDialog.showToast('红娘搜索失败'); |
|||
rethrow; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,108 @@ |
|||
class MatchmakerLiveData { |
|||
List<Records>? records; |
|||
int? total; |
|||
int? size; |
|||
int? current; |
|||
int? pages; |
|||
|
|||
MatchmakerLiveData( |
|||
{this.records, this.total, this.size, this.current, this.pages}); |
|||
|
|||
MatchmakerLiveData.fromJson(Map<String, dynamic> json) { |
|||
if (json['records'] != null) { |
|||
records = <Records>[]; |
|||
json['records'].forEach((v) { |
|||
records!.add(new Records.fromJson(v)); |
|||
}); |
|||
} |
|||
total = json['total']; |
|||
size = json['size']; |
|||
current = json['current']; |
|||
pages = json['pages']; |
|||
} |
|||
|
|||
Map<String, dynamic> toJson() { |
|||
final Map<String, dynamic> data = new Map<String, dynamic>(); |
|||
if (this.records != null) { |
|||
data['records'] = this.records!.map((v) => v.toJson()).toList(); |
|||
} |
|||
data['total'] = this.total; |
|||
data['size'] = this.size; |
|||
data['current'] = this.current; |
|||
data['pages'] = this.pages; |
|||
return data; |
|||
} |
|||
} |
|||
|
|||
class Records { |
|||
String? userId; |
|||
String? miId; |
|||
String? nickName; |
|||
String? profilePhoto; |
|||
int? genderCode; |
|||
String? birthYear; |
|||
String? birthDate; |
|||
int? age; |
|||
int? provinceCode; |
|||
String? provinceName; |
|||
int? cityCode; |
|||
String? cityName; |
|||
Null? isLiveChannelId; |
|||
bool? liveStreaming; |
|||
int? uid; |
|||
|
|||
Records( |
|||
{this.userId, |
|||
this.miId, |
|||
this.nickName, |
|||
this.profilePhoto, |
|||
this.genderCode, |
|||
this.birthYear, |
|||
this.birthDate, |
|||
this.age, |
|||
this.provinceCode, |
|||
this.provinceName, |
|||
this.cityCode, |
|||
this.cityName, |
|||
this.isLiveChannelId, |
|||
this.liveStreaming, |
|||
this.uid}); |
|||
|
|||
Records.fromJson(Map<String, dynamic> json) { |
|||
userId = json['userId']; |
|||
miId = json['miId']; |
|||
nickName = json['nickName']; |
|||
profilePhoto = json['profilePhoto']; |
|||
genderCode = json['genderCode']; |
|||
birthYear = json['birthYear']; |
|||
birthDate = json['birthDate']; |
|||
age = json['age']; |
|||
provinceCode = json['provinceCode']; |
|||
provinceName = json['provinceName']; |
|||
cityCode = json['cityCode']; |
|||
cityName = json['cityName']; |
|||
isLiveChannelId = json['isLiveChannelId']; |
|||
liveStreaming = json['liveStreaming']; |
|||
uid = json['uid']; |
|||
} |
|||
|
|||
Map<String, dynamic> toJson() { |
|||
final Map<String, dynamic> data = new Map<String, dynamic>(); |
|||
data['userId'] = this.userId; |
|||
data['miId'] = this.miId; |
|||
data['nickName'] = this.nickName; |
|||
data['profilePhoto'] = this.profilePhoto; |
|||
data['genderCode'] = this.genderCode; |
|||
data['birthYear'] = this.birthYear; |
|||
data['birthDate'] = this.birthDate; |
|||
data['age'] = this.age; |
|||
data['provinceCode'] = this.provinceCode; |
|||
data['provinceName'] = this.provinceName; |
|||
data['cityCode'] = this.cityCode; |
|||
data['cityName'] = this.cityName; |
|||
data['isLiveChannelId'] = this.isLiveChannelId; |
|||
data['liveStreaming'] = this.liveStreaming; |
|||
data['uid'] = this.uid; |
|||
return data; |
|||
} |
|||
} |
|||
@ -0,0 +1,289 @@ |
|||
import 'package:cached_network_image/cached_network_image.dart'; |
|||
import 'package:dating_touchme_app/components/page_appbar.dart'; |
|||
import 'package:dating_touchme_app/controller/discover/room_controller.dart'; |
|||
import 'package:dating_touchme_app/controller/discover/search_page_controller.dart'; |
|||
import 'package:dating_touchme_app/extension/ex_widget.dart'; |
|||
import 'package:dating_touchme_app/generated/assets.dart'; |
|||
import 'package:dating_touchme_app/model/discover/matchmaker_live_data.dart'; |
|||
import 'package:dating_touchme_app/model/home/marriage_data.dart'; |
|||
import 'package:dating_touchme_app/model/home/user_info_data.dart'; |
|||
import 'package:dating_touchme_app/network/user_api.dart'; |
|||
import 'package:dating_touchme_app/pages/home/user_information_page.dart'; |
|||
import 'package:dating_touchme_app/pages/message/chat_page.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'; |
|||
|
|||
class SearchPage extends StatelessWidget { |
|||
const SearchPage({super.key}); |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return GetX<SearchPageController>( |
|||
init: SearchPageController(), |
|||
builder: (controller){ |
|||
return Scaffold( |
|||
appBar: PageAppbar(title: "搜索"), |
|||
body: Column( |
|||
children: [ |
|||
Container( |
|||
padding: EdgeInsets.symmetric(horizontal: 15.w), |
|||
margin: EdgeInsets.only(bottom: 10.w), |
|||
child: Container( |
|||
height: 30.w, |
|||
padding: EdgeInsets.symmetric(horizontal: 10), |
|||
decoration: BoxDecoration( |
|||
borderRadius: BorderRadius.all(Radius.circular(30.w)), |
|||
color: const Color.fromRGBO(200, 200, 200, 1.0) |
|||
), |
|||
child: Row( |
|||
children: [ |
|||
Icon( |
|||
Icons.search, |
|||
size: 20, |
|||
color: Colors.grey, |
|||
), |
|||
Expanded( |
|||
child: TextField( |
|||
focusNode: controller.blankFocusNode, |
|||
onTapOutside: (e){ |
|||
controller.blankFocusNode.unfocus(); |
|||
}, |
|||
controller: controller.searchController.value, |
|||
textAlignVertical: TextAlignVertical.center, |
|||
style: TextStyle( |
|||
fontSize: ScreenUtil().setWidth(13), |
|||
height: 1 |
|||
), |
|||
decoration: InputDecoration( |
|||
isDense: true, |
|||
contentPadding: EdgeInsets.only( |
|||
left: 0, |
|||
right: -8 |
|||
), |
|||
hintText: "输入趣恋恋ID/昵称进行搜索", |
|||
|
|||
border: const OutlineInputBorder( |
|||
borderSide: BorderSide.none, // 这将移除边框 // 可选:设置圆角 |
|||
), |
|||
// 如果你希望聚焦时和未聚焦时都没有边框,也可以设置 focusedBorder 和 enabledBorder |
|||
focusedBorder: const OutlineInputBorder( |
|||
borderSide: BorderSide.none, |
|||
borderRadius: BorderRadius.all(Radius.circular(4.0)), |
|||
), |
|||
enabledBorder: const OutlineInputBorder( |
|||
borderSide: BorderSide.none, |
|||
borderRadius: BorderRadius.all(Radius.circular(4.0)), |
|||
), |
|||
), |
|||
onChanged: controller.onTextChanged, |
|||
), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
), |
|||
Expanded( |
|||
child: SingleChildScrollView( |
|||
child: Container( |
|||
padding: EdgeInsets.symmetric(horizontal: 15.w), |
|||
child: Column( |
|||
children: [ |
|||
if(controller.matchmakerList.isEmpty) Container( |
|||
padding: EdgeInsets.only(top: 45.w), |
|||
child: Column( |
|||
children: [ |
|||
Text( |
|||
"仅支持搜索", |
|||
style: TextStyle( |
|||
color: const Color.fromRGBO(121, 121, 121, 1) |
|||
), |
|||
), |
|||
Text( |
|||
"趣恋恋主持人" |
|||
) |
|||
], |
|||
), |
|||
), |
|||
...controller.matchmakerList.map((e){ |
|||
return SearchItem(item: e,); |
|||
}), |
|||
], |
|||
), |
|||
), |
|||
), |
|||
) |
|||
], |
|||
), |
|||
); |
|||
}, |
|||
); |
|||
} |
|||
} |
|||
|
|||
class SearchItem extends StatefulWidget { |
|||
final Records item; |
|||
const SearchItem({super.key, required this.item}); |
|||
|
|||
@override |
|||
State<SearchItem> createState() => _SearchItemState(); |
|||
} |
|||
|
|||
class _SearchItemState extends State<SearchItem> { |
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return Container( |
|||
margin: EdgeInsets.only(bottom: 10.w), |
|||
child: Row( |
|||
children: [ |
|||
CachedNetworkImage( |
|||
imageUrl: '${widget.item.profilePhoto}?x-oss-process=image/format,webp/resize,w_320', |
|||
width: 60, |
|||
height: 60, |
|||
fit: BoxFit.cover, |
|||
imageBuilder: (context, imageProvider) => Container( |
|||
decoration: BoxDecoration( |
|||
borderRadius: BorderRadius.circular(60), |
|||
image: DecorationImage( |
|||
image: imageProvider, |
|||
fit: BoxFit.cover, |
|||
), |
|||
), |
|||
), |
|||
errorWidget: (context, url, error) => Image.asset( |
|||
Assets.imagesUserAvatar, |
|||
width: 60, |
|||
height: 60, |
|||
fit: BoxFit.cover, |
|||
), |
|||
), |
|||
SizedBox(width: 15.w,), |
|||
Expanded( |
|||
child: Column( |
|||
crossAxisAlignment: CrossAxisAlignment.start, |
|||
children: [ |
|||
Row( |
|||
children: [ |
|||
Flexible( |
|||
child: Text( |
|||
widget.item.nickName ?? '用户', |
|||
style: TextStyle( |
|||
fontSize: 16, |
|||
fontWeight: FontWeight.w500, |
|||
color: Color.fromRGBO(51, 51, 51, 1), |
|||
), |
|||
maxLines: 1, |
|||
overflow: TextOverflow.ellipsis, |
|||
), |
|||
), |
|||
SizedBox(width: 10.w,), |
|||
if(widget.item.liveStreaming ?? false)Container( |
|||
width: 33.w, |
|||
height: 13.w, |
|||
margin: EdgeInsets.only(left: 6.w), |
|||
decoration: BoxDecoration( |
|||
borderRadius: BorderRadius.all(Radius.circular(13.w)), |
|||
color: const Color.fromRGBO(234, 255, 219, 1) |
|||
), |
|||
child: Center( |
|||
child: Text( |
|||
"在线", |
|||
style: TextStyle( |
|||
fontSize: 11.w, |
|||
color: const Color.fromRGBO(38, 199, 124, 1) |
|||
), |
|||
), |
|||
), |
|||
) |
|||
], |
|||
), |
|||
|
|||
const SizedBox(height: 6), |
|||
SizedBox( |
|||
height: 16, |
|||
child: Text( |
|||
'${widget.item.age ?? 0}岁 ${(widget.item.cityName ?? '') != "" ? "· " : ""}${widget.item.cityName ?? ''}', |
|||
style: TextStyle( |
|||
fontSize: 12, |
|||
color: Color.fromRGBO(51, 51, 51, 1), |
|||
), |
|||
overflow: TextOverflow.ellipsis, |
|||
), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
SizedBox(width: 15.w,), |
|||
Container( |
|||
width: 60.w, |
|||
height: 30.w, |
|||
decoration: BoxDecoration( |
|||
borderRadius: BorderRadius.all(Radius.circular(30.w)), |
|||
color: const Color.fromRGBO(117, 98, 249, 1) |
|||
), |
|||
child: Center( |
|||
child: Text( |
|||
"发消息", |
|||
style: TextStyle( |
|||
fontSize: 13.w, |
|||
color: Colors.white |
|||
), |
|||
), |
|||
), |
|||
).onTap(() async { |
|||
if (widget.item.userId?.isEmpty ?? false) { |
|||
SmartDialog.showToast('用户ID不存在,无法发送消息'); |
|||
return; |
|||
} |
|||
|
|||
try { |
|||
final _userApi = Get.find<UserApi>(); |
|||
final response = await _userApi.getDongwoMarriageInformationDetail(miId: widget.item.miId ?? ""); |
|||
if (response.data.isSuccess && response.data.data != null) { |
|||
|
|||
|
|||
// 使用工厂方法将 UserInfoData 转换为 MarriageData |
|||
final marriageData = MarriageData.fromUserInfoData(response.data.data ?? UserInfoData()); |
|||
|
|||
|
|||
await Get.to(() => ChatPage( |
|||
userId: widget.item.userId ?? "", |
|||
userData: marriageData, |
|||
)); |
|||
|
|||
|
|||
} else { |
|||
// 响应失败,设置错误信息 |
|||
final errorMsg = response.data.message ?? '获取数据失败'; |
|||
|
|||
SmartDialog.showToast(errorMsg); |
|||
} |
|||
} catch(e, stackTrace){ |
|||
print('获取用户信息失败: $e'); |
|||
print('堆栈跟踪: $stackTrace'); |
|||
|
|||
SmartDialog.showToast('获取用户信息失败,请稍后重试'); |
|||
// 不再 rethrow,避免导致闪退 |
|||
} |
|||
}) |
|||
], |
|||
), |
|||
).onTap(() async { |
|||
if (widget.item.liveStreaming ?? false) { |
|||
late final RoomController roomController; |
|||
|
|||
if (Get.isRegistered<RoomController>()) { |
|||
roomController = Get.find<RoomController>(); |
|||
} else { |
|||
roomController = Get.put(RoomController()); |
|||
} |
|||
await roomController.joinChannel(widget.item.isLiveChannelId ?? ""); |
|||
} else { |
|||
Get.to(() => UserInformationPage(miId: widget.item.miId ?? "")); |
|||
} |
|||
|
|||
}); |
|||
} |
|||
} |
|||
|
|||
Write
Preview
Loading…
Cancel
Save