Browse Source

Merge branch 'master' of http://git.qniao.cn/dating-agency/dating_touchme_app

* 'master' of http://git.qniao.cn/dating-agency/dating_touchme_app:
  feat(live): 实现直播间RTC频道详情获取与展示优化
ios
ZHR007 4 months ago
parent
commit
ee65fef89c
13 changed files with 220 additions and 135 deletions
  1. BIN
      assets/images/room_video.png
  2. 58
      lib/controller/discover/room_controller.dart
  3. 1
      lib/generated/assets.dart
  4. 2
      lib/network/api_service.g.dart
  5. 89
      lib/network/api_urls.dart
  6. 2
      lib/network/home_api.g.dart
  7. 10
      lib/network/rtc_api.dart
  8. 36
      lib/network/rtc_api.g.dart
  9. 2
      lib/network/user_api.g.dart
  10. 36
      lib/pages/discover/live_room_page.dart
  11. 17
      lib/rtc/rtm_manager.dart
  12. 52
      lib/widget/live/live_room_notice_chat_panel.dart
  13. 50
      lib/widget/live/live_room_user_header.dart

BIN
assets/images/room_video.png

Before After
Width: 72  |  Height: 54  |  Size: 610 B

58
lib/controller/discover/room_controller.dart

@ -1,7 +1,7 @@
import 'package:agora_rtc_engine/agora_rtc_engine.dart';
import 'package:agora_token_generator/agora_token_generator.dart';
import 'package:dating_touchme_app/model/live/live_chat_message.dart';
import 'package:dating_touchme_app/model/rtc/rtc_channel_data.dart';
import 'package:dating_touchme_app/model/rtc/rtc_channel_detail.dart';
import 'package:dating_touchme_app/network/network_service.dart';
import 'package:dating_touchme_app/rtc/rtc_manager.dart';
import 'package:dating_touchme_app/service/live_chat_message_service.dart';
@ -12,18 +12,20 @@ import 'package:permission_handler/permission_handler.dart';
///
class RoomController extends GetxController {
RoomController({NetworkService? networkService})
: _networkService = networkService ?? Get.find<NetworkService>();
: _networkService = networkService ?? Get.find<NetworkService>();
final NetworkService _networkService;
///
final Rxn<RtcChannelData> rtcChannel = Rxn<RtcChannelData>();
final Rxn<RtcChannelDetail> rtcChannelDetail = Rxn<RtcChannelDetail>();
///
final RxList<LiveChatMessage> chatMessages = <LiveChatMessage>[].obs;
///
final LiveChatMessageService _messageService = LiveChatMessageService.instance;
final LiveChatMessageService _messageService =
LiveChatMessageService.instance;
@override
void onInit() {
@ -54,10 +56,12 @@ class RoomController extends GetxController {
///
void _addMessage(LiveChatMessage message) {
// userId + content + timestamp
final exists = chatMessages.any((m) =>
m.userId == message.userId &&
m.content == message.content &&
(m.timestamp - message.timestamp).abs() < 1000); // 1
final exists = chatMessages.any(
(m) =>
m.userId == message.userId &&
m.content == message.content &&
(m.timestamp - message.timestamp).abs() < 1000,
); // 1
if (exists) {
print('⚠️ 消息已存在,跳过添加');
@ -84,7 +88,12 @@ class RoomController extends GetxController {
final base = response.data;
if (base.isSuccess && base.data != null) {
rtcChannel.value = base.data;
await _joinRtcChannel(base.data!.token, base.data!.channelId, base.data!.uid, ClientRoleType.clientRoleBroadcaster);
await _joinRtcChannel(
base.data!.token,
base.data!.channelId,
base.data!.uid,
ClientRoleType.clientRoleBroadcaster,
);
} else {
final message = base.message.isNotEmpty ? base.message : '创建频道失败';
SmartDialog.showToast(message);
@ -100,7 +109,12 @@ class RoomController extends GetxController {
final base = response.data;
if (base.isSuccess && base.data != null) {
rtcChannel.value = base.data;
await _joinRtcChannel(base.data!.token, channelName, base.data!.uid, ClientRoleType.clientRoleAudience);
await _joinRtcChannel(
base.data!.token,
channelName,
base.data!.uid,
ClientRoleType.clientRoleAudience,
);
}
} catch (e) {
SmartDialog.showToast('加入频道异常:$e');
@ -111,9 +125,10 @@ class RoomController extends GetxController {
String token,
String channelName,
int uid,
ClientRoleType roleType
ClientRoleType roleType,
) async {
try {
await _fetchRtcChannelDetail(channelName);
await RTCManager.instance.joinChannel(
token: token,
channelId: channelName,
@ -125,9 +140,24 @@ class RoomController extends GetxController {
}
}
Future<void> _fetchRtcChannelDetail(String channelName) async {
try {
final response = await _networkService.rtcApi.getRtcChannelDetail(
channelName,
);
final base = response.data;
if (base.isSuccess && base.data != null) {
rtcChannelDetail.value = base.data;
}
} catch (e) {
print('获取 RTC 频道详情失败:$e');
}
}
///
Future<void> sendChatMessage(String content) async {
final channelName = rtcChannel.value?.channelId ?? RTCManager.instance.currentChannelId;
final channelName =
rtcChannel.value?.channelId ?? RTCManager.instance.currentChannelId;
final result = await _messageService.sendMessage(
content: content,
@ -152,8 +182,9 @@ class RoomController extends GetxController {
return true;
}
final permanentlyDenied =
statuses.values.any((status) => status.isPermanentlyDenied);
final permanentlyDenied = statuses.values.any(
(status) => status.isPermanentlyDenied,
);
if (permanentlyDenied) {
SmartDialog.showToast('请在系统设置中开启摄像头和麦克风权限');
await openAppSettings();
@ -167,4 +198,3 @@ class RoomController extends GetxController {
await RTCManager.instance.leaveChannel();
}
}

1
lib/generated/assets.dart

@ -150,6 +150,7 @@ class Assets {
static const String imagesRocket1 = 'assets/images/rocket1.svga';
static const String imagesRocket2 = 'assets/images/rocket2.svga';
static const String imagesRocket3 = 'assets/images/rocket3.svga';
static const String imagesRoomVideo = 'assets/images/room_video.png';
static const String imagesRose = 'assets/images/rose.png';
static const String imagesRoseBanner = 'assets/images/rose_banner.png';
static const String imagesRoseGift = 'assets/images/rose_gift.png';

2
lib/network/api_service.g.dart

@ -8,7 +8,7 @@ part of 'api_service.dart';
// RetrofitGenerator
// **************************************************************************
// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations,unused_element_parameter,avoid_unused_constructor_parameters,unreachable_from_main
// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations,unused_element_parameter
class _ApiService implements ApiService {
_ApiService(this._dio, {this.baseUrl, this.errorLogger}) {

89
lib/network/api_urls.dart

@ -3,40 +3,69 @@
class ApiUrls {
//
static const String login = 'dating-agency-uec/authorize/by-captcha';
static const String getVerificationCode = 'dating-agency-uec/authorize/get/auth-captcha';
static const String getVerificationCode =
'dating-agency-uec/authorize/get/auth-captcha';
//
static const String getBaseUserInfo = 'dating-agency-uec/user/get/base-info';
static const String updatePhone = 'dating-agency-uec/user/modify/phone';
static const String getMarriageInformationDetail = 'dating-agency-service/user/get/dongwo/marriage-information-detail';
static const String registerMarriageInformation = 'dating-agency-service/user/register/marriage-information';
static const String getHxUserToken = 'dating-agency-chat-audio/user/get/hx/user/token';
static const String getApplyTempAuth = 'dating-agency-uec/get/apply-temp-auth';
static const String saveCertificationAudit = 'dating-agency-service/user/save/certification/audit';
static const String listVirtualCurrencyProduct = 'dating-agency-mall/user/list/virtual-currency-product';
static const String getVirtualAccount = 'dating-agency-mall/user/get/virtual-account';
static const String pageVirtualAccountRecord = 'dating-agency-mall/user/page/virtual-account-record';
static const String getCertificationList = 'dating-agency-service/user/get/certification/item/all/list';
static const String getMarriageInformationDetail =
'dating-agency-service/user/get/dongwo/marriage-information-detail';
static const String registerMarriageInformation =
'dating-agency-service/user/register/marriage-information';
static const String getHxUserToken =
'dating-agency-chat-audio/user/get/hx/user/token';
static const String getApplyTempAuth =
'dating-agency-uec/get/apply-temp-auth';
static const String saveCertificationAudit =
'dating-agency-service/user/save/certification/audit';
static const String listVirtualCurrencyProduct =
'dating-agency-mall/user/list/virtual-currency-product';
static const String getVirtualAccount =
'dating-agency-mall/user/get/virtual-account';
static const String pageVirtualAccountRecord =
'dating-agency-mall/user/page/virtual-account-record';
static const String getCertificationList =
'dating-agency-service/user/get/certification/item/all/list';
static const String submitOrder = 'dating-agency-mall/user/submit/order';
static const String getEducationList = 'dating-agency-service/user/get/education/list';
static const String getIncomeList = 'dating-agency-service/user/get/income/list';
static const String getMaritalStatusList = 'dating-agency-service/user/get/marital/status/list';
static const String getPropertyList = 'dating-agency-service/user/get/property/permits';
static const String getOccupationList = 'dating-agency-service/user/get/occupation/list';
static const String getSwRtcToken = 'dating-agency-chat-audio/user/get/sw/rtc/token';
static const String createRtcChannel = 'dating-agency-chat-audio/user/create/rtc-channel';
static const String editOwnMarriageInformation = 'dating-agency-service/user/edit/own-marriage-information';
static const String getSwRtmToken = 'dating-agency-chat-audio/user/get/sw/rtm/token';
static const String listBankCardByIndividual = 'dating-agency-mall/user/list/bank-card/by-individual';
static const String createBankCardByIndividual = 'dating-agency-mall/user/create/bank-card/by-individual';
static const String recognizeBankCard = 'dating-agency-uec/user/recognize/bank-card';
static const String calculateWithdrawServiceFee = 'dating-agency-mall/user/calculate/withdraw-service-fee';
static const String applyWalletAccountWithdraw = 'dating-agency-mall/user/apply/wallet-account/withdraw';
static const String pageWithdrawAudit = 'dating-agency-mall/user/page/withdraw-audit';
static const String getMarriageInformationDetailsById = 'dating-agency-service/user/get/marriage/information/details/byid';
static const String getEducationList =
'dating-agency-service/user/get/education/list';
static const String getIncomeList =
'dating-agency-service/user/get/income/list';
static const String getMaritalStatusList =
'dating-agency-service/user/get/marital/status/list';
static const String getPropertyList =
'dating-agency-service/user/get/property/permits';
static const String getOccupationList =
'dating-agency-service/user/get/occupation/list';
static const String getSwRtcToken =
'dating-agency-chat-audio/user/get/sw/rtc/token';
static const String createRtcChannel =
'dating-agency-chat-audio/user/create/rtc-channel';
static const String editOwnMarriageInformation =
'dating-agency-service/user/edit/own-marriage-information';
static const String getSwRtmToken =
'dating-agency-chat-audio/user/get/sw/rtm/token';
static const String getRtcChannelDetail =
'dating-agency-chat-audio/user/get/dating-rtc-channel/detail';
static const String listBankCardByIndividual =
'dating-agency-mall/user/list/bank-card/by-individual';
static const String createBankCardByIndividual =
'dating-agency-mall/user/create/bank-card/by-individual';
static const String recognizeBankCard =
'dating-agency-uec/user/recognize/bank-card';
static const String calculateWithdrawServiceFee =
'dating-agency-mall/user/calculate/withdraw-service-fee';
static const String applyWalletAccountWithdraw =
'dating-agency-mall/user/apply/wallet-account/withdraw';
static const String pageWithdrawAudit =
'dating-agency-mall/user/page/withdraw-audit';
static const String getMarriageInformationDetailsById =
'dating-agency-service/user/get/marriage/information/details/byid';
//
static const String getMarriageList = 'dating-agency-service/user/page/dongwo/marriage-information';
static const String getMarriageList =
'dating-agency-service/user/page/dongwo/marriage-information';
// API端点
}
}

2
lib/network/home_api.g.dart

@ -8,7 +8,7 @@ part of 'home_api.dart';
// RetrofitGenerator
// **************************************************************************
// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations,unused_element_parameter,avoid_unused_constructor_parameters,unreachable_from_main
// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations,unused_element_parameter
class _HomeApi implements HomeApi {
_HomeApi(this._dio, {this.baseUrl, this.errorLogger});

10
lib/network/rtc_api.dart

@ -1,4 +1,5 @@
import 'package:dating_touchme_app/model/rtc/rtc_channel_data.dart';
import 'package:dating_touchme_app/model/rtc/rtc_channel_detail.dart';
import 'package:dating_touchme_app/network/api_urls.dart';
import 'package:dating_touchme_app/network/response_model.dart';
import 'package:dio/dio.dart';
@ -14,7 +15,7 @@ abstract class RtcApi {
///
@GET(ApiUrls.getSwRtcToken)
Future<HttpResponse<BaseResponse<RtcChannelData>>> getSwRtcToken(
@Query('channelId') String channelId,
@Query('channelId') String channelId,
);
/// RTM Token
@ -24,5 +25,10 @@ abstract class RtcApi {
///
@POST(ApiUrls.createRtcChannel)
Future<HttpResponse<BaseResponse<RtcChannelData>>> createRtcChannel();
}
/// RTC
@GET(ApiUrls.getRtcChannelDetail)
Future<HttpResponse<BaseResponse<RtcChannelDetail>>> getRtcChannelDetail(
@Query('channelId') String channelId,
);
}

36
lib/network/rtc_api.g.dart

@ -8,7 +8,7 @@ part of 'rtc_api.dart';
// RetrofitGenerator
// **************************************************************************
// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations,unused_element_parameter,avoid_unused_constructor_parameters,unreachable_from_main
// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations,unused_element_parameter
class _RtcApi implements RtcApi {
_RtcApi(this._dio, {this.baseUrl, this.errorLogger});
@ -114,6 +114,40 @@ class _RtcApi implements RtcApi {
return httpResponse;
}
@override
Future<HttpResponse<BaseResponse<RtcChannelDetail>>> getRtcChannelDetail(
String channelId,
) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{r'channelId': channelId};
final _headers = <String, dynamic>{};
const Map<String, dynamic>? _data = null;
final _options =
_setStreamType<HttpResponse<BaseResponse<RtcChannelDetail>>>(
Options(method: 'GET', headers: _headers, extra: _extra)
.compose(
_dio.options,
'dating-agency-chat-audio/user/get/dating-rtc-channel/detail',
queryParameters: queryParameters,
data: _data,
)
.copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)),
);
final _result = await _dio.fetch<Map<String, dynamic>>(_options);
late BaseResponse<RtcChannelDetail> _value;
try {
_value = BaseResponse<RtcChannelDetail>.fromJson(
_result.data!,
(json) => RtcChannelDetail.fromJson(json as Map<String, dynamic>),
);
} on Object catch (e, s) {
errorLogger?.logError(e, s, _options);
rethrow;
}
final httpResponse = HttpResponse(_value, _result);
return httpResponse;
}
RequestOptions _setStreamType<T>(RequestOptions requestOptions) {
if (T != dynamic &&
!(requestOptions.responseType == ResponseType.bytes ||

2
lib/network/user_api.g.dart

@ -8,7 +8,7 @@ part of 'user_api.dart';
// RetrofitGenerator
// **************************************************************************
// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations,unused_element_parameter,avoid_unused_constructor_parameters,unreachable_from_main
// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations,unused_element_parameter
class _UserApi implements UserApi {
_UserApi(this._dio, {this.baseUrl, this.errorLogger});

36
lib/pages/discover/live_room_page.dart

@ -89,7 +89,7 @@ class _LiveRoomPageState extends State<LiveRoomPage> {
//
await _roomController.sendChatMessage(content);
//
_messageController.clear();
message = '';
@ -159,29 +159,23 @@ class _LiveRoomPageState extends State<LiveRoomPage> {
child: Column(
children: [
SizedBox(height: 10.w),
Builder(
builder: (context) {
// GlobalData
final userData = GlobalData().userData;
final userName = userData?.nickName ?? '用户';
//
final popularityText = '0'; // TODO:
return LiveRoomUserHeader(
userName: userName,
popularityText: popularityText,
avatarAsset: (userData?.profilePhoto != null &&
userData!.profilePhoto!.isNotEmpty)
? userData.profilePhoto!
: Assets.imagesUserAvatar,
);
},
),
Obx(() {
final detail = _roomController.rtcChannelDetail.value;
final anchorInfo = detail?.anchorInfo;
final userName = anchorInfo!.nickName;
final avatarAsset = anchorInfo.profilePhoto;
const popularityText = '0'; // TODO: 使
return LiveRoomUserHeader(
userName: userName,
popularityText: popularityText,
avatarAsset: avatarAsset,
);
}),
SizedBox(height: 7.w),
LiveRoomAnchorShowcase(),
SizedBox(height: 5.w),
const LiveRoomSeatList(),
SizedBox(height: 5.w),
const LiveRoomActiveSpeaker(),
SizedBox(height: 9.w),
const LiveRoomNoticeChatPanel(),

17
lib/rtc/rtm_manager.dart

@ -63,7 +63,8 @@ class RTMManager {
await dispose();
final (status, client) = await RTM(appId, userId, config: config);
print('RTM初始化成功');
print('----------RTM $userId');
print(status.error ? '❌ RTM 初始化失败' : '✅ RTM 初始化成功');
if (status.error) {
onOperationError?.call(status);
@ -74,13 +75,13 @@ class RTMManager {
_currentUserId = userId;
_isInitialized = true;
_registerClientListeners();
// final response = await _networkService.rtcApi.getSwRtmToken();
// //
// if (response.data.isSuccess) {
await login('007eJxTYIhoZ/m/gMf0gdWv1PV2R6I/Lpry06W77sOR2BDuFv89JeEKDCbJRqmJlinJSSbJpiYmBqaWxokmhhYpaQZpKSmmhkYpE9/IZgrwMTDYOB4rZ2RgYmAEQhCfkcEcAPTmHg4=');
// } else {
// SmartDialog.showToast(response.data.message);
// }
final response = await _networkService.rtcApi.getSwRtmToken();
//
if (response.data.isSuccess) {
await login(response.data.data!.token);
} else {
SmartDialog.showToast(response.data.message);
}
return true;
}

52
lib/widget/live/live_room_notice_chat_panel.dart

@ -9,7 +9,8 @@ class LiveRoomNoticeChatPanel extends StatefulWidget {
const LiveRoomNoticeChatPanel({super.key});
@override
State<LiveRoomNoticeChatPanel> createState() => _LiveRoomNoticeChatPanelState();
State<LiveRoomNoticeChatPanel> createState() =>
_LiveRoomNoticeChatPanelState();
}
class _LiveRoomNoticeChatPanelState extends State<LiveRoomNoticeChatPanel> {
@ -30,7 +31,6 @@ class _LiveRoomNoticeChatPanelState extends State<LiveRoomNoticeChatPanel> {
@override
Widget build(BuildContext context) {
return Container(
height: 230.w,
padding: EdgeInsets.only(left: 13.w, right: 9.w),
@ -73,14 +73,52 @@ class _LiveRoomNoticeChatPanelState extends State<LiveRoomNoticeChatPanel> {
}),
),
SizedBox(width: 18.w),
Image.asset(
Assets.imagesAd,
width: 73.w,
fit: BoxFit.cover,
Container(
width: 120.w,
height: 55.w,
padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 10.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10.w),
gradient: const LinearGradient(
colors: [Color(0xFF7C63FF), Color(0xFF987CFF)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: Row(
children: [
Image.asset(
Assets.imagesRoomVideo,
width: 26.w,
),
SizedBox(width: 8.w),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'免费连麦',
style: TextStyle(
fontSize: 13.w,
color: Colors.white,
fontWeight: FontWeight.w600,
),
),
SizedBox(height: 2.w),
Text(
'剩余2张相亲卡',
style: TextStyle(
fontSize: 9.w,
color: Colors.white.withOpacity(0.8),
),
),
],
),
],
),
),
],
),
);
}
}

50
lib/widget/live/live_room_user_header.dart

@ -39,8 +39,7 @@ class LiveRoomUserHeader extends StatelessWidget {
child: Row(
children: [
//
avatarAsset.startsWith('http://') || avatarAsset.startsWith('https://')
? ClipOval(
ClipOval(
child: Image.network(
avatarAsset,
width: 34.w,
@ -54,11 +53,6 @@ class LiveRoomUserHeader extends StatelessWidget {
);
},
),
)
: Image.asset(
avatarAsset,
width: 34.w,
height: 34.w,
),
SizedBox(width: 7.w),
Column(
@ -72,50 +66,8 @@ class LiveRoomUserHeader extends StatelessWidget {
color: Colors.white,
),
),
SizedBox(height: 2.w),
Row(
children: [
Image.asset(
fireIconAsset,
width: 10.w,
height: 12.w,
),
SizedBox(width: 4.w),
Text(
popularityText,
style: TextStyle(
fontSize: 10.w,
color: Colors.white,
fontWeight: FontWeight.w500,
),
),
],
),
],
),
SizedBox(width: 15.w),
GestureDetector(
onTap: onFollowTap,
child: Container(
width: 47.w,
height: 27.w,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(27.w)),
color: const Color.fromRGBO(253, 43, 84, 1),
),
child: Center(
child: Text(
'关注',
style: TextStyle(
fontSize: 13.w,
color: Colors.white,
fontWeight: FontWeight.w500,
height: 1,
),
),
),
),
),
],
),
),

Loading…
Cancel
Save