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(main): 添加SVGA动画播放功能并更新资源文件
  refactor(discover): 优化房间控制器逻辑
  feat(live): 实现直播观看功能并优化RTC管理
ios
ZHR007 4 months ago
parent
commit
f58929110f
21 changed files with 328 additions and 225 deletions
  1. 3
      android/app/build.gradle.kts
  2. 1
      android/app/src/main/AndroidManifest.xml
  3. 13
      android/app/src/main/res/xml/network_security_config.xml
  4. BIN
      assets/images/rocket1.svga
  5. BIN
      assets/images/rocket2.svga
  6. BIN
      assets/images/rocket3.svga
  7. 33
      lib/controller/discover/room_controller.dart
  8. 2
      lib/controller/mine/login_controller.dart
  9. 10
      lib/generated/assets.dart
  10. 4
      lib/main.dart
  11. 4
      lib/network/rtc_api.dart
  12. 6
      lib/network/rtc_api.g.dart
  13. 18
      lib/pages/discover/discover_page.dart
  14. 2
      lib/pages/discover/live_room_page.dart
  15. 15
      lib/pages/main/main_page.dart
  16. 116
      lib/pages/mine/login_page.dart
  17. 79
      lib/rtc/rtc_manager.dart
  18. 14
      lib/rtc/rtm_manager.dart
  19. 184
      lib/widget/live/live_room_anchor_showcase.dart
  20. 48
      pubspec.lock
  21. 1
      pubspec.yaml

3
android/app/build.gradle.kts

