banner参数 非全面屏适配 语言房进入逻辑修改
This commit is contained in:
parent
39464469ba
commit
83fb6465f9
@ -651,6 +651,8 @@
|
||||
"pleaseGetOnTheMicFirst": "يرجى الصعود إلى الميكروفون أولاً.",
|
||||
"welcomeMessage": "مرحبًا بك في تطبيقنا، {name}!",
|
||||
"operationFail": "فشلت العملية.",
|
||||
"enterRoomFailedRetry": "فشل دخول الغرفة. يرجى المحاولة مرة أخرى.",
|
||||
"voiceConnectionFailedRetry": "فشل الاتصال الصوتي. يرجى المحاولة مرة أخرى.",
|
||||
"doYouWantToKeepTheDraft": "هل تريد الاحتفاظ بالمسودة؟",
|
||||
"duration2": "المدة:{1}"
|
||||
}
|
||||
|
||||
@ -268,6 +268,8 @@
|
||||
"dice": "ডাইস",
|
||||
"rps": "পাথর-কাগজ-কাঁচি",
|
||||
"operationFail": "অপারেশন ব্যর্থ হয়েছে।",
|
||||
"enterRoomFailedRetry": "রুমে প্রবেশ ব্যর্থ হয়েছে। অনুগ্রহ করে আবার চেষ্টা করুন।",
|
||||
"voiceConnectionFailedRetry": "ভয়েস সংযোগ ব্যর্থ হয়েছে। অনুগ্রহ করে আবার চেষ্টা করুন।",
|
||||
"likedYourComment": "আপনার মন্তব্য পছন্দ করেছেন।",
|
||||
"doYouWantToKeepTheDraft": "আপনি কি ড্রাফট রাখতে চান?",
|
||||
"operationsAreTooFrequent": "অপারেশন খুব ঘন ঘন",
|
||||
|
||||
@ -239,6 +239,8 @@
|
||||
"dice": "Dice",
|
||||
"rps": "RPS",
|
||||
"operationFail": "The operation failed.",
|
||||
"enterRoomFailedRetry": "Failed to enter the room. Please try again.",
|
||||
"voiceConnectionFailedRetry": "Voice connection failed. Please try again.",
|
||||
"likedYourComment": "Liked your comment.",
|
||||
"doYouWantToKeepTheDraft": "Do you want to keep the draft?",
|
||||
"operationsAreTooFrequent": "Operations are too frequent",
|
||||
|
||||
@ -237,8 +237,10 @@
|
||||
"win": "Kazanan",
|
||||
"dice": "Zar",
|
||||
"rps": "Taş-Kağıt-Makas",
|
||||
"operationFail": "İşlem başarısız oldu.",
|
||||
"likedYourComment": "Yorumunuzu beğendi.",
|
||||
"operationFail": "İşlem başarısız oldu.",
|
||||
"enterRoomFailedRetry": "Odaya girilemedi. Lütfen tekrar deneyin.",
|
||||
"voiceConnectionFailedRetry": "Ses bağlantısı başarısız oldu. Lütfen tekrar deneyin.",
|
||||
"likedYourComment": "Yorumunuzu beğendi.",
|
||||
"doYouWantToKeepTheDraft": "Taslağı saklamak ister misiniz?",
|
||||
"operationsAreTooFrequent": "İşlemler çok sık",
|
||||
"luckNumber": "Şans Numarası",
|
||||
|
||||
@ -239,6 +239,20 @@ class SCAppLocalizations {
|
||||
|
||||
String get operationFail => translate('operationFail');
|
||||
|
||||
String get enterRoomFailedRetry {
|
||||
final value = translate('enterRoomFailedRetry');
|
||||
return value == 'enterRoomFailedRetry'
|
||||
? 'Failed to enter the room. Please try again.'
|
||||
: value;
|
||||
}
|
||||
|
||||
String get voiceConnectionFailedRetry {
|
||||
final value = translate('voiceConnectionFailedRetry');
|
||||
return value == 'voiceConnectionFailedRetry'
|
||||
? 'Voice connection failed. Please try again.'
|
||||
: value;
|
||||
}
|
||||
|
||||
String get forMoreRewardsPleaseCheckTheTaskCenter =>
|
||||
translate('forMoreRewardsPleaseCheckTheTaskCenter');
|
||||
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
import 'dart:math' as math;
|
||||
import 'dart:async';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:yumi/app_localizations.dart';
|
||||
import 'package:yumi/app/constants/sc_global_config.dart';
|
||||
import 'package:yumi/ui_kit/components/sc_compontent.dart';
|
||||
import 'package:yumi/services/audio/rtc_manager.dart';
|
||||
@ -60,6 +63,7 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
|
||||
RoomGiftSeatFlightController();
|
||||
final Map<String, _LuckyGiftComboSession> _luckyGiftComboSessions =
|
||||
<String, _LuckyGiftComboSession>{};
|
||||
int _shownRoomStartupFailureToken = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@ -154,6 +158,89 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
|
||||
SCGiftVapSvgaManager().stopPlayback();
|
||||
}
|
||||
|
||||
Widget _buildRoomStartupLayer() {
|
||||
return Selector<RtcProvider, _RoomStartupSnapshot>(
|
||||
selector:
|
||||
(context, provider) => _RoomStartupSnapshot(
|
||||
status: provider.roomStartupStatus,
|
||||
failureType: provider.roomStartupFailureType,
|
||||
failureToken: provider.roomStartupFailureToken,
|
||||
),
|
||||
builder: (context, snapshot, child) {
|
||||
if (snapshot.status == RoomStartupStatus.failed) {
|
||||
_showRoomStartupFailureDialog(snapshot);
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
if (snapshot.status != RoomStartupStatus.loading) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return Positioned.fill(
|
||||
child: AbsorbPointer(
|
||||
child: Container(
|
||||
color: Colors.black.withValues(alpha: 0.18),
|
||||
alignment: Alignment.center,
|
||||
child: Container(
|
||||
width: 55.w,
|
||||
height: 55.w,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black26,
|
||||
borderRadius: BorderRadius.circular(8.w),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: const CupertinoActivityIndicator(color: Colors.white24),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _showRoomStartupFailureDialog(_RoomStartupSnapshot snapshot) {
|
||||
if (_shownRoomStartupFailureToken == snapshot.failureToken) {
|
||||
return;
|
||||
}
|
||||
_shownRoomStartupFailureToken = snapshot.failureToken;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
SmartDialog.dismiss(tag: "roomStartupFailureDialog");
|
||||
SmartDialog.show(
|
||||
tag: "roomStartupFailureDialog",
|
||||
alignment: Alignment.center,
|
||||
animationType: SmartAnimationType.fade,
|
||||
backType: SmartBackType.block,
|
||||
clickMaskDismiss: false,
|
||||
builder: (_) {
|
||||
return _RoomStartupFailureDialog(
|
||||
title: SCAppLocalizations.of(context)!.tips,
|
||||
message: _roomStartupFailureMessage(snapshot.failureType),
|
||||
btnText: SCAppLocalizations.of(context)!.confirm,
|
||||
onEnsure: () {
|
||||
SmartDialog.dismiss(tag: "roomStartupFailureDialog");
|
||||
context.read<RtcProvider>().confirmRoomStartupFailureAndLeave(
|
||||
navigationContext: context,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
String _roomStartupFailureMessage(RoomStartupFailureType failureType) {
|
||||
switch (failureType) {
|
||||
case RoomStartupFailureType.im:
|
||||
return SCAppLocalizations.of(context)!.enterRoomFailedRetry;
|
||||
case RoomStartupFailureType.rtc:
|
||||
return SCAppLocalizations.of(context)!.voiceConnectionFailedRetry;
|
||||
case RoomStartupFailureType.entry:
|
||||
case RoomStartupFailureType.none:
|
||||
return SCAppLocalizations.of(context)!.operationFail;
|
||||
}
|
||||
}
|
||||
|
||||
String? _resolveRoomThemeBackground(JoinRoomRes? room) {
|
||||
final roomTheme = room?.roomProps?.roomTheme;
|
||||
final themeBack = roomTheme?.themeBack ?? "";
|
||||
@ -172,6 +259,11 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
|
||||
canPop: false,
|
||||
onPopInvokedWithResult: (bool didPop, Object? result) {
|
||||
if (!didPop) {
|
||||
final rtcProvider = context.read<RtcProvider>();
|
||||
if (rtcProvider.roomStartupStatus == RoomStartupStatus.loading ||
|
||||
rtcProvider.roomStartupStatus == RoomStartupStatus.failed) {
|
||||
return;
|
||||
}
|
||||
_suspendRoomVisualEffects();
|
||||
SCFloatIchart().show();
|
||||
SCNavigatorUtils.goBack(context);
|
||||
@ -231,6 +323,7 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
|
||||
),
|
||||
],
|
||||
),
|
||||
_buildRoomStartupLayer(),
|
||||
// _buildPlayViews(),
|
||||
///幸运礼物中奖动画
|
||||
LuckGiftNomorAnimWidget(),
|
||||
@ -727,3 +820,110 @@ class _LuckyGiftComboSession {
|
||||
clearQueueTimer?.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
class _RoomStartupSnapshot {
|
||||
const _RoomStartupSnapshot({
|
||||
required this.status,
|
||||
required this.failureType,
|
||||
required this.failureToken,
|
||||
});
|
||||
|
||||
final RoomStartupStatus status;
|
||||
final RoomStartupFailureType failureType;
|
||||
final int failureToken;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
return other is _RoomStartupSnapshot &&
|
||||
other.status == status &&
|
||||
other.failureType == failureType &&
|
||||
other.failureToken == failureToken;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(status, failureType, failureToken);
|
||||
}
|
||||
|
||||
class _RoomStartupFailureDialog extends StatelessWidget {
|
||||
const _RoomStartupFailureDialog({
|
||||
required this.title,
|
||||
required this.message,
|
||||
required this.btnText,
|
||||
required this.onEnsure,
|
||||
});
|
||||
|
||||
final String title;
|
||||
final String message;
|
||||
final String btnText;
|
||||
final VoidCallback onEnsure;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
type: MaterialType.transparency,
|
||||
child: Center(
|
||||
child: Container(
|
||||
constraints: BoxConstraints(maxWidth: 320.w),
|
||||
margin: EdgeInsets.symmetric(horizontal: 22.w),
|
||||
padding: EdgeInsets.fromLTRB(18.w, 18.w, 18.w, 30.w),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xff09372E),
|
||||
borderRadius: BorderRadius.circular(12.w),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 16.sp,
|
||||
fontWeight: FontWeight.w700,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 14.w),
|
||||
Text(
|
||||
message,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 14.sp,
|
||||
height: 1.25,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 18.w),
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: onEnsure,
|
||||
child: Container(
|
||||
width: 160.w,
|
||||
height: 38.w,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xff18F2B1),
|
||||
borderRadius: BorderRadius.circular(12.w),
|
||||
),
|
||||
child: Text(
|
||||
btnText,
|
||||
style: TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 14.sp,
|
||||
fontWeight: FontWeight.w700,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -388,8 +388,11 @@ class BaishunJsBridge {
|
||||
static String buildConfigScript(
|
||||
BaishunBridgeConfigModel config, {
|
||||
String? jsCallbackPath,
|
||||
Map<String, dynamic> viewportPayload = const <String, dynamic>{},
|
||||
}) {
|
||||
final payload = jsonEncode(config.toJson());
|
||||
final payload = jsonEncode(
|
||||
_mergeViewportPayload(config.toJson(), viewportPayload),
|
||||
);
|
||||
final encodedCallbackPath = jsonEncode(jsCallbackPath?.trim() ?? '');
|
||||
return '''
|
||||
(function() {
|
||||
@ -437,8 +440,15 @@ class BaishunJsBridge {
|
||||
}
|
||||
window.__baishunLastConfig = config;
|
||||
window.baishunBridgeConfig = config;
|
||||
const viewport = config.viewport || config.nativeViewport || {};
|
||||
window.__baishunViewport = viewport;
|
||||
window.baishunViewport = viewport;
|
||||
window.NativeBridge = window.NativeBridge || {};
|
||||
window.NativeBridge.config = config;
|
||||
window.NativeBridge.viewport = viewport;
|
||||
window.NativeBridge.getViewport = function() {
|
||||
return viewport;
|
||||
};
|
||||
const callbackPath = explicitCallbackPath || window.__baishunLastJsCallback || '';
|
||||
let callbackInvoked = false;
|
||||
if (explicitCallbackPath) {
|
||||
@ -455,6 +465,7 @@ class BaishunJsBridge {
|
||||
gsp: config.gsp,
|
||||
code: maskText(config.code),
|
||||
codeLength: config.code ? String(config.code).length : 0,
|
||||
viewport: config.viewport || config.nativeViewport || null,
|
||||
callbackPath: callbackPath
|
||||
});
|
||||
}
|
||||
@ -486,6 +497,30 @@ class BaishunJsBridge {
|
||||
''';
|
||||
}
|
||||
|
||||
static Map<String, dynamic> _mergeViewportPayload(
|
||||
Map<String, dynamic> payload,
|
||||
Map<String, dynamic> viewport,
|
||||
) {
|
||||
if (viewport.isEmpty) {
|
||||
return payload;
|
||||
}
|
||||
return <String, dynamic>{
|
||||
...payload,
|
||||
'viewport': viewport,
|
||||
'nativeViewport': viewport,
|
||||
'containerWidth': viewport['containerWidth'],
|
||||
'containerHeight': viewport['containerHeight'],
|
||||
'visibleHeight': viewport['visibleHeight'],
|
||||
'webViewHeight': viewport['webViewHeight'],
|
||||
'dialogHeight': viewport['dialogHeight'],
|
||||
'safeAreaTop': viewport['safeAreaTop'],
|
||||
'safeAreaBottom': viewport['safeAreaBottom'],
|
||||
'devicePixelRatio': viewport['devicePixelRatio'],
|
||||
'screenMode': viewport['screenMode'],
|
||||
'isCompactViewport': viewport['isCompactViewport'],
|
||||
};
|
||||
}
|
||||
|
||||
static String buildWalletUpdateScript({Map<String, dynamic>? payload}) {
|
||||
final safePayload = jsonEncode(
|
||||
payload ??
|
||||
|
||||
@ -172,6 +172,14 @@ class LeaderJsBridge {
|
||||
normalizedPayload.entry && typeof normalizedPayload.entry === 'object'
|
||||
? normalizedPayload.entry
|
||||
: {};
|
||||
const viewport =
|
||||
normalizedPayload.viewport && typeof normalizedPayload.viewport === 'object'
|
||||
? normalizedPayload.viewport
|
||||
: (
|
||||
launchConfig.viewport && typeof launchConfig.viewport === 'object'
|
||||
? launchConfig.viewport
|
||||
: {}
|
||||
);
|
||||
window.__leaderLaunchPayload = normalizedPayload;
|
||||
window.__leaderLaunchPayloadText = payloadText;
|
||||
window.leaderLaunchPayload = normalizedPayload;
|
||||
@ -179,14 +187,21 @@ class LeaderJsBridge {
|
||||
window.leaderLaunchConfig = launchConfig;
|
||||
window.__leaderLaunchEntry = entry;
|
||||
window.leaderLaunchEntry = entry;
|
||||
window.__leaderViewport = viewport;
|
||||
window.leaderViewport = viewport;
|
||||
window.NativeBridge = window.NativeBridge || {};
|
||||
window.NativeBridge.config = launchConfig;
|
||||
window.NativeBridge.launchConfig = launchConfig;
|
||||
window.NativeBridge.entry = entry;
|
||||
window.NativeBridge.viewport = viewport;
|
||||
window.NativeBridge.getConfig = function(callback) {
|
||||
invokeCallback(callback, launchConfig);
|
||||
return launchConfig;
|
||||
};
|
||||
window.NativeBridge.getViewport = function(callback) {
|
||||
invokeCallback(callback, viewport);
|
||||
return viewport;
|
||||
};
|
||||
window.NativeBridge.getLaunchPayload = function(callback) {
|
||||
invokeCallback(callback, normalizedPayload);
|
||||
return normalizedPayload;
|
||||
@ -207,7 +222,8 @@ class LeaderJsBridge {
|
||||
? String(launchConfig.code).slice(0, 6) + '***'
|
||||
: '',
|
||||
codeLength: launchConfig.code ? String(launchConfig.code).length : 0,
|
||||
entryUrl: entry.entryUrl || ''
|
||||
entryUrl: entry.entryUrl || '',
|
||||
viewport: viewport
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
145
lib/modules/room_game/utils/room_game_viewport.dart
Normal file
145
lib/modules/room_game/utils/room_game_viewport.dart
Normal file
@ -0,0 +1,145 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
const double roomGameDesignWidth = 750;
|
||||
|
||||
double resolveRoomGameAvailableHeight(
|
||||
BuildContext context, {
|
||||
double reservedTop = 12,
|
||||
}) {
|
||||
final mediaQuery = MediaQuery.maybeOf(context);
|
||||
if (mediaQuery == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
final bottomInset =
|
||||
mediaQuery.viewInsets.bottom > mediaQuery.padding.bottom
|
||||
? mediaQuery.viewInsets.bottom
|
||||
: mediaQuery.padding.bottom;
|
||||
final availableHeight =
|
||||
mediaQuery.size.height -
|
||||
mediaQuery.padding.top -
|
||||
bottomInset -
|
||||
reservedTop;
|
||||
if (!availableHeight.isFinite || availableHeight <= 0) {
|
||||
return mediaQuery.size.height;
|
||||
}
|
||||
return availableHeight.clamp(0.0, mediaQuery.size.height).toDouble();
|
||||
}
|
||||
|
||||
double clampRoomGameHeight(
|
||||
double value,
|
||||
double maxHeight, {
|
||||
double minHeight = 0,
|
||||
}) {
|
||||
if (!value.isFinite || value <= 0) {
|
||||
return maxHeight > 0 ? maxHeight : 0;
|
||||
}
|
||||
if (!maxHeight.isFinite || maxHeight <= 0) {
|
||||
return value;
|
||||
}
|
||||
if (minHeight > maxHeight) {
|
||||
return maxHeight;
|
||||
}
|
||||
return value.clamp(minHeight, maxHeight).toDouble();
|
||||
}
|
||||
|
||||
String resolveRoomGameScreenMode(
|
||||
Map<String, dynamic> launchParams, {
|
||||
required bool fullScreen,
|
||||
}) {
|
||||
final candidates = <String>[
|
||||
launchParams['screenMode']?.toString() ?? '',
|
||||
launchParams['screen_mode']?.toString() ?? '',
|
||||
launchParams['displayMode']?.toString() ?? '',
|
||||
launchParams['gameScreenMode']?.toString() ?? '',
|
||||
];
|
||||
for (final candidate in candidates) {
|
||||
final trimmed = candidate.trim();
|
||||
if (trimmed.isNotEmpty) {
|
||||
return trimmed.toLowerCase();
|
||||
}
|
||||
}
|
||||
return fullScreen ? 'full' : 'half';
|
||||
}
|
||||
|
||||
Map<String, dynamic> buildRoomGameViewportPayload(
|
||||
BuildContext context, {
|
||||
required double containerHeight,
|
||||
double? dialogHeight,
|
||||
double headerHeight = 0,
|
||||
double topCrop = 0,
|
||||
required String screenMode,
|
||||
required int safeHeight,
|
||||
double designWidth = roomGameDesignWidth,
|
||||
double? designHeight,
|
||||
double? actualWebViewHeight,
|
||||
}) {
|
||||
final mediaQuery = MediaQuery.maybeOf(context);
|
||||
final size = mediaQuery?.size ?? Size.zero;
|
||||
final padding = mediaQuery?.padding ?? EdgeInsets.zero;
|
||||
final viewInsets = mediaQuery?.viewInsets ?? EdgeInsets.zero;
|
||||
final targetDesignHeight =
|
||||
designHeight ?? (safeHeight > 0 ? safeHeight.toDouble() : null);
|
||||
final expectedHeight =
|
||||
targetDesignHeight != null && size.width > 0 && designWidth > 0
|
||||
? size.width * targetDesignHeight / designWidth
|
||||
: null;
|
||||
final availableHeight = resolveRoomGameAvailableHeight(context);
|
||||
|
||||
return <String, dynamic>{
|
||||
'containerWidth': _roundMetric(size.width),
|
||||
'containerHeight': _roundMetric(containerHeight),
|
||||
'visibleHeight': _roundMetric(containerHeight),
|
||||
'webViewHeight': _roundMetric(actualWebViewHeight ?? containerHeight),
|
||||
'dialogHeight': _roundMetric(
|
||||
dialogHeight ?? containerHeight + headerHeight,
|
||||
),
|
||||
'screenHeight': _roundMetric(size.height),
|
||||
'headerHeight': _roundMetric(headerHeight),
|
||||
'topCrop': _roundMetric(topCrop),
|
||||
'safeAreaTop': _roundMetric(padding.top),
|
||||
'safeAreaBottom': _roundMetric(padding.bottom),
|
||||
'viewInsetBottom': _roundMetric(viewInsets.bottom),
|
||||
'availableHeight': _roundMetric(availableHeight),
|
||||
'devicePixelRatio': _roundMetric(mediaQuery?.devicePixelRatio ?? 1),
|
||||
'screenMode': screenMode,
|
||||
'isCompactViewport':
|
||||
expectedHeight != null ? containerHeight + 1 < expectedHeight : false,
|
||||
'designWidth': _roundMetric(designWidth),
|
||||
if (targetDesignHeight != null)
|
||||
'designHeight': _roundMetric(targetDesignHeight),
|
||||
'safeHeight': safeHeight,
|
||||
};
|
||||
}
|
||||
|
||||
Map<String, dynamic> mergeRoomGameViewportPayload(
|
||||
Map<String, dynamic> payload,
|
||||
Map<String, dynamic> viewport,
|
||||
) {
|
||||
if (viewport.isEmpty) {
|
||||
return payload;
|
||||
}
|
||||
|
||||
return <String, dynamic>{
|
||||
...payload,
|
||||
'viewport': viewport,
|
||||
'nativeViewport': viewport,
|
||||
'containerWidth': viewport['containerWidth'],
|
||||
'containerHeight': viewport['containerHeight'],
|
||||
'visibleHeight': viewport['visibleHeight'],
|
||||
'webViewHeight': viewport['webViewHeight'],
|
||||
'dialogHeight': viewport['dialogHeight'],
|
||||
'safeAreaTop': viewport['safeAreaTop'],
|
||||
'safeAreaBottom': viewport['safeAreaBottom'],
|
||||
'devicePixelRatio': viewport['devicePixelRatio'],
|
||||
'screenMode': viewport['screenMode'],
|
||||
'isCompactViewport': viewport['isCompactViewport'],
|
||||
};
|
||||
}
|
||||
|
||||
double _roundMetric(double value) {
|
||||
if (!value.isFinite) {
|
||||
return 0;
|
||||
}
|
||||
return double.parse(value.toStringAsFixed(2));
|
||||
}
|
||||
@ -11,6 +11,7 @@ import 'package:yumi/app/routes/sc_fluro_navigator.dart';
|
||||
import 'package:yumi/modules/room_game/bridge/baishun_js_bridge.dart';
|
||||
import 'package:yumi/modules/room_game/data/models/room_game_models.dart';
|
||||
import 'package:yumi/modules/room_game/data/room_game_repository.dart';
|
||||
import 'package:yumi/modules/room_game/utils/room_game_viewport.dart';
|
||||
import 'package:yumi/modules/room_game/views/baishun_loading_view.dart';
|
||||
import 'package:yumi/modules/wallet/wallet_route.dart';
|
||||
import 'package:yumi/ui_kit/components/sc_tts.dart';
|
||||
@ -33,7 +34,6 @@ class BaishunGamePage extends StatefulWidget {
|
||||
|
||||
class _BaishunGamePageState extends State<BaishunGamePage> {
|
||||
static const String _logPrefix = '[BaishunGame]';
|
||||
static const double _designWidth = 750;
|
||||
|
||||
final RoomGameRepository _repository = RoomGameRepository();
|
||||
final Set<Factory<OneSequenceGestureRecognizer>> _webGestureRecognizers =
|
||||
@ -260,15 +260,17 @@ class _BaishunGamePageState extends State<BaishunGamePage> {
|
||||
_hasDeliveredLaunchConfig = true;
|
||||
final rawConfig = widget.launchModel.bridgeConfig;
|
||||
final config = _buildEffectiveBridgeConfig(rawConfig);
|
||||
final viewport = _buildViewportPayload();
|
||||
_log(
|
||||
'send_config jsCallback=${jsCallbackPath ?? ''} '
|
||||
'force=$force config=${_stringifyForLog(_buildConfigSummary(config, rawConfig: rawConfig))}',
|
||||
'force=$force config=${_stringifyForLog(<String, dynamic>{..._buildConfigSummary(config, rawConfig: rawConfig), 'viewport': viewport})}',
|
||||
);
|
||||
try {
|
||||
await _controller.runJavaScript(
|
||||
BaishunJsBridge.buildConfigScript(
|
||||
config,
|
||||
jsCallbackPath: jsCallbackPath,
|
||||
viewportPayload: viewport,
|
||||
),
|
||||
);
|
||||
} catch (error) {
|
||||
@ -609,22 +611,45 @@ class _BaishunGamePageState extends State<BaishunGamePage> {
|
||||
return 0;
|
||||
}
|
||||
|
||||
String _resolveScreenMode() {
|
||||
return resolveRoomGameScreenMode(
|
||||
widget.game.launchParams,
|
||||
fullScreen: widget.game.fullScreen,
|
||||
);
|
||||
}
|
||||
|
||||
double _calculateWebViewHeight(BuildContext context) {
|
||||
final screenSize = MediaQuery.sizeOf(context);
|
||||
final safePadding = MediaQuery.paddingOf(context);
|
||||
final mediaQuery = MediaQuery.maybeOf(context);
|
||||
final screenSize = mediaQuery?.size ?? Size.zero;
|
||||
final safeHeight = _resolveSafeHeight();
|
||||
final fallbackHeight = screenSize.height * 0.5;
|
||||
final maxHeight = resolveRoomGameAvailableHeight(
|
||||
context,
|
||||
reservedTop: 12.w,
|
||||
);
|
||||
if (safeHeight <= 0 || screenSize.width <= 0) {
|
||||
return fallbackHeight;
|
||||
return clampRoomGameHeight(fallbackHeight, maxHeight);
|
||||
}
|
||||
|
||||
final ratio = _designWidth / safeHeight;
|
||||
final ratio = roomGameDesignWidth / safeHeight;
|
||||
final webViewHeight = screenSize.width / ratio;
|
||||
final maxHeight = screenSize.height - safePadding.top - 12.w;
|
||||
if (maxHeight <= 0) {
|
||||
return fallbackHeight;
|
||||
}
|
||||
return webViewHeight.clamp(0.0, maxHeight).toDouble();
|
||||
return clampRoomGameHeight(webViewHeight, maxHeight);
|
||||
}
|
||||
|
||||
Map<String, dynamic> _buildViewportPayload() {
|
||||
final topCrop = 28.w;
|
||||
final webViewHeight = _calculateWebViewHeight(context);
|
||||
final safeHeight = _resolveSafeHeight();
|
||||
return buildRoomGameViewportPayload(
|
||||
context,
|
||||
containerHeight: webViewHeight,
|
||||
dialogHeight: webViewHeight,
|
||||
topCrop: topCrop,
|
||||
screenMode: _resolveScreenMode(),
|
||||
safeHeight: safeHeight,
|
||||
designHeight: safeHeight > 0 ? safeHeight.toDouble() : null,
|
||||
actualWebViewHeight: webViewHeight + topCrop,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@ -10,6 +10,7 @@ import 'package:yumi/app/routes/sc_fluro_navigator.dart';
|
||||
import 'package:yumi/modules/room_game/bridge/leader_js_bridge.dart';
|
||||
import 'package:yumi/modules/room_game/data/models/room_game_models.dart';
|
||||
import 'package:yumi/modules/room_game/data/room_game_repository.dart';
|
||||
import 'package:yumi/modules/room_game/utils/room_game_viewport.dart';
|
||||
import 'package:yumi/modules/room_game/views/baishun_loading_view.dart';
|
||||
import 'package:yumi/modules/wallet/wallet_route.dart';
|
||||
import 'package:yumi/ui_kit/components/sc_tts.dart';
|
||||
@ -32,7 +33,6 @@ class LeaderGamePage extends StatefulWidget {
|
||||
|
||||
class _LeaderGamePageState extends State<LeaderGamePage> {
|
||||
static const String _logPrefix = '[LeaderGame]';
|
||||
static const double _designWidth = 750;
|
||||
static const double _hdDesignHeight = 1044;
|
||||
|
||||
final RoomGameRepository _repository = RoomGameRepository();
|
||||
@ -215,7 +215,9 @@ class _LeaderGamePageState extends State<LeaderGamePage> {
|
||||
}
|
||||
try {
|
||||
await _controller.runJavaScript(
|
||||
LeaderJsBridge.bootstrapScript(launchPayload: _buildLaunchPayload()),
|
||||
LeaderJsBridge.bootstrapScript(
|
||||
launchPayload: _buildLaunchPayload(viewport: _buildViewportPayload()),
|
||||
),
|
||||
);
|
||||
} catch (error) {
|
||||
_log(
|
||||
@ -328,23 +330,10 @@ class _LeaderGamePageState extends State<LeaderGamePage> {
|
||||
}
|
||||
|
||||
String _resolveScreenMode() {
|
||||
final launchParams = widget.game.launchParams;
|
||||
final candidates = <String>[
|
||||
launchParams['screenMode']?.toString() ?? '',
|
||||
launchParams['screen_mode']?.toString() ?? '',
|
||||
launchParams['displayMode']?.toString() ?? '',
|
||||
launchParams['gameScreenMode']?.toString() ?? '',
|
||||
];
|
||||
for (final candidate in candidates) {
|
||||
final trimmed = candidate.trim();
|
||||
if (trimmed.isNotEmpty) {
|
||||
return trimmed.toLowerCase();
|
||||
}
|
||||
}
|
||||
if (widget.game.fullScreen) {
|
||||
return 'full';
|
||||
}
|
||||
return 'half';
|
||||
return resolveRoomGameScreenMode(
|
||||
widget.game.launchParams,
|
||||
fullScreen: widget.game.fullScreen,
|
||||
);
|
||||
}
|
||||
|
||||
int _resolveSafeHeight() {
|
||||
@ -357,33 +346,38 @@ class _LeaderGamePageState extends State<LeaderGamePage> {
|
||||
return 0;
|
||||
}
|
||||
|
||||
double _calculateBodyHeight(BuildContext context) {
|
||||
final screenSize = MediaQuery.sizeOf(context);
|
||||
final safePadding = MediaQuery.paddingOf(context);
|
||||
double _calculateBodyHeight(BuildContext context, double headerHeight) {
|
||||
final mediaQuery = MediaQuery.maybeOf(context);
|
||||
final screenSize = mediaQuery?.size ?? Size.zero;
|
||||
final screenMode = _resolveScreenMode();
|
||||
final safeHeight = _resolveSafeHeight();
|
||||
final fallback = screenSize.width;
|
||||
final maxHeight = screenSize.height - safePadding.top - 24.w;
|
||||
if (maxHeight <= 0) {
|
||||
return fallback;
|
||||
final maxDialogHeight = resolveRoomGameAvailableHeight(
|
||||
context,
|
||||
reservedTop: 24.w,
|
||||
);
|
||||
final maxBodyHeight = maxDialogHeight - headerHeight;
|
||||
if (maxBodyHeight <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (screenMode == 'full') {
|
||||
return maxHeight;
|
||||
return maxBodyHeight;
|
||||
}
|
||||
|
||||
if (safeHeight > 0 && screenSize.width > 0) {
|
||||
final ratio = _designWidth / safeHeight;
|
||||
final ratio = roomGameDesignWidth / safeHeight;
|
||||
final bodyHeight = screenSize.width / ratio;
|
||||
return bodyHeight.clamp(0.0, maxHeight).toDouble();
|
||||
return clampRoomGameHeight(bodyHeight, maxBodyHeight);
|
||||
}
|
||||
|
||||
if (screenMode == 'hd') {
|
||||
final bodyHeight = screenSize.width * _hdDesignHeight / _designWidth;
|
||||
return bodyHeight.clamp(0.0, maxHeight).toDouble();
|
||||
final bodyHeight =
|
||||
screenSize.width * _hdDesignHeight / roomGameDesignWidth;
|
||||
return clampRoomGameHeight(bodyHeight, maxBodyHeight);
|
||||
}
|
||||
|
||||
return fallback.clamp(0.0, maxHeight).toDouble();
|
||||
return clampRoomGameHeight(fallback, maxBodyHeight);
|
||||
}
|
||||
|
||||
void _log(String message) {
|
||||
@ -446,7 +440,51 @@ class _LeaderGamePageState extends State<LeaderGamePage> {
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> _buildLaunchPayload() {
|
||||
Map<String, dynamic> _buildLaunchPayload({
|
||||
Map<String, dynamic> viewport = const <String, dynamic>{},
|
||||
}) {
|
||||
final launchConfig = mergeRoomGameViewportPayload(
|
||||
widget.launchModel.launchConfig.toJson(),
|
||||
viewport,
|
||||
);
|
||||
final payload = <String, dynamic>{
|
||||
'provider': widget.launchModel.provider,
|
||||
'gameId': widget.launchModel.gameId,
|
||||
'providerGameId': widget.launchModel.providerGameId,
|
||||
'gameSessionId': widget.launchModel.gameSessionId,
|
||||
'entry': widget.launchModel.entry.toJson(),
|
||||
'launchConfig': launchConfig,
|
||||
'roomState': widget.launchModel.roomState.toJson(),
|
||||
'game': <String, dynamic>{
|
||||
'gameId': widget.game.gameId,
|
||||
'provider': widget.game.provider,
|
||||
'gameType': widget.game.gameType,
|
||||
'name': widget.game.name,
|
||||
'launchMode': widget.game.launchMode,
|
||||
},
|
||||
};
|
||||
return mergeRoomGameViewportPayload(payload, viewport);
|
||||
}
|
||||
|
||||
Map<String, dynamic> _buildViewportPayload() {
|
||||
final headerHeight = 44.w;
|
||||
final bodyHeight = _calculateBodyHeight(context, headerHeight);
|
||||
final safeHeight = _resolveSafeHeight();
|
||||
return buildRoomGameViewportPayload(
|
||||
context,
|
||||
containerHeight: bodyHeight,
|
||||
dialogHeight: bodyHeight + headerHeight,
|
||||
headerHeight: headerHeight,
|
||||
screenMode: _resolveScreenMode(),
|
||||
safeHeight: safeHeight,
|
||||
designHeight:
|
||||
safeHeight > 0
|
||||
? safeHeight.toDouble()
|
||||
: (_resolveScreenMode() == 'hd' ? _hdDesignHeight : null),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> _buildLaunchPayloadForLog() {
|
||||
return <String, dynamic>{
|
||||
'provider': widget.launchModel.provider,
|
||||
'gameId': widget.launchModel.gameId,
|
||||
@ -466,7 +504,7 @@ class _LeaderGamePageState extends State<LeaderGamePage> {
|
||||
}
|
||||
|
||||
Map<String, dynamic> _buildLaunchSummary() {
|
||||
final payload = _buildLaunchPayload();
|
||||
final payload = _buildLaunchPayloadForLog();
|
||||
return <String, dynamic>{
|
||||
'roomId': widget.roomId,
|
||||
'provider': widget.game.provider,
|
||||
@ -525,8 +563,8 @@ class _LeaderGamePageState extends State<LeaderGamePage> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final bodyHeight = _calculateBodyHeight(context);
|
||||
final headerHeight = 44.w;
|
||||
final bodyHeight = _calculateBodyHeight(context, headerHeight);
|
||||
return PopScope(
|
||||
canPop: false,
|
||||
onPopInvokedWithResult: (bool didPop, Object? result) async {
|
||||
|
||||
@ -4,6 +4,7 @@ import 'package:provider/provider.dart';
|
||||
import 'dart:convert';
|
||||
import 'package:yumi/modules/room_game/data/models/room_game_models.dart';
|
||||
import 'package:yumi/modules/room_game/data/room_game_repository.dart';
|
||||
import 'package:yumi/modules/room_game/utils/room_game_viewport.dart';
|
||||
import 'package:yumi/modules/room_game/views/baishun_game_page.dart';
|
||||
import 'package:yumi/modules/room_game/views/leader_game_page.dart';
|
||||
import 'package:yumi/services/audio/rtc_manager.dart';
|
||||
@ -210,11 +211,17 @@ class _RoomGameListSheetState extends State<RoomGameListSheet> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final mediaQuery = MediaQuery.maybeOf(context);
|
||||
final screenHeight = mediaQuery?.size.height ?? ScreenUtil().screenHeight;
|
||||
final sheetHeight = clampRoomGameHeight(
|
||||
screenHeight * 0.5,
|
||||
resolveRoomGameAvailableHeight(context),
|
||||
);
|
||||
return SafeArea(
|
||||
top: false,
|
||||
child: SizedBox(
|
||||
width: ScreenUtil().screenWidth,
|
||||
height: ScreenUtil().screenHeight * 0.5,
|
||||
height: sheetHeight,
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final innerLeft = constraints.maxWidth * 0.05;
|
||||
|
||||
@ -46,6 +46,10 @@ import '../../ui_kit/components/sc_float_ichart.dart';
|
||||
typedef OnSoundVoiceChange = Function(num index, int volum);
|
||||
typedef RtcProvider = RealTimeCommunicationManager;
|
||||
|
||||
enum RoomStartupStatus { idle, loading, ready, failed }
|
||||
|
||||
enum RoomStartupFailureType { none, entry, im, rtc }
|
||||
|
||||
class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
static const Duration _micListPollingInterval = Duration(seconds: 2);
|
||||
static const Duration _onlineUsersPollingInterval = Duration(seconds: 3);
|
||||
@ -57,6 +61,10 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
bool needUpDataUserInfo = false;
|
||||
bool _roomVisualEffectsEnabled = false;
|
||||
bool _isExitingCurrentVoiceRoomSession = false;
|
||||
RoomStartupStatus _roomStartupStatus = RoomStartupStatus.idle;
|
||||
RoomStartupFailureType _roomStartupFailureType = RoomStartupFailureType.none;
|
||||
int _roomStartupFailureToken = 0;
|
||||
bool _isHandlingRoomStartupFailure = false;
|
||||
Timer? _micListPollingTimer;
|
||||
Timer? _onlineUsersPollingTimer;
|
||||
bool _isRefreshingMicList = false;
|
||||
@ -129,6 +137,12 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
bool get isExitingCurrentVoiceRoomSession =>
|
||||
_isExitingCurrentVoiceRoomSession;
|
||||
|
||||
RoomStartupStatus get roomStartupStatus => _roomStartupStatus;
|
||||
|
||||
RoomStartupFailureType get roomStartupFailureType => _roomStartupFailureType;
|
||||
|
||||
int get roomStartupFailureToken => _roomStartupFailureToken;
|
||||
|
||||
bool get shouldShowRoomVisualEffects =>
|
||||
currenRoom != null && _roomVisualEffectsEnabled;
|
||||
|
||||
@ -140,6 +154,35 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void _setRoomStartupLoading() {
|
||||
_isHandlingRoomStartupFailure = false;
|
||||
_roomStartupFailureType = RoomStartupFailureType.none;
|
||||
if (_roomStartupStatus == RoomStartupStatus.loading) {
|
||||
return;
|
||||
}
|
||||
_roomStartupStatus = RoomStartupStatus.loading;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void _setRoomStartupReady() {
|
||||
_isHandlingRoomStartupFailure = false;
|
||||
_roomStartupFailureType = RoomStartupFailureType.none;
|
||||
if (_roomStartupStatus == RoomStartupStatus.ready) {
|
||||
return;
|
||||
}
|
||||
_roomStartupStatus = RoomStartupStatus.ready;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void _setRoomStartupFailed(RoomStartupFailureType failureType) {
|
||||
_stopRoomStatePolling();
|
||||
_isHandlingRoomStartupFailure = false;
|
||||
_roomStartupFailureType = failureType;
|
||||
_roomStartupFailureToken += 1;
|
||||
_roomStartupStatus = RoomStartupStatus.failed;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void _setExitingCurrentVoiceRoomSession(bool enabled, {bool notify = true}) {
|
||||
if (_isExitingCurrentVoiceRoomSession == enabled) {
|
||||
return;
|
||||
@ -775,7 +818,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> joinAgoraVoiceChannel() async {
|
||||
Future<void> joinAgoraVoiceChannel({bool throwOnError = false}) async {
|
||||
try {
|
||||
final rtcEngine = await _initAgoraRtcEngine();
|
||||
rtcEngine.setAudioProfile(
|
||||
@ -814,6 +857,9 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
_syncSelfMicRuntimeState();
|
||||
rtcEngine.muteAllRemoteAudioStreams(roomIsMute);
|
||||
} catch (e) {
|
||||
if (throwOnError) {
|
||||
rethrow;
|
||||
}
|
||||
SCTts.show("Join room fail");
|
||||
exitCurrentVoiceRoomSession(false);
|
||||
print('加入失败:${e.runtimeType},${e.toString()}');
|
||||
@ -1046,6 +1092,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
retrieveMicrophoneList();
|
||||
fetchOnlineUsersList();
|
||||
_startRoomStatePolling();
|
||||
_setRoomStartupReady();
|
||||
setRoomVisualEffectsEnabled(true);
|
||||
SCFloatIchart().remove();
|
||||
SCNavigatorUtils.push(
|
||||
@ -1099,7 +1146,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
}
|
||||
|
||||
///初始化房间相关
|
||||
Future initializeRoomSession({
|
||||
Future<void> initializeRoomSession({
|
||||
bool needOpenRedenvelope = false,
|
||||
String? redPackId,
|
||||
}) async {
|
||||
@ -1112,7 +1159,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
currenRoom?.entrants?.roles == SCRoomRolesType.HOMEOWNER.name) {
|
||||
///需要去修改房间信息,创建群聊
|
||||
VoiceRoomRoute.openRoomEdit(context!, needRestCurrentRoomInfo: true);
|
||||
return Future;
|
||||
return;
|
||||
}
|
||||
SCRoomUtils.roomSCGlobalConfig(
|
||||
currenRoom?.roomProfile?.roomProfile?.id ?? "",
|
||||
@ -1126,46 +1173,57 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
});
|
||||
}
|
||||
_setRoomStartupLoading();
|
||||
setRoomVisualEffectsEnabled(true);
|
||||
SCNavigatorUtils.push(context!, VoiceRoomRoute.voiceRoom, replace: false);
|
||||
var joinResult = await rtmProvider?.joinRoomGroup(
|
||||
currenRoom?.roomProfile?.roomProfile?.roomAccount ?? "",
|
||||
"",
|
||||
);
|
||||
SCChatRoomRepository()
|
||||
.rocketClaim(currenRoom?.roomProfile?.roomProfile?.id ?? "")
|
||||
.catchError((e) {
|
||||
return true; // 错误已处理
|
||||
});
|
||||
rtmProvider?.addMsg(
|
||||
Msg(
|
||||
groupId: currenRoom?.roomProfile?.roomProfile?.roomAccount ?? "",
|
||||
msg: SCAppLocalizations.of(context!)!.systemRoomTips,
|
||||
type: SCRoomMsgType.systemTips,
|
||||
),
|
||||
);
|
||||
rtmProvider?.addMsg(
|
||||
Msg(
|
||||
groupId: currenRoom?.roomProfile?.roomProfile?.roomAccount ?? "",
|
||||
msg: currenRoom?.roomProfile?.roomProfile?.roomDesc,
|
||||
type: SCRoomMsgType.systemTips,
|
||||
),
|
||||
);
|
||||
unawaited(_bootstrapEnteredVoiceRoomSession());
|
||||
}
|
||||
|
||||
await _bootstrapRoomHeartbeatState();
|
||||
Future<void> _bootstrapEnteredVoiceRoomSession() async {
|
||||
var failureType = RoomStartupFailureType.im;
|
||||
try {
|
||||
final groupId = currenRoom?.roomProfile?.roomProfile?.roomAccount ?? "";
|
||||
final joinResult = await rtmProvider?.joinRoomGroup(groupId, "");
|
||||
if (joinResult == null || joinResult.code != 0) {
|
||||
debugPrint(
|
||||
'[RoomStartup] join IM group failed code=${joinResult?.code} desc=${joinResult?.desc}',
|
||||
);
|
||||
_setRoomStartupFailed(RoomStartupFailureType.im);
|
||||
return;
|
||||
}
|
||||
|
||||
rtmProvider?.addMsg(
|
||||
Msg(
|
||||
groupId: currenRoom?.roomProfile?.roomProfile?.roomAccount ?? "",
|
||||
msg: SCAppLocalizations.of(context!)!.systemRoomTips,
|
||||
type: SCRoomMsgType.systemTips,
|
||||
),
|
||||
);
|
||||
rtmProvider?.addMsg(
|
||||
Msg(
|
||||
groupId: currenRoom?.roomProfile?.roomProfile?.roomAccount ?? "",
|
||||
msg: currenRoom?.roomProfile?.roomProfile?.roomDesc,
|
||||
type: SCRoomMsgType.systemTips,
|
||||
),
|
||||
);
|
||||
|
||||
await _bootstrapRoomHeartbeatState();
|
||||
|
||||
///获取麦位
|
||||
retrieveMicrophoneList();
|
||||
fetchOnlineUsersList();
|
||||
_startRoomStatePolling();
|
||||
Provider.of<SocialChatRoomManager>(
|
||||
context!,
|
||||
listen: false,
|
||||
).fetchContributionLevelData(
|
||||
currenRoom?.roomProfile?.roomProfile?.id ?? "",
|
||||
);
|
||||
|
||||
failureType = RoomStartupFailureType.rtc;
|
||||
await joinAgoraVoiceChannel(throwOnError: true);
|
||||
_setRoomStartupReady();
|
||||
|
||||
///获取麦位
|
||||
retrieveMicrophoneList();
|
||||
fetchOnlineUsersList();
|
||||
_startRoomStatePolling();
|
||||
Provider.of<SocialChatRoomManager>(
|
||||
context!,
|
||||
listen: false,
|
||||
).fetchContributionLevelData(
|
||||
currenRoom?.roomProfile?.roomProfile?.id ?? "",
|
||||
);
|
||||
await joinAgoraVoiceChannel();
|
||||
if (joinResult?.code == 0) {
|
||||
Msg joinMsg = Msg(
|
||||
groupId: currenRoom?.roomProfile?.roomProfile?.roomAccount ?? "",
|
||||
user: AccountStorage().getCurrentUser()?.userProfile,
|
||||
@ -1174,28 +1232,44 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
type: SCRoomMsgType.joinRoom,
|
||||
);
|
||||
rtmProvider?.dispatchMessage(joinMsg, addLocal: true);
|
||||
if (SCGlobalConfig.isEntryVehicleAnimation) {
|
||||
if (AccountStorage().getCurrentUser()?.userProfile?.getMountains() !=
|
||||
null &&
|
||||
shouldShowRoomVisualEffects) {
|
||||
Future.delayed(Duration(milliseconds: 550), () {
|
||||
SCGiftVapSvgaManager().play(
|
||||
AccountStorage()
|
||||
.getCurrentUser()
|
||||
?.userProfile
|
||||
?.getMountains()
|
||||
?.sourceUrl ??
|
||||
"",
|
||||
priority: 100,
|
||||
type: 1,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
if (rtmProvider?.msgUserJoinListener != null) {
|
||||
rtmProvider?.msgUserJoinListener!(joinMsg);
|
||||
_playRoomEntryEffects(joinMsg);
|
||||
_loadRoomSecondaryData();
|
||||
} catch (error, stackTrace) {
|
||||
debugPrint('[RoomStartup] bootstrap failed: $error\n$stackTrace');
|
||||
_setRoomStartupFailed(failureType);
|
||||
}
|
||||
}
|
||||
|
||||
void _playRoomEntryEffects(Msg joinMsg) {
|
||||
if (SCGlobalConfig.isEntryVehicleAnimation) {
|
||||
if (AccountStorage().getCurrentUser()?.userProfile?.getMountains() !=
|
||||
null &&
|
||||
shouldShowRoomVisualEffects) {
|
||||
Future.delayed(Duration(milliseconds: 550), () {
|
||||
SCGiftVapSvgaManager().play(
|
||||
AccountStorage()
|
||||
.getCurrentUser()
|
||||
?.userProfile
|
||||
?.getMountains()
|
||||
?.sourceUrl ??
|
||||
"",
|
||||
priority: 100,
|
||||
type: 1,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
if (rtmProvider?.msgUserJoinListener != null) {
|
||||
rtmProvider?.msgUserJoinListener!(joinMsg);
|
||||
}
|
||||
}
|
||||
|
||||
void _loadRoomSecondaryData() {
|
||||
SCChatRoomRepository()
|
||||
.rocketClaim(currenRoom?.roomProfile?.roomProfile?.id ?? "")
|
||||
.catchError((e) {
|
||||
return true; // 错误已处理
|
||||
});
|
||||
SCChatRoomRepository()
|
||||
.rocketStatus(currenRoom?.roomProfile?.roomProfile?.id ?? "")
|
||||
.then((res) {
|
||||
@ -1391,6 +1465,45 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> confirmRoomStartupFailureAndLeave({
|
||||
BuildContext? navigationContext,
|
||||
}) async {
|
||||
if (_isHandlingRoomStartupFailure) {
|
||||
return;
|
||||
}
|
||||
_isHandlingRoomStartupFailure = true;
|
||||
final failedRoom = currenRoom;
|
||||
final groupId = failedRoom?.roomProfile?.roomProfile?.roomAccount ?? "";
|
||||
final roomId = failedRoom?.roomProfile?.roomProfile?.id ?? "";
|
||||
final navigatorContext = navigationContext ?? context;
|
||||
final navigator =
|
||||
navigatorContext != null ? Navigator.maybeOf(navigatorContext) : null;
|
||||
try {
|
||||
_stopRoomStatePolling();
|
||||
SCGiftVapSvgaManager().stopPlayback();
|
||||
SCHeartbeatUtils.cancelTimer();
|
||||
SCHeartbeatUtils.cancelAnchorTimer();
|
||||
rtmProvider?.cleanRoomData();
|
||||
if (groupId.isNotEmpty) {
|
||||
await rtmProvider?.quitGroup(groupId);
|
||||
}
|
||||
await engine?.leaveChannel();
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
if (roomId.isNotEmpty) {
|
||||
await SCAccountRepository().quitRoom(roomId);
|
||||
}
|
||||
} catch (e) {
|
||||
print('rtc进房失败清理本地房间状态出错: $e');
|
||||
} finally {
|
||||
_clearData();
|
||||
_isHandlingRoomStartupFailure = false;
|
||||
notifyListeners();
|
||||
if (navigator != null && navigator.mounted && navigator.canPop()) {
|
||||
navigator.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///清空列表数据
|
||||
void _clearData() {
|
||||
_stopRoomStatePolling();
|
||||
@ -1399,6 +1512,9 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
_disableMicListRefreshForCurrentSession = false;
|
||||
_disableOnlineUsersRefreshForCurrentSession = false;
|
||||
_isSelfMicActionInFlight = false;
|
||||
_roomStartupStatus = RoomStartupStatus.idle;
|
||||
_roomStartupFailureType = RoomStartupFailureType.none;
|
||||
_isHandlingRoomStartupFailure = false;
|
||||
_pendingMicListRefresh = false;
|
||||
_pendingMicListRefreshNotifyIfUnchanged = false;
|
||||
_seatEmojiEventVersions.clear();
|
||||
|
||||
@ -5,6 +5,7 @@ import 'package:yumi/shared/tools/sc_url_launcher_utils.dart';
|
||||
import 'package:yumi/shared/business_logic/models/res/sc_index_banner_res.dart';
|
||||
import 'package:yumi/modules/index/main_route.dart';
|
||||
import 'package:yumi/app/routes/sc_fluro_navigator.dart';
|
||||
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
|
||||
|
||||
import '../../shared/data_sources/models/enum/sc_banner_open_type.dart';
|
||||
|
||||
@ -35,12 +36,29 @@ class SCBannerUtils {
|
||||
}
|
||||
|
||||
if (SCStringUtils.checkIfUrl(params)) {
|
||||
final h5Url = _appendToken(params);
|
||||
SCNavigatorUtils.push(
|
||||
context,
|
||||
"${SCMainRoute.webViewPage}?url=${Uri.encodeComponent(params)}&showTitle=false",
|
||||
"${SCMainRoute.webViewPage}?url=${Uri.encodeComponent(h5Url)}&showTitle=false",
|
||||
replace: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static String _appendToken(String url) {
|
||||
final token = AccountStorage().getToken();
|
||||
if (token.isEmpty) {
|
||||
return url;
|
||||
}
|
||||
|
||||
final uri = Uri.tryParse(url);
|
||||
if (uri == null) {
|
||||
return url;
|
||||
}
|
||||
|
||||
final queryParameters = Map<String, String>.from(uri.queryParameters);
|
||||
queryParameters['token'] = token;
|
||||
return uri.replace(queryParameters: queryParameters).toString();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_debouncer/flutter_debouncer.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
@ -288,7 +289,16 @@ class OpenRoomLoadingDialog extends Dialog {
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Image.asset("sc_images/raw/loading.webp", width: 80.w),
|
||||
SizedBox(
|
||||
width: 80.w,
|
||||
height: 80.w,
|
||||
child: Center(
|
||||
child: CupertinoActivityIndicator(
|
||||
color: Colors.white24,
|
||||
radius: 14.w,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 12.w),
|
||||
Text(
|
||||
"正在进入聊天室......",
|
||||
|
||||
7
需求进度.md
7
需求进度.md
@ -3,6 +3,13 @@
|
||||
## 当前总目标
|
||||
- 控制当前 Flutter Android 发包体积,持续定位冗余组件、超大资源和不合理构建配置,并把每一步处理结果落盘记录。
|
||||
|
||||
## 本轮语言房进房加载逻辑优化(已完成)
|
||||
- 已按竞品加载思路梳理当前语言房进房链路:当前业务 `entryRoom` 成功后会先进入房间页,IM 加群、声网入会、麦位与在线用户刷新在房间页展示后继续进行;这与“IM 和声网都完成后才进入完整房间”的体验存在差异。
|
||||
- 已把语言房进房过程改造为“先进入房间骨架页,再完成关键启动任务”的状态机:新增房间启动态,进入房间页后显示房内 loading;IM 加群和声网入会成功后才切到 ready;任一关键步骤失败则进入 failed。
|
||||
- 已将 IM 加群失败和声网入会失败统一收口到房间页阻断弹窗:失败后不再分散 toast 或静默失败,用户点击确认后会清理 IM 群、声网频道、心跳、轮询与本地房间状态,并返回上一级页面。
|
||||
- 已为进房失败与语音连接失败提示补齐 `en/ar/tr/bn` 多语言文案,避免新失败弹窗出现硬编码中文。
|
||||
- 当前麦位与在线用户仍保持分批加载策略:进入房间页后会继续拉麦位、在线用户并启动轮询,头像框、礼物、火箭、红包、榜单等非关键资源仍放在房间进入后的异步补齐链路,避免阻塞首屏骨架。
|
||||
|
||||
## 本轮语言房背景页框架(进行中)
|
||||
- 已按 2026-04-22 当前最新需求先落第一版页面骨架:新增两个独立路由,分别用于“背景选择页”和“自定义背景上传页”;其中语音房底部菜单里的 `Background` 图标入口,现已从旧的房间主题页切到新的背景选择页。
|
||||
- 背景选择页已按“接近项目商店页”的结构先搭好导航和内容骨架:顶部为返回键、`官方 / 我的` 两个 tab 和右侧 `自定义` 文本按钮;点击右侧 `自定义` 会跳转到新的上传页。当前页内已先补上双列背景卡片栅格、选中高亮和“使用中”状态角标,便于后续继续接后台数据。
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user