@ -55,6 +55,9 @@ android {
ndk {
abiFilters.clear()
abiFilters += "arm64-v8a"
abiFilters += "armeabi-v7a"
abiFilters += "x86"
abiFilters += "x86_64"
// 或者:abiFilters.add("arm64-v8a")
}

1
android/app/src/main/AndroidManifest.xml

@ -8,6 +8,7 @@
<application
android:label="动我"
android:name="${applicationName}"
android:networkSecurityConfig="@xml/network_security_config">
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"

13
android/app/src/main/res/xml/network_security_config.xml

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="false">
<trust-anchors>
<!-- 信任系统默认CA -->
<certificates src="system" />
</trust-anchors>
<!-- 强制TLS 1.2+ -->
<tls-config>
<min-tls-version>TLSv1.2</min-tls-version>
</tls-config>
</base-config>
</network-security-config>

BIN
assets/images/rocket1.svga

BIN
assets/images/rocket2.svga

BIN
assets/images/rocket3.svga

33
lib/controller/discover/room_controller.dart

@ -1,3 +1,5 @@
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/network/network_service.dart';
@ -17,9 +19,6 @@ class RoomController extends GetxController {
///
final Rxn<RtcChannelData> rtcChannel = Rxn<RtcChannelData>();
///
final RxBool isLoading = false.obs;
///
final RxList<LiveChatMessage> chatMessages = <LiveChatMessage>[].obs;
@ -77,37 +76,49 @@ class RoomController extends GetxController {
/// RTC
Future<void> createRtcChannel() async {
if (isLoading.value) return;
final granted = await _ensureRtcPermissions();
if (!granted) return;
try {
isLoading.value = true;
final response = await _networkService.rtcApi.createRtcChannel();
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);
await _joinRtcChannel(base.data!.token, base.data!.channelId, base.data!.uid, ClientRoleType.clientRoleBroadcaster);
} else {
final message = base.message.isNotEmpty ? base.message : '创建频道失败';
SmartDialog.showToast(message);
}
} catch (e) {
SmartDialog.showToast('创建频道异常:$e');
} finally {
isLoading.value = false;
}
}
Future<void> joinChannel(String channelName) async {
try {
final response = await _networkService.rtcApi.getSwRtcToken(channelName);
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);
}
} catch (e) {
SmartDialog.showToast('加入频道异常:$e');
}
}
Future<void> _joinRtcChannel(
String token,
String channelName,
int uid,
ClientRoleType roleType
) async {
try {
await RTCManager.instance.joinChannel(
token: token,
channelId: channelName,
uid: uid,
role: roleType,
);
} catch (e) {
SmartDialog.showToast('加入频道失败:$e');
@ -117,7 +128,7 @@ class RoomController extends GetxController {
///
Future<void> sendChatMessage(String content) async {
final channelName = rtcChannel.value?.channelId ?? RTCManager.instance.currentChannelId;
final result = await _messageService.sendMessage(
content: content,
channelName: channelName,
@ -152,8 +163,8 @@ class RoomController extends GetxController {
return false;
}
Future<void> disposeRtcResources() async {
await RTCManager.instance.dispose();
Future<void> leaveChannel() async {
await RTCManager.instance.leaveChannel();
}
}

2
lib/controller/mine/login_controller.dart

@ -115,7 +115,7 @@ class LoginController extends GetxController {
GlobalData().userId = result.userId;
GlobalData().qnToken = result.token;
await storage.write('token', result.token);
// await storage.write('userId', result.userId);
await storage.write('userId', result.userId);
//
await _handleUserInfoRetrieval(result.userId);

10
lib/generated/assets.dart

@ -66,8 +66,8 @@ class Assets {
static const String emojiEmoji62 = 'assets/images/emoji/emoji_62.png';
static const String emojiEmoji63 = 'assets/images/emoji/emoji_63.png';
static const String emojiEmoji64 = 'assets/images/emoji/emoji_64.png';
static const String imagesAd = 'assets/images/ad.png';
static const String imagesAcceptCall = 'assets/images/accept_call.png';
static const String imagesAd = 'assets/images/ad.png';
static const String imagesAdd = 'assets/images/add.png';
static const String imagesAliPay = 'assets/images/ali_pay.png';
static const String imagesArrow = 'assets/images/arrow.png';
@ -110,6 +110,7 @@ class Assets {
static const String imagesGift4 = 'assets/images/gift4.png';
static const String imagesGift5 = 'assets/images/gift5.png';
static const String imagesGiftIcon = 'assets/images/gift_icon.png';
static const String imagesGiftPic = 'assets/images/gift_pic.png';
static const String imagesHelpBg = 'assets/images/help_bg.png';
static const String imagesHiIcon = 'assets/images/hi_icon.png';
static const String imagesHomeNol = 'assets/images/home_nol.png';
@ -137,15 +138,18 @@ class Assets {
static const String imagesPhoto = 'assets/images/photo.png';
static const String imagesPhotoChecked = 'assets/images/photo_checked.png';
static const String imagesPhotoUncheck = 'assets/images/photo_uncheck.png';
static const String imagesPlayIcon = 'assets/images/play_icon.png';
static const String imagesPlayer = 'assets/images/player.png';
static const String imagesPlatVoiceMessage = 'assets/images/plat_voice_message.png';
static const String imagesPlatVoiceMessageSelf = 'assets/images/plat_voice_message_self.png';
static const String imagesPlayIcon = 'assets/images/play_icon.png';
static const String imagesPlayer = 'assets/images/player.png';
static const String imagesRealChecked = 'assets/images/real_checked.png';
static const String imagesRealName = 'assets/images/real_name.png';
static const String imagesRealUncheck = 'assets/images/real_uncheck.png';
static const String imagesRealnameHelp = 'assets/images/realname_help.png';
static const String imagesRejectCall = 'assets/images/reject_call.png';
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 imagesRose = 'assets/images/rose.png';
static const String imagesRoseBanner = 'assets/images/rose_banner.png';
static const String imagesRoseGift = 'assets/images/rose_gift.png';

4
lib/main.dart

@ -84,10 +84,10 @@ class MyApp extends StatelessWidget {
// token是否为空
final storage = GetStorage();
final userId = storage.read<String>('userId');
final token = storage.read<String>('token');
// token不为空token为空
if (userId != null && userId.isNotEmpty) {
if (token != null && token.isNotEmpty) {
return MainPage();
} else {
return LoginPage();

4
lib/network/rtc_api.dart

@ -13,7 +13,9 @@ abstract class RtcApi {
///
@GET(ApiUrls.getSwRtcToken)
Future<HttpResponse<BaseResponse<RtcChannelData>>> getSwRtcToken();
Future<HttpResponse<BaseResponse<RtcChannelData>>> getSwRtcToken(
@Query('channelId') String channelId,
);
/// RTM Token
@GET(ApiUrls.getSwRtmToken)

6
lib/network/rtc_api.g.dart

@ -20,9 +20,11 @@ class _RtcApi implements RtcApi {
final ParseErrorLogger? errorLogger;
@override
Future<HttpResponse<BaseResponse<RtcChannelData>>> getSwRtcToken() async {
Future<HttpResponse<BaseResponse<RtcChannelData>>> getSwRtcToken(
String channelId,
) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final queryParameters = <String, dynamic>{r'channelId': channelId};
final _headers = <String, dynamic>{};
const Map<String, dynamic>? _data = null;
final _options = _setStreamType<HttpResponse<BaseResponse<RtcChannelData>>>(

18
lib/pages/discover/discover_page.dart

@ -149,11 +149,25 @@ class LiveItem extends StatefulWidget {
}
class _LiveItemState extends State<LiveItem> {
late final RoomController roomController;
@override
void initState() {
super.initState();
if (Get.isRegistered<RoomController>()) {
roomController = Get.find<RoomController>();
} else {
roomController = Get.put(RoomController());
}
}
@override
Widget build(BuildContext context) {
return InkWell(
onTap: () {
Get.to(() => LiveRoomPage(id: 0));
onTap: () async{
// Get.to(() => LiveRoomPage(id: 0));
await roomController.joinChannel('1189028638616588288');
},
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(10.w)),

2
lib/pages/discover/live_room_page.dart

@ -75,7 +75,7 @@ class _LiveRoomPageState extends State<LiveRoomPage> {
@override
void dispose() {
_roomController.disposeRtcResources();
_roomController.leaveChannel();
_messageController.dispose();
super.dispose();
}

15
lib/pages/main/main_page.dart

@ -1,8 +1,11 @@
import 'package:dating_touchme_app/generated/assets.dart';
import 'package:dating_touchme_app/pages/main/tabbar/main_tab_bar.dart';
import 'package:dating_touchme_app/pages/message/message_page.dart';
import 'package:dating_touchme_app/pages/mine/mine_page.dart';
import 'package:dating_touchme_app/rtc/rtm_manager.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:flutter_svga/flutter_svga.dart';
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
@ -64,6 +67,18 @@ class _MainPageState extends State<MainPage> {
initRTM() async {
String? userId = storage.read<String>('userId');
await RTMManager.instance.initialize(appId: '4c2ea9dcb4c5440593a418df0fdd512d', userId: userId ?? '');
SmartDialog.show(
alignment: Alignment.center,
builder: (context){
return SVGAEasyPlayer(
assetsName: Assets.imagesRocket2,
fit: BoxFit.contain,
);
}
);
Future.delayed(const Duration(seconds: 4), () {
SmartDialog.dismiss();
});
}
@override

116
lib/pages/mine/login_page.dart

@ -7,7 +7,7 @@ import 'package:dating_touchme_app/controller/mine/login_controller.dart';
class LoginPage extends StatelessWidget {
LoginPage({super.key});
//
final agreeTerms = Rx<bool>(false);
@ -20,13 +20,16 @@ class LoginPage extends StatelessWidget {
resizeToAvoidBottomInset: false,
body: Stack(
children: [
Image.asset(
Assets.imagesLoginBg,
width: 1.sw,
height: 1.sh,
),
Container(
width: double.infinity,
height: 1.sh,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage(Assets.imagesLoginBg),
fit: BoxFit.cover,
alignment: Alignment.topCenter,
),
),
padding: EdgeInsets.symmetric(horizontal: 20),
child: Column(
children: [
@ -35,10 +38,7 @@ class LoginPage extends StatelessWidget {
Center(
child: Column(
children: [
Image.asset(
Assets.imagesLoginLogo,
height: 60,
),
Image.asset(Assets.imagesLoginLogo, height: 60),
const SizedBox(height: 10),
const Text(
'心动就动我 幸福马上行动',
@ -70,7 +70,10 @@ class LoginPage extends StatelessWidget {
decoration: const InputDecoration(
hintText: '请输入你的手机号',
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(horizontal: 15, vertical: 14),
contentPadding: EdgeInsets.symmetric(
horizontal: 15,
vertical: 14,
),
counterText: '',
),
keyboardType: TextInputType.phone,
@ -101,7 +104,10 @@ class LoginPage extends StatelessWidget {
decoration: const InputDecoration(
hintText: '请输入验证码',
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(horizontal: 15, vertical: 14),
contentPadding: EdgeInsets.symmetric(
horizontal: 15,
vertical: 14,
),
counterText: '',
),
keyboardType: TextInputType.number,
@ -114,18 +120,25 @@ class LoginPage extends StatelessWidget {
),
//
GestureDetector(
onTap: controller.isSendingCode.value || controller.countdownSeconds.value > 0
onTap:
controller.isSendingCode.value ||
controller.countdownSeconds.value > 0
? null
: controller.getVerificationCode,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 14),
padding: const EdgeInsets.symmetric(
horizontal: 15,
vertical: 14,
),
child: Text(
controller.countdownSeconds.value > 0
? '${controller.countdownSeconds.value}秒后重试'
: '获取验证码',
style: TextStyle(
fontSize: 14,
color: (controller.isSendingCode.value || controller.countdownSeconds.value > 0)
color:
(controller.isSendingCode.value ||
controller.countdownSeconds.value > 0)
? Colors.grey.shade400
: const Color.fromRGBO(74, 99, 235, 1),
),
@ -141,16 +154,19 @@ class LoginPage extends StatelessWidget {
//
Row(
children: [
Obx(() => Checkbox(
value: agreeTerms.value,
onChanged: (value) {
agreeTerms.value = value ?? false;
},
activeColor: Colors.grey,
side: const BorderSide(color: Colors.grey),
shape: const CircleBorder(),
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
)),
Obx(
() => Checkbox(
value: agreeTerms.value,
onChanged: (value) {
agreeTerms.value = value ?? false;
},
activeColor: Colors.grey,
side: const BorderSide(color: Colors.grey),
shape: const CircleBorder(),
materialTapTargetSize:
MaterialTapTargetSize.shrinkWrap,
),
),
const Text(
'我已阅读并同意',
style: TextStyle(
@ -199,18 +215,20 @@ class LoginPage extends StatelessWidget {
onPressed: controller.isLoggingIn.value
? null
: () {
//
if (!agreeTerms.value) {
SmartDialog.showToast('请阅读并同意用户协议和隐私政策');
return;
}
//
if (!agreeTerms.value) {
SmartDialog.showToast('请阅读并同意用户协议和隐私政策');
return;
}
//
controller.login();
},
//
controller.login();
},
style: ElevatedButton.styleFrom(
minimumSize: const Size(double.infinity, 50),
backgroundColor: agreeTerms.value ? const Color.fromRGBO(74, 99, 235, 1) : Colors.grey.shade300,
backgroundColor: agreeTerms.value
? const Color.fromRGBO(74, 99, 235, 1)
: Colors.grey.shade300,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(25),
),
@ -218,26 +236,26 @@ class LoginPage extends StatelessWidget {
),
child: controller.isLoggingIn.value
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
color: Colors.white,
strokeWidth: 2,
),
)
width: 20,
height: 20,
child: CircularProgressIndicator(
color: Colors.white,
strokeWidth: 2,
),
)
: const Text(
'注册并登录',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
'注册并登录',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
),
],
),
)
),
],
),
);

79
lib/rtc/rtc_manager.dart

@ -11,6 +11,9 @@ import '../pages/discover/live_room_page.dart';
class RTCManager {
/// UI监听
final ValueNotifier<bool> channelJoinedNotifier = ValueNotifier<bool>(false);
final ValueNotifier<List<int>> remoteUsersNotifier = ValueNotifier<List<int>>(
<int>[],
);
RtcEngine? get engine => _engine;
bool get isInChannel => _isInChannel;
int? get currentUid => _currentUid;
@ -26,8 +29,8 @@ class RTCManager {
bool _isInChannel = false;
String? _currentChannelId;
int? _currentUid;
int? _streamId;
ClientRoleType _clientRole = ClientRoleType.clientRoleBroadcaster;
final List<int> _remoteUserIds = <int>[];
//
Function(RtcConnection connection, int elapsed)? onJoinChannelSuccess;
Function(RtcConnection connection, int remoteUid, int elapsed)? onUserJoined;
@ -86,9 +89,9 @@ class RTCManager {
await _engine!.initialize(
RtcEngineContext(appId: appId, channelProfile: channelProfile),
);
await _engine?.setClientRole(role: ClientRoleType.clientRoleBroadcaster);
await _engine?.setClientRole(role: _clientRole);
await _engine?.enableVideo();
await _engine?.startPreview();
// await _engine?.startPreview();
//
_registerEventHandlers();
@ -107,16 +110,19 @@ class RTCManager {
_engine!.registerEventHandler(
RtcEngineEventHandler(
onJoinChannelSuccess: (RtcConnection connection, int elapsed) async{
onJoinChannelSuccess: (RtcConnection connection, int elapsed) async {
_isInChannel = true;
_remoteUserIds.clear();
remoteUsersNotifier.value = const [];
channelJoinedNotifier.value = true;
_currentChannelId = connection.channelId;
print('加入频道成功,频道名:${connection.channelId},耗时:${elapsed}ms');
if(connection.localUid == _currentUid){
if (connection.localUid == _currentUid) {
await RTMManager.instance.subscribe(_currentChannelId ?? '');
await RTMManager.instance.publishChannelMessage(
channelName: _currentChannelId ?? '',
message: json.encode({'type': 'join_room', 'uid': _currentUid})
channelName: _currentChannelId ?? '',
message: json.encode({'type': 'join_room', 'uid': _currentUid}),
);
Get.to(() => const LiveRoomPage(id: 0));
}
@ -126,13 +132,11 @@ class RTCManager {
},
onUserJoined: (RtcConnection connection, int remoteUid, int elapsed) {
print('用户加入,UID:$remoteUid');
_handleRemoteUserJoined(remoteUid);
if (onUserJoined != null) {
onUserJoined!(connection, remoteUid, elapsed);
}
},
onStreamMessage: (RtcConnection connection, int remoteUid, int streamId, Uint8List data, int length, int sentTs){
print('收到消息,UID:$remoteUid,流ID:$streamId,数据:${utf8.decode(data)}');
},
onUserOffline:
(
RtcConnection connection,
@ -140,12 +144,15 @@ class RTCManager {
UserOfflineReasonType reason,
) {
print('用户离开,UID:$remoteUid,原因:$reason');
_handleRemoteUserOffline(remoteUid);
if (onUserOffline != null) {
onUserOffline!(connection, remoteUid, reason);
}
},
onLeaveChannel: (RtcConnection connection, RtcStats stats) {
_isInChannel = false;
_remoteUserIds.clear();
remoteUsersNotifier.value = const [];
channelJoinedNotifier.value = false;
_currentChannelId = null;
print('离开频道,统计信息:${stats.duration}');
@ -343,7 +350,7 @@ class RTCManager {
String? token,
required String channelId,
int uid = 0,
ChannelMediaOptions? options,
ClientRoleType role = ClientRoleType.clientRoleBroadcaster,
}) async {
if (_engine == null) {
throw Exception('RTC Engine not initialized');
@ -352,15 +359,24 @@ class RTCManager {
print('已经在频道中,先离开当前频道');
await leaveChannel();
}
await setClientRole(role: role);
_currentUid = uid;
if (role == ClientRoleType.clientRoleBroadcaster) {
await _engine?.startPreview();
}
await _engine!.joinChannel(
token: token ?? '',
channelId: channelId,
uid: uid,
options: options ?? const ChannelMediaOptions(),
options: ChannelMediaOptions(
channelProfile: ChannelProfileType.channelProfileLiveBroadcasting,
clientRoleType: role,
autoSubscribeAudio: true,
autoSubscribeVideo: true,
publishCameraTrack: true,
publishMicrophoneTrack: true,
),
);
_streamId = await _engine?.createDataStream(DataStreamConfig(syncWithAudio: false, ordered: false));
print('正在加入频道:$channelId,UID:$uid');
}
@ -373,7 +389,7 @@ class RTCManager {
print('当前不在频道中');
return;
}
await RTMManager.instance.unsubscribe(_currentChannelId ?? '');
await _engine!.leaveChannel();
_currentUid = null;
print('已离开频道');
@ -417,24 +433,18 @@ class RTCManager {
if (_engine == null) {
throw Exception('RTC Engine not initialized');
}
_clientRole = role;
await _engine!.setClientRole(role: role, options: options);
print('客户端角色已设置为:$role');
}
///
Future<void> sendMessage(String message) async {
Uint8List data = utf8.encode(message);
await _engine!.sendStreamMessage(
streamId: _streamId ?? 0,
data: data,
length: data.length,
);
print('已发送消息:$message');
}
/// ID
String? get currentChannelId => _currentChannelId;
ClientRoleType get clientRole => _clientRole;
List<int> get remoteUserIds => List<int>.unmodifiable(_remoteUserIds);
///
Future<void> dispose() async {
try {
@ -445,6 +455,8 @@ class RTCManager {
await _engine!.release();
_engine = null;
}
_remoteUserIds.clear();
remoteUsersNotifier.value = const [];
_isInitialized = false;
_isInChannel = false;
_currentChannelId = null;
@ -455,4 +467,17 @@ class RTCManager {
print('Failed to dispose RTC Engine: $e');
}
}
void _handleRemoteUserJoined(int remoteUid) {
print('用户已加入频道:$remoteUid');
if (_remoteUserIds.contains(remoteUid)) return;
_remoteUserIds.add(remoteUid);
remoteUsersNotifier.value = List<int>.unmodifiable(_remoteUserIds);
}
void _handleRemoteUserOffline(int remoteUid) {
final removed = _remoteUserIds.remove(remoteUid);
if (!removed) return;
remoteUsersNotifier.value = List<int>.unmodifiable(_remoteUserIds);
}
}

14
lib/rtc/rtm_manager.dart

@ -74,13 +74,13 @@ class RTMManager {
_currentUserId = userId;
_isInitialized = true;
_registerClientListeners();
final response = await _networkService.rtcApi.getSwRtmToken();
//
if (response.data.isSuccess) {
await login(response.data.data?.token ?? '');
} else {
SmartDialog.showToast(response.data.message);
}
// final response = await _networkService.rtcApi.getSwRtmToken();
// //
// if (response.data.isSuccess) {
await login('007eJxTYIhoZ/m/gMf0gdWv1PV2R6I/Lpry06W77sOR2BDuFv89JeEKDCbJRqmJlinJSSbJpiYmBqaWxokmhhYpaQZpKSmmhkYpE9/IZgrwMTDYOB4rZ2RgYmAEQhCfkcEcAPTmHg4=');
// } else {
// SmartDialog.showToast(response.data.message);
// }
return true;
}

184
lib/widget/live/live_room_anchor_showcase.dart

@ -19,114 +19,132 @@ class _LiveRoomAnchorShowcaseState extends State<LiveRoomAnchorShowcase> {
return ValueListenableBuilder<bool>(
valueListenable: _rtcManager.channelJoinedNotifier,
builder: (context, joined, _) {
return Column(
children: [
Stack(
return ValueListenableBuilder<List<int>>(
valueListenable: _rtcManager.remoteUsersNotifier,
builder: (context, remoteUids, __) {
final int? remoteUid = remoteUids.isNotEmpty
? remoteUids.first
: null;
return Column(
children: [
_buildAnchorVideo(joined),
Positioned(
top: 5.w,
left: 5.w,
child: Container(
width: 42.w,
height: 13.w,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(13.w)),
color: const Color.fromRGBO(142, 20, 186, 1),
),
child: Center(
child: Text(
"主持人",
style: TextStyle(fontSize: 9.w, color: Colors.white),
Stack(
children: [
_buildAnchorVideo(joined, remoteUid),
Positioned(
top: 5.w,
left: 5.w,
child: Container(
width: 42.w,
height: 13.w,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(13.w)),
color: const Color.fromRGBO(142, 20, 186, 1),
),
child: Center(
child: Text(
"主持人",
style: TextStyle(
fontSize: 9.w,
color: Colors.white,
),
),
),
),
),
),
),
Positioned(
top: 5.w,
right: 5.w,
child: Container(
width: 20.w,
height: 20.w,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(20.w)),
color: const Color.fromRGBO(0, 0, 0, .3),
),
child: Center(
child: Image.asset(
Assets.imagesGiftIcon,
width: 19.w,
height: 19.w,
Positioned(
top: 5.w,
right: 5.w,
child: Container(
width: 20.w,
height: 20.w,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(20.w)),
color: const Color.fromRGBO(0, 0, 0, .3),
),
child: Center(
child: Image.asset(
Assets.imagesGiftIcon,
width: 19.w,
height: 19.w,
),
),
),
),
),
),
Positioned(
bottom: 5.w,
right: 5.w,
child: Container(
width: 47.w,
height: 20.w,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(20.w)),
color: Colors.white,
),
child: Center(
child: Text(
"加好友",
style: TextStyle(
fontSize: 11.w,
color: const Color.fromRGBO(117, 98, 249, 1),
Positioned(
bottom: 5.w,
right: 5.w,
child: Container(
width: 47.w,
height: 20.w,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(20.w)),
color: Colors.white,
),
child: Center(
child: Text(
"加好友",
style: TextStyle(
fontSize: 11.w,
color: const Color.fromRGBO(117, 98, 249, 1),
),
),
),
),
),
),
),
],
),
SizedBox(height: 5.w),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_buildSideAnchorCard(
isLeft: true,
micIcon: Assets.imagesMicClose,
],
),
_buildSideAnchorCard(
isLeft: false,
micIcon: Assets.imagesMicOpen,
SizedBox(height: 5.w),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_buildSideAnchorCard(
isLeft: true,
micIcon: Assets.imagesMicClose,
),
_buildSideAnchorCard(
isLeft: false,
micIcon: Assets.imagesMicOpen,
),
],
),
],
),
],
);
},
);
},
);
}
Widget _buildAnchorVideo(bool joined) {
Widget _buildAnchorVideo(bool joined, int? remoteUid) {
final engine = _rtcManager.engine;
if (!joined || engine == null) {
return _buildWaitingPlaceholder();
}
final localUid = _rtcManager.currentUid ?? 0;
print('joined: $joined');
ClientRoleType role = _rtcManager.clientRole;
return ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(9.w)),
child: SizedBox(
width: 177.w,
height: 175.w,
child: AgoraVideoView(
controller: VideoViewController(
rtcEngine: engine,
canvas: VideoCanvas(
uid: 0,
),
),
onAgoraVideoViewCreated: (viewId){
engine.startPreview();
},
),
child: role == ClientRoleType.clientRoleBroadcaster
? AgoraVideoView(
controller: VideoViewController(
rtcEngine: engine,
canvas: const VideoCanvas(uid: 0),
),
)
: (_rtcManager.currentChannelId == null
? _buildWaitingPlaceholder()
: AgoraVideoView(
controller: VideoViewController.remote(
rtcEngine: engine,
canvas: VideoCanvas(uid: _rtcManager.remoteUserIds.first),
connection: RtcConnection(
channelId: _rtcManager.currentChannelId!,
),
),
)),
),
);
}

48
pubspec.lock

@ -587,6 +587,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.9.8+9"
flutter_svga:
dependency: "direct main"
description:
name: flutter_svga
sha256: "8b96237fd33c80f3e9850245515d41c70520a4bcb9f3415e96b53fb798e6ab0a"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.0.8"
flutter_swiper_null_safety:
dependency: transitive
description:
@ -605,14 +613,6 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
frontend_server_client:
dependency: transitive
description:
name: frontend_server_client
sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.0.0"
get:
dependency: "direct main"
description:
@ -645,14 +645,6 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.3.2"
hotreloader:
dependency: transitive
description:
name: hotreloader
sha256: bc167a1163807b03bada490bfe2df25b0d744df359227880220a5cbd04e5734b
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.3.0"
html:
dependency: transitive
description:
@ -869,14 +861,6 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.0.2"
lean_builder:
dependency: transitive
description:
name: lean_builder
sha256: ef5cd5f907157eb7aa87d1704504b5a6386d2cbff88a3c2b3344477bab323ee9
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.1.2"
lints:
dependency: transitive
description:
@ -1160,10 +1144,10 @@ packages:
dependency: transitive
description:
name: protobuf
sha256: "2fcc8a202ca7ec17dab7c97d6b6d91cf03aa07fe6f65f8afbb6dfa52cc5bd902"
sha256: de9c9eb2c33f8e933a42932fe1dc504800ca45ebc3d673e6ed7f39754ee4053e
url: "https://pub.flutter-io.cn"
source: hosted
version: "5.1.0"
version: "4.2.0"
provider:
dependency: transitive
description:
@ -1280,10 +1264,10 @@ packages:
dependency: "direct dev"
description:
name: retrofit_generator
sha256: "56df50afab95199dada9e1afbfe5ec228612d03859b250e5e4846c3ebfe50bf7"
sha256: "5827551e2496f2c9586e4f23eedd6ce0b519286d2f405f91bf70f342a96b48ce"
url: "https://pub.flutter-io.cn"
source: hosted
version: "10.1.4"
version: "10.0.6"
rxdart:
dependency: transitive
description:
@ -1721,14 +1705,6 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "6.6.1"
xxh3:
dependency: transitive
description:
name: xxh3
sha256: "399a0438f5d426785723c99da6b16e136f4953fb1e9db0bf270bd41dd4619916"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.2.0"
yaml:
dependency: transitive
description:

1
pubspec.yaml

@ -74,6 +74,7 @@ dependencies:
location_plugin:
path: location_plugin
image_picker_android: ^0.8.12+23
flutter_svga: ^0.0.8
dev_dependencies:
flutter_test:

Loading…
Cancel
Save