This commit is contained in:
NIGGER SLAYER 2026-04-16 17:44:56 +08:00
parent 3088dff966
commit 526a498de6
7 changed files with 222 additions and 892 deletions

View File

@ -12,8 +12,7 @@ import 'package:yumi/shared/tools/sc_path_utils.dart';
import 'package:yumi/shared/business_logic/models/res/join_room_res.dart'; import 'package:yumi/shared/business_logic/models/res/join_room_res.dart';
import 'package:yumi/shared/business_logic/models/res/mic_res.dart'; import 'package:yumi/shared/business_logic/models/res/mic_res.dart';
import 'package:yumi/services/audio/rtc_manager.dart'; import 'package:yumi/services/audio/rtc_manager.dart';
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart'; import 'package:yumi/shared/data_sources/models/enum/sc_room_special_mike_type.dart';
import 'package:yumi/ui_kit/widgets/svga/sc_svga_asset_widget.dart';
/// ///
class SCSeatItem extends StatefulWidget { class SCSeatItem extends StatefulWidget {
@ -426,44 +425,17 @@ class _SonicState extends State<Sonic> with TickerProviderStateMixin {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isCurrentUserSeat = _isCurrentUserSeat();
return Stack( return Stack(
clipBehavior: Clip.none, clipBehavior: Clip.none,
children: [ children: [
SonicItem( SonicItem(ctrList[0], widget.room, isGameModel: widget.isGameModel),
ctrList[0], SonicItem(ctrList[1], widget.room, isGameModel: widget.isGameModel),
widget.room, SonicItem(ctrList[2], widget.room, isGameModel: widget.isGameModel),
isGameModel: widget.isGameModel, SonicItem(ctrList[3], widget.room, isGameModel: widget.isGameModel),
isCurrentUserSeat: isCurrentUserSeat,
),
SonicItem(
ctrList[1],
widget.room,
isGameModel: widget.isGameModel,
isCurrentUserSeat: isCurrentUserSeat,
),
SonicItem(
ctrList[2],
widget.room,
isGameModel: widget.isGameModel,
isCurrentUserSeat: isCurrentUserSeat,
),
SonicItem(
ctrList[3],
widget.room,
isGameModel: widget.isGameModel,
isCurrentUserSeat: isCurrentUserSeat,
),
], ],
); );
} }
bool _isCurrentUserSeat() {
final userId = provider.roomWheatMap[widget.index]?.user?.id;
return (userId?.isNotEmpty ?? false) &&
userId == AccountStorage().getCurrentUser()?.userProfile?.id;
}
void _checkSoundAni(int volume) async { void _checkSoundAni(int volume) async {
if (volume <= 20) return; if (volume <= 20) return;
// await Future.delayed(Duration(milliseconds: 10)); // await Future.delayed(Duration(milliseconds: 10));
@ -502,79 +474,64 @@ class SonicItem extends AnimatedWidget {
final Animation<double> animation; final Animation<double> animation;
final JoinRoomRes? room; final JoinRoomRes? room;
final bool isGameModel; final bool isGameModel;
final bool isCurrentUserSeat;
SonicItem( SonicItem(this.animation, this.room, {super.key, this.isGameModel = false})
this.animation, : super(listenable: animation);
this.room, {
super.key,
this.isGameModel = false,
required this.isCurrentUserSeat,
}) : super(listenable: animation);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final specialMikeType = room?.roomProfile?.roomSetting?.roomSpecialMikeType;
final Widget fallbackWidget = _buildFallbackWidget(
specialMikeType,
animation.value,
);
return Visibility( return Visibility(
visible: animation.value > 0, visible: animation.value > 0,
child: ScaleTransition( child: ScaleTransition(
scale: Tween(begin: 1.0, end: 1.4).animate(animation), scale: Tween(begin: 1.0, end: 1.4).animate(animation),
child: SizedBox( child: Container(
height: width(isGameModel ? 34 : 52), height: width(isGameModel ? 34 : 52),
width: width(isGameModel ? 34 : 52), width: width(isGameModel ? 34 : 52),
child: SCSvgaAssetWidget( decoration:
assetPath: _resolveSvgaAssetPath(specialMikeType), (room?.roomProfile?.roomSetting?.roomSpecialMikeType ?? "")
active: true, .isEmpty ||
loop: true, (room?.roomProfile?.roomSetting?.roomSpecialMikeType !=
fit: BoxFit.contain, SCRoomSpecialMikeType.TYPE_VIP3.name &&
fallback: fallbackWidget, room?.roomProfile?.roomSetting?.roomSpecialMikeType !=
), SCRoomSpecialMikeType.TYPE_VIP4.name &&
), room?.roomProfile?.roomSetting?.roomSpecialMikeType !=
), SCRoomSpecialMikeType.TYPE_VIP5.name &&
); room?.roomProfile?.roomSetting?.roomSpecialMikeType !=
} SCRoomSpecialMikeType.TYPE_VIP6.name)
? BoxDecoration(
String _resolveSvgaAssetPath(String? specialMikeType) {
switch (specialMikeType) {
case "TYPE_VIP3":
return "sc_images/room/sc_icon_room_vip3_sonic_anim.svga";
case "TYPE_VIP4":
return "sc_images/room/sc_icon_room_vip4_sonic_anim.svga";
case "TYPE_VIP5":
return "sc_images/room/sc_icon_room_vip5_sonic_anim.svga";
case "TYPE_VIP6":
return "sc_images/room/sc_icon_room_vip6_sonic_anim.svga";
default:
return isCurrentUserSeat
? "sc_images/room/sc_icon_room_self_sonic_anim.svga"
: "sc_images/room/sc_icon_room_other_sonic_anim.svga";
}
}
Widget _buildFallbackWidget(String? specialMikeType, double animationValue) {
switch (specialMikeType) {
case "TYPE_VIP3":
return Image.asset("sc_images/room/sc_icon_room_vip3_sonic_anim.webp");
case "TYPE_VIP4":
return Image.asset("sc_images/room/sc_icon_room_vip4_sonic_anim.webp");
case "TYPE_VIP5":
return Image.asset("sc_images/room/sc_icon_room_vip5_sonic_anim.webp");
case "TYPE_VIP6":
return Image.asset("sc_images/room/sc_icon_room_vip6_sonic_anim.webp");
default:
return DecoratedBox(
decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
color: SocialChatTheme.primaryColor.withValues( color: SocialChatTheme.primaryColor.withValues(
alpha: 1 - animationValue, alpha: 1 - animation.value,
),
)
: BoxDecoration(),
child:
room?.roomProfile?.roomSetting?.roomSpecialMikeType ==
SCRoomSpecialMikeType.TYPE_VIP3.name
? Image.asset(
"sc_images/room/sc_icon_room_vip3_sonic_anim.webp",
)
: (room?.roomProfile?.roomSetting?.roomSpecialMikeType ==
SCRoomSpecialMikeType.TYPE_VIP4.name
? Image.asset(
"sc_images/room/sc_icon_room_vip4_sonic_anim.webp",
)
: (room?.roomProfile?.roomSetting?.roomSpecialMikeType ==
SCRoomSpecialMikeType.TYPE_VIP5.name
? Image.asset(
"sc_images/room/sc_icon_room_vip5_sonic_anim.webp",
)
: (room
?.roomProfile
?.roomSetting
?.roomSpecialMikeType ==
SCRoomSpecialMikeType.TYPE_VIP6.name
? Image.asset(
"sc_images/room/sc_icon_room_vip6_sonic_anim.webp",
)
: Container()))),
), ),
), ),
); );
} }
}
} }

View File

@ -1,445 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
class BaishunDebugLogEntry {
const BaishunDebugLogEntry({
required this.timestamp,
required this.tag,
required this.message,
});
final DateTime timestamp;
final String tag;
final String message;
String get formattedTime {
final hh = timestamp.hour.toString().padLeft(2, '0');
final mm = timestamp.minute.toString().padLeft(2, '0');
final ss = timestamp.second.toString().padLeft(2, '0');
return '$hh:$mm:$ss';
}
}
class BaishunDebugSnapshot {
const BaishunDebugSnapshot({
required this.roomId,
required this.gameName,
required this.gameId,
required this.gameSessionId,
required this.entryUrl,
required this.launchMode,
required this.loading,
required this.pageFinished,
required this.receivedBridgeMessage,
required this.injectCount,
required this.configSendCount,
required this.lastBridgeSource,
required this.lastBridgeAction,
required this.lastBridgePayload,
required this.lastJsCallback,
required this.lastConfigSummary,
required this.errorMessage,
required this.logs,
});
final String roomId;
final String gameName;
final String gameId;
final String gameSessionId;
final String entryUrl;
final String launchMode;
final bool loading;
final bool pageFinished;
final bool receivedBridgeMessage;
final int injectCount;
final int configSendCount;
final String lastBridgeSource;
final String lastBridgeAction;
final String lastBridgePayload;
final String lastJsCallback;
final String lastConfigSummary;
final String errorMessage;
final List<BaishunDebugLogEntry> logs;
}
class BaishunDebugPanel extends StatefulWidget {
const BaishunDebugPanel({
super.key,
required this.snapshot,
required this.onReload,
required this.onInjectBridge,
required this.onReplayConfig,
required this.onWalletUpdate,
required this.onClearLogs,
});
final BaishunDebugSnapshot snapshot;
final VoidCallback onReload;
final VoidCallback onInjectBridge;
final VoidCallback onReplayConfig;
final VoidCallback onWalletUpdate;
final VoidCallback onClearLogs;
@override
State<BaishunDebugPanel> createState() => _BaishunDebugPanelState();
}
class _BaishunDebugPanelState extends State<BaishunDebugPanel> {
bool _expanded = false;
@override
Widget build(BuildContext context) {
final maxWidth = (ScreenUtil().screenWidth - 24.w).clamp(220.w, 360.w);
return AnimatedSwitcher(
duration: const Duration(milliseconds: 180),
child:
_expanded
? Container(
key: const ValueKey<String>('baishun_debug_panel_expanded'),
width: maxWidth,
padding: EdgeInsets.all(12.w),
decoration: BoxDecoration(
color: const Color(0xE616302B),
borderRadius: BorderRadius.circular(16.w),
border: Border.all(
color: Colors.white.withValues(alpha: 0.14),
width: 1.w,
),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.22),
blurRadius: 18.w,
offset: Offset(0, 8.w),
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
'BAISHUN DEBUG',
style: TextStyle(
color: Colors.white,
fontSize: 12.sp,
fontWeight: FontWeight.w800,
letterSpacing: 0.4,
),
),
const Spacer(),
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
setState(() {
_expanded = false;
});
},
child: Padding(
padding: EdgeInsets.all(2.w),
child: Icon(
Icons.close,
size: 16.w,
color: Colors.white70,
),
),
),
],
),
SizedBox(height: 8.w),
_InfoLine(label: 'Room', value: widget.snapshot.roomId),
_InfoLine(
label: 'Game',
value:
'${widget.snapshot.gameName} (${widget.snapshot.gameId})',
),
_InfoLine(
label: 'Session',
value: widget.snapshot.gameSessionId,
),
_InfoLine(
label: 'Launch',
value: widget.snapshot.launchMode,
),
_InfoLine(
label: 'Loading',
value: widget.snapshot.loading ? 'YES' : 'NO',
),
_InfoLine(
label: 'PageFinished',
value: widget.snapshot.pageFinished ? 'YES' : 'NO',
),
_InfoLine(
label: 'BridgeMsg',
value:
widget.snapshot.receivedBridgeMessage ? 'YES' : 'NO',
),
_InfoLine(
label: 'InjectCount',
value: '${widget.snapshot.injectCount}',
),
_InfoLine(
label: 'ConfigSend',
value: '${widget.snapshot.configSendCount}',
),
_InfoLine(
label: 'LastSource',
value:
widget.snapshot.lastBridgeSource.isEmpty
? '--'
: widget.snapshot.lastBridgeSource,
),
_InfoLine(
label: 'LastAction',
value:
widget.snapshot.lastBridgeAction.isEmpty
? '--'
: widget.snapshot.lastBridgeAction,
),
_InfoLine(
label: 'LastCallback',
value:
widget.snapshot.lastJsCallback.isEmpty
? '--'
: widget.snapshot.lastJsCallback,
),
if (widget.snapshot.lastConfigSummary.isNotEmpty)
_MultilineLine(
label: 'Config',
value: widget.snapshot.lastConfigSummary,
),
_MultilineLine(
label: 'EntryUrl',
value: widget.snapshot.entryUrl,
),
if (widget.snapshot.lastBridgePayload.isNotEmpty)
_MultilineLine(
label: 'Payload',
value: widget.snapshot.lastBridgePayload,
),
if (widget.snapshot.errorMessage.isNotEmpty)
_MultilineLine(
label: 'Error',
value: widget.snapshot.errorMessage,
valueColor: const Color(0xFFFFB4B4),
),
SizedBox(height: 10.w),
Wrap(
spacing: 8.w,
runSpacing: 8.w,
children: [
_DebugActionChip(
label: 'Reload',
onTap: widget.onReload,
),
_DebugActionChip(
label: 'Inject',
onTap: widget.onInjectBridge,
),
_DebugActionChip(
label: 'ReplayConfig',
onTap: widget.onReplayConfig,
),
_DebugActionChip(
label: 'Wallet',
onTap: widget.onWalletUpdate,
),
_DebugActionChip(
label: 'ClearLogs',
onTap: widget.onClearLogs,
),
],
),
SizedBox(height: 10.w),
Text(
'Recent Logs',
style: TextStyle(
color: Colors.white,
fontSize: 11.sp,
fontWeight: FontWeight.w700,
),
),
SizedBox(height: 6.w),
Container(
height: 180.w,
padding: EdgeInsets.all(8.w),
decoration: BoxDecoration(
color: Colors.black.withValues(alpha: 0.22),
borderRadius: BorderRadius.circular(12.w),
border: Border.all(
color: Colors.white.withValues(alpha: 0.08),
),
),
child:
widget.snapshot.logs.isEmpty
? Center(
child: Text(
'No bridge logs yet',
style: TextStyle(
color: Colors.white54,
fontSize: 11.sp,
),
),
)
: ListView.separated(
itemCount: widget.snapshot.logs.length,
separatorBuilder:
(_, __) => SizedBox(height: 6.w),
itemBuilder: (context, index) {
final entry = widget.snapshot.logs[index];
return Text(
'[${entry.formattedTime}] ${entry.tag} ${entry.message}',
style: TextStyle(
color: Colors.white70,
fontSize: 10.sp,
height: 1.35,
),
);
},
),
),
],
),
)
: GestureDetector(
key: const ValueKey<String>('baishun_debug_panel_collapsed'),
behavior: HitTestBehavior.opaque,
onTap: () {
setState(() {
_expanded = true;
});
},
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 12.w,
vertical: 8.w,
),
decoration: BoxDecoration(
color: const Color(0xD916302B),
borderRadius: BorderRadius.circular(999.w),
border: Border.all(
color: Colors.white.withValues(alpha: 0.12),
),
),
child: Text(
'BS DEBUG',
style: TextStyle(
color: Colors.white,
fontSize: 11.sp,
fontWeight: FontWeight.w700,
),
),
),
),
);
}
}
class _InfoLine extends StatelessWidget {
const _InfoLine({required this.label, required this.value});
final String label;
final String value;
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.only(bottom: 4.w),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 82.w,
child: Text(
'$label:',
style: TextStyle(
color: Colors.white60,
fontSize: 10.sp,
fontWeight: FontWeight.w600,
),
),
),
Expanded(
child: Text(
value,
style: TextStyle(
color: Colors.white,
fontSize: 10.sp,
height: 1.3,
),
),
),
],
),
);
}
}
class _MultilineLine extends StatelessWidget {
const _MultilineLine({
required this.label,
required this.value,
this.valueColor = Colors.white,
});
final String label;
final String value;
final Color valueColor;
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.only(top: 4.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'$label:',
style: TextStyle(
color: Colors.white60,
fontSize: 10.sp,
fontWeight: FontWeight.w600,
),
),
SizedBox(height: 2.w),
SelectableText(
value,
style: TextStyle(color: valueColor, fontSize: 10.sp, height: 1.35),
),
],
),
);
}
}
class _DebugActionChip extends StatelessWidget {
const _DebugActionChip({required this.label, required this.onTap});
final String label;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: onTap,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 6.w),
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.08),
borderRadius: BorderRadius.circular(999.w),
border: Border.all(color: Colors.white.withValues(alpha: 0.08)),
),
child: Text(
label,
style: TextStyle(
color: Colors.white,
fontSize: 10.sp,
fontWeight: FontWeight.w600,
),
),
),
);
}
}

View File

@ -1,5 +1,4 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
@ -11,7 +10,6 @@ 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/bridge/baishun_js_bridge.dart';
import 'package:yumi/modules/room_game/data/models/room_game_models.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/data/room_game_repository.dart';
import 'package:yumi/modules/room_game/views/baishun_debug_panel.dart';
import 'package:yumi/modules/room_game/views/baishun_loading_view.dart'; import 'package:yumi/modules/room_game/views/baishun_loading_view.dart';
import 'package:yumi/modules/wallet/wallet_route.dart'; import 'package:yumi/modules/wallet/wallet_route.dart';
import 'package:yumi/ui_kit/components/sc_tts.dart'; import 'package:yumi/ui_kit/components/sc_tts.dart';
@ -41,31 +39,13 @@ class _BaishunGamePageState extends State<BaishunGamePage> {
late final WebViewController _controller; late final WebViewController _controller;
Timer? _bridgeBootstrapTimer; Timer? _bridgeBootstrapTimer;
Timer? _loadingFallbackTimer; Timer? _loadingFallbackTimer;
final List<BaishunDebugLogEntry> _debugLogs = <BaishunDebugLogEntry>[];
bool _isLoading = true; bool _isLoading = true;
bool _isClosing = false; bool _isClosing = false;
bool _didReceiveBridgeMessage = false; bool _didReceiveBridgeMessage = false;
bool _didFinishPageLoad = false; bool _didFinishPageLoad = false;
bool _hasDeliveredLaunchConfig = false; bool _hasDeliveredLaunchConfig = false;
int _bridgeInjectCount = 0;
int _configSendCount = 0;
String? _errorMessage; String? _errorMessage;
String _lastBridgeSource = '';
String _lastBridgeAction = '';
String _lastBridgePayload = '';
String _lastJsCallback = '';
String _lastConfigSummary = '';
int get _launchOrientationValue {
if (widget.launchModel.entry.orientation != 0) {
return widget.launchModel.entry.orientation;
}
if (widget.game.orientation != 0) {
return widget.game.orientation;
}
return 1;
}
@override @override
void initState() { void initState() {
@ -114,19 +94,13 @@ class _BaishunGamePageState extends State<BaishunGamePage> {
NavigationDelegate( NavigationDelegate(
onPageStarted: (String _) { onPageStarted: (String _) {
_prepareForPageLoad(); _prepareForPageLoad();
_appendDebugLog('page', 'page started');
}, },
onPageFinished: (String _) async { onPageFinished: (String _) async {
_didFinishPageLoad = true; _didFinishPageLoad = true;
_appendDebugLog('page', 'page finished');
await _injectBridge(reason: 'page_finished'); await _injectBridge(reason: 'page_finished');
}, },
onWebResourceError: (WebResourceError error) { onWebResourceError: (WebResourceError error) {
_stopBridgeBootstrap(); _stopBridgeBootstrap();
_appendDebugLog(
'error',
'web resource error: ${error.description}',
);
if (!mounted) { if (!mounted) {
return; return;
} }
@ -150,14 +124,7 @@ class _BaishunGamePageState extends State<BaishunGamePage> {
_didReceiveBridgeMessage = false; _didReceiveBridgeMessage = false;
_didFinishPageLoad = false; _didFinishPageLoad = false;
_hasDeliveredLaunchConfig = false; _hasDeliveredLaunchConfig = false;
_bridgeInjectCount = 0;
_configSendCount = 0;
_stopBridgeBootstrap(); _stopBridgeBootstrap();
_lastBridgeSource = '';
_lastBridgeAction = '';
_lastBridgePayload = '';
_lastJsCallback = '';
_lastConfigSummary = '';
_bridgeBootstrapTimer = Timer.periodic(const Duration(milliseconds: 250), ( _bridgeBootstrapTimer = Timer.periodic(const Duration(milliseconds: 250), (
Timer timer, Timer timer,
) { ) {
@ -174,7 +141,6 @@ class _BaishunGamePageState extends State<BaishunGamePage> {
if (!mounted || _didReceiveBridgeMessage || _errorMessage != null) { if (!mounted || _didReceiveBridgeMessage || _errorMessage != null) {
return; return;
} }
_appendDebugLog('loading', 'timeout fallback, hide loading');
setState(() { setState(() {
_isLoading = false; _isLoading = false;
}); });
@ -199,10 +165,6 @@ class _BaishunGamePageState extends State<BaishunGamePage> {
try { try {
_prepareForPageLoad(); _prepareForPageLoad();
final entryUrl = widget.launchModel.entry.entryUrl.trim(); final entryUrl = widget.launchModel.entry.entryUrl.trim();
_appendDebugLog(
'launch',
'load entry launchMode=${widget.launchModel.entry.launchMode} orientation=$_launchOrientationValue url=$entryUrl',
);
if (entryUrl.isEmpty || entryUrl.startsWith('mock://')) { if (entryUrl.isEmpty || entryUrl.startsWith('mock://')) {
final html = await rootBundle.loadString( final html = await rootBundle.loadString(
'assets/debug/baishun_mock/index.html', 'assets/debug/baishun_mock/index.html',
@ -220,7 +182,6 @@ class _BaishunGamePageState extends State<BaishunGamePage> {
} }
await _controller.loadRequest(uri); await _controller.loadRequest(uri);
} catch (error) { } catch (error) {
_appendDebugLog('error', 'load entry failed: $error');
if (!mounted) { if (!mounted) {
return; return;
} }
@ -232,51 +193,21 @@ class _BaishunGamePageState extends State<BaishunGamePage> {
} }
Future<void> _injectBridge({String reason = 'manual'}) async { Future<void> _injectBridge({String reason = 'manual'}) async {
_bridgeInjectCount += 1;
if (reason != 'bootstrap') {
_appendDebugLog('inject', 'reason=$reason count=$_bridgeInjectCount');
}
try { try {
await _controller.runJavaScript(BaishunJsBridge.bootstrapScript()); await _controller.runJavaScript(BaishunJsBridge.bootstrapScript());
} catch (_) {} } catch (_) {}
} }
Future<void> _sendConfigToGame({ Future<void> _sendConfigToGame({
required String reason,
String? jsCallbackPath, String? jsCallbackPath,
bool force = false, bool force = false,
}) async { }) async {
if (_hasDeliveredLaunchConfig && !force) { if (_hasDeliveredLaunchConfig && !force) {
_appendDebugLog(
'config',
'skip duplicate config send because BAISHUN code is one-time',
);
return; return;
} }
_configSendCount += 1;
_hasDeliveredLaunchConfig = true; _hasDeliveredLaunchConfig = true;
final rawConfig = widget.launchModel.bridgeConfig; final rawConfig = widget.launchModel.bridgeConfig;
final config = _buildEffectiveBridgeConfig(rawConfig); final config = _buildEffectiveBridgeConfig(rawConfig);
final code = config.code.trim();
final maskedCode =
code.length <= 8
? code
: '${code.substring(0, 4)}...${code.substring(code.length - 4)}';
_lastConfigSummary =
'appChannel=${config.appChannel}, appId=${config.appId}, userId=${config.userId}, roomId=${config.roomId}, gameMode=${config.gameMode}, language=${config.language}(raw=${rawConfig.language}), gsp=${config.gsp}, code=$maskedCode(len=${code.length})';
if (config.language != rawConfig.language.trim()) {
_appendDebugLog(
'config',
'normalize language ${rawConfig.language} -> ${config.language} by BAISHUN table',
);
}
_appendDebugLog(
'config',
'reason=$reason count=$_configSendCount callback=${jsCallbackPath?.trim().isNotEmpty == true ? jsCallbackPath!.trim() : '--'} $_lastConfigSummary',
);
if (mounted) {
setState(() {});
}
try { try {
await _controller.runJavaScript( await _controller.runJavaScript(
BaishunJsBridge.buildConfigScript( BaishunJsBridge.buildConfigScript(
@ -289,11 +220,7 @@ class _BaishunGamePageState extends State<BaishunGamePage> {
Future<void> _handleBridgeMessage(JavaScriptMessage message) async { Future<void> _handleBridgeMessage(JavaScriptMessage message) async {
final bridgeMessage = BaishunBridgeMessage.parse(message.message); final bridgeMessage = BaishunBridgeMessage.parse(message.message);
await _dispatchBridgeMessage( await _dispatchBridgeMessage(bridgeMessage);
bridgeMessage,
source: BaishunJsBridge.channelName,
rawMessage: message.message,
);
} }
Future<void> _handleNamedChannelMessage( Future<void> _handleNamedChannelMessage(
@ -304,58 +231,27 @@ class _BaishunGamePageState extends State<BaishunGamePage> {
action, action,
message.message, message.message,
); );
await _dispatchBridgeMessage( await _dispatchBridgeMessage(bridgeMessage);
bridgeMessage,
source: 'channel:$action',
rawMessage: message.message,
);
} }
Future<void> _dispatchBridgeMessage( Future<void> _dispatchBridgeMessage(
BaishunBridgeMessage bridgeMessage, { BaishunBridgeMessage bridgeMessage,
required String source, ) async {
required String rawMessage,
}) async {
if (bridgeMessage.action == BaishunBridgeActions.debugLog) { if (bridgeMessage.action == BaishunBridgeActions.debugLog) {
final tag = bridgeMessage.payload['tag']?.toString().trim();
final message = bridgeMessage.payload['message']?.toString().trim();
if ((tag ?? '').isNotEmpty || (message ?? '').isNotEmpty) {
_appendDebugLog(
tag == null || tag.isEmpty ? 'h5' : 'h5:$tag',
message == null || message.isEmpty ? rawMessage.trim() : message,
);
}
return; return;
} }
final callbackPath = _extractCallbackPath(bridgeMessage.payload); final callbackPath = _extractCallbackPath(bridgeMessage.payload);
if (bridgeMessage.action.isNotEmpty) { if (bridgeMessage.action.isNotEmpty) {
_didReceiveBridgeMessage = true; _didReceiveBridgeMessage = true;
_stopBridgeBootstrap(); _stopBridgeBootstrap();
_lastBridgeSource = source;
_lastBridgeAction = bridgeMessage.action;
_lastBridgePayload = _formatPayload(bridgeMessage.payload);
if (callbackPath.isNotEmpty) {
_lastJsCallback = callbackPath;
}
_appendDebugLog(
'bridge',
'$source -> ${bridgeMessage.action}${callbackPath.isEmpty ? '' : ' callback=$callbackPath'}${_lastBridgePayload.isEmpty ? '' : ' payload=$_lastBridgePayload'}',
);
if (mounted) {
setState(() {});
}
} else if (rawMessage.trim().isNotEmpty) {
_appendDebugLog('bridge', '$source -> raw=${rawMessage.trim()}');
} }
switch (bridgeMessage.action) { switch (bridgeMessage.action) {
case BaishunBridgeActions.getConfig: case BaishunBridgeActions.getConfig:
await _sendConfigToGame( await _sendConfigToGame(
reason: 'get_config',
jsCallbackPath: callbackPath.isEmpty ? null : callbackPath, jsCallbackPath: callbackPath.isEmpty ? null : callbackPath,
); );
break; break;
case BaishunBridgeActions.gameLoaded: case BaishunBridgeActions.gameLoaded:
_appendDebugLog('loading', 'gameLoaded received');
if (!mounted) { if (!mounted) {
return; return;
} }
@ -365,12 +261,10 @@ class _BaishunGamePageState extends State<BaishunGamePage> {
}); });
break; break;
case BaishunBridgeActions.gameRecharge: case BaishunBridgeActions.gameRecharge:
_appendDebugLog('bridge', 'open recharge page');
await SCNavigatorUtils.push(context, WalletRoute.recharge); await SCNavigatorUtils.push(context, WalletRoute.recharge);
await _notifyWalletUpdate(); await _notifyWalletUpdate();
break; break;
case BaishunBridgeActions.destroy: case BaishunBridgeActions.destroy:
_appendDebugLog('bridge', 'destroy requested by H5');
await _closeAndExit(reason: 'h5_destroy'); await _closeAndExit(reason: 'h5_destroy');
break; break;
default: default:
@ -379,7 +273,6 @@ class _BaishunGamePageState extends State<BaishunGamePage> {
} }
Future<void> _notifyWalletUpdate() async { Future<void> _notifyWalletUpdate() async {
_appendDebugLog('wallet', 'send walletUpdate to H5');
try { try {
await _controller.runJavaScript( await _controller.runJavaScript(
BaishunJsBridge.buildWalletUpdateScript(), BaishunJsBridge.buildWalletUpdateScript(),
@ -388,40 +281,14 @@ class _BaishunGamePageState extends State<BaishunGamePage> {
} }
Future<void> _reload() async { Future<void> _reload() async {
if (!mounted) {
return;
}
_appendDebugLog('panel', 'manual reload');
await _loadGameEntry();
}
Future<void> _manualInjectBridge() async {
await _injectBridge(reason: 'manual');
}
Future<void> _replayLastConfig() async {
final callbackPath = _lastJsCallback.trim();
_appendDebugLog(
'panel',
callbackPath.isEmpty
? 'manual replay config without jsCallback'
: 'manual replay config to $callbackPath',
);
await _sendConfigToGame(
reason: 'manual_replay',
jsCallbackPath: callbackPath.isEmpty ? null : callbackPath,
force: true,
);
}
void _clearDebugLogs() {
if (!mounted) { if (!mounted) {
return; return;
} }
setState(() { setState(() {
_debugLogs.clear(); _errorMessage = null;
_isLoading = true;
}); });
_appendDebugLog('panel', 'logs cleared'); await _loadGameEntry();
} }
Future<void> _closeAndExit({String reason = 'page_back'}) async { Future<void> _closeAndExit({String reason = 'page_back'}) async {
@ -542,39 +409,6 @@ class _BaishunGamePageState extends State<BaishunGamePage> {
} }
} }
String _formatPayload(Map<String, dynamic> payload) {
if (payload.isEmpty) {
return '';
}
try {
return jsonEncode(payload);
} catch (_) {
return payload.toString();
}
}
void _appendDebugLog(String tag, String message) {
const maxLogEntries = 80;
final entry = BaishunDebugLogEntry(
timestamp: DateTime.now(),
tag: tag,
message: message,
);
if (!mounted) {
_debugLogs.insert(0, entry);
if (_debugLogs.length > maxLogEntries) {
_debugLogs.removeRange(maxLogEntries, _debugLogs.length);
}
return;
}
setState(() {
_debugLogs.insert(0, entry);
if (_debugLogs.length > maxLogEntries) {
_debugLogs.removeRange(maxLogEntries, _debugLogs.length);
}
});
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return PopScope( return PopScope(
@ -585,14 +419,33 @@ class _BaishunGamePageState extends State<BaishunGamePage> {
} }
await _closeAndExit(); await _closeAndExit();
}, },
child: Scaffold( child: Material(
backgroundColor: const Color(0xFF081915), color: Colors.transparent,
body: SafeArea( child: Align(
bottom: false, alignment: Alignment.bottomCenter,
child: ClipRRect(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(24.w),
topRight: Radius.circular(24.w),
),
child: Container(
width: ScreenUtil().screenWidth,
height: ScreenUtil().screenHeight * 0.56,
color: const Color(0xFF081915),
child: Column( child: Column(
children: [ children: [
SizedBox(height: 10.w),
Container(
width: 34.w,
height: 4.w,
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.28),
borderRadius: BorderRadius.circular(99.w),
),
),
SizedBox(height: 6.w),
Padding( Padding(
padding: EdgeInsets.fromLTRB(10.w, 6.w, 10.w, 6.w), padding: EdgeInsets.symmetric(horizontal: 10.w),
child: Row( child: Row(
children: [ children: [
IconButton( IconButton(
@ -637,40 +490,6 @@ class _BaishunGamePageState extends State<BaishunGamePage> {
message: 'Waiting for gameLoaded...', message: 'Waiting for gameLoaded...',
), ),
), ),
Positioned(
left: 12.w,
bottom: 12.w,
child: BaishunDebugPanel(
snapshot: BaishunDebugSnapshot(
roomId: widget.roomId,
gameName: widget.game.name,
gameId: widget.game.gameId,
gameSessionId: widget.launchModel.gameSessionId,
entryUrl: widget.launchModel.entry.entryUrl,
launchMode:
'${widget.launchModel.entry.launchMode} / ori=$_launchOrientationValue',
loading: _isLoading,
pageFinished: _didFinishPageLoad,
receivedBridgeMessage: _didReceiveBridgeMessage,
injectCount: _bridgeInjectCount,
configSendCount: _configSendCount,
lastBridgeSource: _lastBridgeSource,
lastBridgeAction: _lastBridgeAction,
lastBridgePayload: _lastBridgePayload,
lastJsCallback: _lastJsCallback,
lastConfigSummary: _lastConfigSummary,
errorMessage: _errorMessage ?? '',
logs: List<BaishunDebugLogEntry>.unmodifiable(
_debugLogs,
),
),
onReload: _reload,
onInjectBridge: _manualInjectBridge,
onReplayConfig: _replayLastConfig,
onWalletUpdate: _notifyWalletUpdate,
onClearLogs: _clearDebugLogs,
),
),
], ],
), ),
), ),
@ -678,6 +497,8 @@ class _BaishunGamePageState extends State<BaishunGamePage> {
), ),
), ),
), ),
),
),
); );
} }

View File

@ -8,6 +8,7 @@ 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/data/room_game_repository.dart';
import 'package:yumi/modules/room_game/views/baishun_game_page.dart'; import 'package:yumi/modules/room_game/views/baishun_game_page.dart';
import 'package:yumi/services/audio/rtc_manager.dart'; import 'package:yumi/services/audio/rtc_manager.dart';
import 'package:yumi/shared/tools/sc_lk_dialog_util.dart';
import 'package:yumi/ui_kit/components/custom_cached_image.dart'; import 'package:yumi/ui_kit/components/custom_cached_image.dart';
import 'package:yumi/ui_kit/components/sc_tts.dart'; import 'package:yumi/ui_kit/components/sc_tts.dart';
import 'package:yumi/ui_kit/components/text/sc_text.dart'; import 'package:yumi/ui_kit/components/text/sc_text.dart';
@ -88,34 +89,11 @@ class _RoomGameListSheetState extends State<RoomGameListSheet> {
if (!widget.roomContext.mounted) { if (!widget.roomContext.mounted) {
return; return;
} }
await Navigator.of(widget.roomContext).push( showBottomInBottomDialog(
PageRouteBuilder<void>( widget.roomContext,
transitionDuration: const Duration(milliseconds: 260), BaishunGamePage(roomId: _roomId, game: game, launchModel: launchModel),
reverseTransitionDuration: const Duration(milliseconds: 220), barrierColor: Colors.black54,
pageBuilder: barrierDismissible: false,
(context, animation, secondaryAnimation) => BaishunGamePage(
roomId: _roomId,
game: game,
launchModel: launchModel,
),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
final curved = CurvedAnimation(
parent: animation,
curve: Curves.easeOutCubic,
reverseCurve: Curves.easeInCubic,
);
return FadeTransition(
opacity: curved,
child: SlideTransition(
position: Tween<Offset>(
begin: const Offset(0, 0.05),
end: Offset.zero,
).animate(curved),
child: child,
),
);
},
),
); );
} catch (error) { } catch (error) {
SCTts.show('Launch failed'); SCTts.show('Launch failed');

View File

@ -111,8 +111,7 @@ class _MePage2State extends State<MePage2> {
} }
Widget _buildProfileSection(SocialChatUserProfileManager profileManager) { Widget _buildProfileSection(SocialChatUserProfileManager profileManager) {
final profile = final profile = profileManager.currentUserProfile;
profileManager.userProfile ?? AccountStorage().getCurrentUser()?.userProfile;
return SCDebounceWidget( return SCDebounceWidget(
onTap: () { onTap: () {
@ -562,11 +561,9 @@ class _MenuRowData {
required this.title, required this.title,
required this.onTap, required this.onTap,
this.assetIcon, this.assetIcon,
this.iconData,
}); });
final String title; final String title;
final String? assetIcon; final String? assetIcon;
final IconData? iconData;
final VoidCallback onTap; final VoidCallback onTap;
} }

View File

@ -61,11 +61,13 @@ class SocialChatUserProfileManager extends ChangeNotifier {
notifyListeners(); notifyListeners();
} }
SocialChatUserProfile? get currentUserProfile =>
AccountStorage().getCurrentUser()?.userProfile;
void syncCurrentUserProfile(SocialChatUserProfile? profile) { void syncCurrentUserProfile(SocialChatUserProfile? profile) {
if (profile == null) { if (profile == null) {
return; return;
} }
userProfile = profile;
final currentUser = AccountStorage().getCurrentUser(); final currentUser = AccountStorage().getCurrentUser();
if (currentUser != null) { if (currentUser != null) {
currentUser.setUserProfile(profile); currentUser.setUserProfile(profile);
@ -94,7 +96,7 @@ class SocialChatUserProfileManager extends ChangeNotifier {
notifyListeners(); notifyListeners();
} }
/// /// Me
SocialChatUserProfile? userProfile; SocialChatUserProfile? userProfile;
void getUserInfoById(String userId) async { void getUserInfoById(String userId) async {
@ -138,43 +140,58 @@ class SocialChatUserProfileManager extends ChangeNotifier {
} }
void inviteHostOpt(BuildContext ct, String id, String status) async { void inviteHostOpt(BuildContext ct, String id, String status) async {
final successMessage = SCAppLocalizations.of(ct)!.operationSuccessful;
try { try {
await SCAccountRepository().inviteHost(id, status); await SCAccountRepository().inviteHost(id, status);
getUserIdentity(); getUserIdentity();
SCTts.show(SCAppLocalizations.of(ct)!.operationSuccessful); SCTts.show(successMessage);
} catch (e) {} } catch (e) {
debugPrint('inviteHostOpt failed: $e');
}
} }
void inviteAgentOpt(BuildContext ct, String id, String status) async { void inviteAgentOpt(BuildContext ct, String id, String status) async {
final successMessage = SCAppLocalizations.of(ct)!.operationSuccessful;
try { try {
await SCAccountRepository().inviteAgent(id, status); await SCAccountRepository().inviteAgent(id, status);
getUserIdentity(); getUserIdentity();
SCTts.show(SCAppLocalizations.of(ct)!.operationSuccessful); SCTts.show(successMessage);
} catch (e) {} } catch (e) {
debugPrint('inviteAgentOpt failed: $e');
}
} }
void inviteBDOpt(BuildContext ct, String id, String status) async { void inviteBDOpt(BuildContext ct, String id, String status) async {
final successMessage = SCAppLocalizations.of(ct)!.operationSuccessful;
try { try {
await SCAccountRepository().inviteBD(id, status); await SCAccountRepository().inviteBD(id, status);
getUserIdentity(); getUserIdentity();
SCTts.show(SCAppLocalizations.of(ct)!.operationSuccessful); SCTts.show(successMessage);
} catch (e) {} } catch (e) {
debugPrint('inviteBDOpt failed: $e');
}
} }
void inviteBDLeader(BuildContext ct, String id, String status) async { void inviteBDLeader(BuildContext ct, String id, String status) async {
final successMessage = SCAppLocalizations.of(ct)!.operationSuccessful;
try { try {
await SCAccountRepository().inviteBDLeader(id, status); await SCAccountRepository().inviteBDLeader(id, status);
getUserIdentity(); getUserIdentity();
SCTts.show(SCAppLocalizations.of(ct)!.operationSuccessful); SCTts.show(successMessage);
} catch (e) {} } catch (e) {
debugPrint('inviteBDLeader failed: $e');
}
} }
void inviteRechargeAgent(BuildContext ct, String id, String status) async { void inviteRechargeAgent(BuildContext ct, String id, String status) async {
final successMessage = SCAppLocalizations.of(ct)!.operationSuccessful;
try { try {
await SCAccountRepository().inviteRechargeAgent(id, status); await SCAccountRepository().inviteRechargeAgent(id, status);
getUserIdentity(); getUserIdentity();
SCTts.show(SCAppLocalizations.of(ct)!.operationSuccessful); SCTts.show(successMessage);
} catch (e) {} } catch (e) {
debugPrint('inviteRechargeAgent failed: $e');
}
} }
void cpRlationshipProcessApply( void cpRlationshipProcessApply(
@ -182,10 +199,13 @@ class SocialChatUserProfileManager extends ChangeNotifier {
String id, String id,
bool status, bool status,
) async { ) async {
final successMessage = SCAppLocalizations.of(ct)!.operationSuccessful;
try { try {
await SCAccountRepository().cpRelationshipProcessApply(id, status); await SCAccountRepository().cpRelationshipProcessApply(id, status);
fetchUserProfileData(loadGuardCount: false); fetchUserProfileData(loadGuardCount: false);
SCTts.show(SCAppLocalizations.of(ct)!.operationSuccessful); SCTts.show(successMessage);
} catch (e) {} } catch (e) {
debugPrint('cpRlationshipProcessApply failed: $e');
}
} }
} }

View File

@ -13,12 +13,13 @@
- 本轮按需求暂未处理网络链路上的启动等待,例如审核态检查或远端启动页配置请求。 - 本轮按需求暂未处理网络链路上的启动等待,例如审核态检查或远端启动页配置请求。
## 已完成模块 ## 已完成模块
- 已修复 `Me -> Friends/Fans 列表 -> 他人主页 -> 返回 Me` 后个人资料串号的问题:`MePage2` 现只读取当前登录用户资料,`SocialChatUserProfileManager.userProfile` 继续只承载“当前查看中的详情页用户”,同时移除了 `syncCurrentUserProfile()` 对详情态的覆盖,避免查看他人资料后把 `Me` 页错误渲染成对方信息。
- 已按本轮动效替换需求完成首页底部 tab 动效接入,并同步将本轮新增本地 `.svga` 资源统一改成项目命名风格:当前 `Home / Explore / Message / Me` 已分别使用 `sc_icon_home_anim.svga / sc_icon_explore_anim.svga / sc_icon_message_anim.svga / sc_icon_me_anim.svga`,并继续保留原有 png 作为失败兜底;后续新增本地动效资源默认也按 `sc_icon_*_anim` 规则命名。 - 已按本轮动效替换需求完成首页底部 tab 动效接入,并同步将本轮新增本地 `.svga` 资源统一改成项目命名风格:当前 `Home / Explore / Message / Me` 已分别使用 `sc_icon_home_anim.svga / sc_icon_explore_anim.svga / sc_icon_message_anim.svga / sc_icon_me_anim.svga`,并继续保留原有 png 作为失败兜底;后续新增本地动效资源默认也按 `sc_icon_*_anim` 规则命名。
- 已将语言房右侧 `game` 悬浮入口从 emoji 占位替换为本地动效资源,并按最新反馈移除外层圆形 `container`:当前直接使用 `sc_icon_room_game_entry_anim.svga` 本体作为入口展示,尺寸与原入口占位一致;若 SVGA 加载失败,会自动回退到项目原有 `sc_icon_botton_game.png` - 已将语言房右侧 `game` 悬浮入口从 emoji 占位替换为本地动效资源,并按最新反馈移除外层圆形 `container`:当前直接使用 `sc_icon_room_game_entry_anim.svga` 本体作为入口展示,尺寸与原入口占位一致;若 SVGA 加载失败,会自动回退到项目原有 `sc_icon_botton_game.png`
- 已完成语言房房间麦位声波资源替换并同步按项目规范重命名VIP3/4/5/6 麦位现分别改接 `sc_icon_room_vip3/4/5/6_sonic_anim.svga`,普通麦位新增区分“本人/他人”声波资源,默认分别使用 `sc_icon_room_self_sonic_anim.svga``sc_icon_room_other_sonic_anim.svga`;原有说话触发阈值、错峰扩散逻辑和旧 `webp`/纯色圆形 fallback 仍保留 - 已按最新反馈撤回语言房房间麦位声波的 SVGA 替换当前已恢复为项目原有展示VIP3/4/5/6 继续使用原先的 `webp` 声波资源,普通麦位回到原来的纯色圆形扩散效果;说话触发阈值与错峰扩散逻辑保持不变
- 已修复语言房顶部左侧房间榜单入口出现 `right overflowed` 的问题:当前贡献值区域已改为约束宽度 + 自适应缩放文本,不再因为积分位数变长把右箭头顶出容器。 - 已修复语言房顶部左侧房间榜单入口出现 `right overflowed` 的问题:当前贡献值区域已改为约束宽度 + 自适应缩放文本,不再因为积分位数变长把右箭头顶出容器。
- 已根据最新确认回退首页 Party 区的 `recommend_rank_top` 三个素材替换:财富榜、房间榜、魅力榜前三头像现已恢复为原先版本,不再在该位置叠加 `recommend_rank_top` 动态头像框;相关素材仍保留在工程内并已按规范重命名,后续待确认真正使用位置。 - 已根据最新确认回退首页 Party 区的 `recommend_rank_top` 三个素材替换:财富榜、房间榜、魅力榜前三头像现已恢复为原先版本,不再在该位置叠加 `recommend_rank_top` 动态头像框;相关素材仍保留在工程内并已按规范重命名,后续待确认真正使用位置。
- 已新增通用本地 SVGA 资源组件 `lib/ui_kit/widgets/svga/sc_svga_asset_widget.dart`,用于统一处理本地 assets 解码、内存缓存、单次播放/循环播放和失败兜底;本轮底部 tab、语言房 game 按钮和麦位声波均复用这套实现,避免后续重复写播放器逻辑 - 已新增通用本地 SVGA 资源组件 `lib/ui_kit/widgets/svga/sc_svga_asset_widget.dart`,用于统一处理本地 assets 解码、内存缓存、单次播放/循环播放和失败兜底;当前首页底部 tab 和语言房 game 按钮已复用这套实现,便于后续继续接入其它本地动效资源
- 已按最新语言房底部栏视觉需求完成 UI 重构:底部 5 个入口现改为同一水平基线布局,移除礼物按钮原先的中间悬浮 `Stack` 结构,并按最新顺序调整为“输入入口、礼物、麦克风、菜单、消息”;其中消息入口已移动到最右侧,且在麦克风隐藏时,礼物、菜单、消息 3 个入口会自动向右收拢成一组,避免中间留空。 - 已按最新语言房底部栏视觉需求完成 UI 重构:底部 5 个入口现改为同一水平基线布局,移除礼物按钮原先的中间悬浮 `Stack` 结构,并按最新顺序调整为“输入入口、礼物、麦克风、菜单、消息”;其中消息入口已移动到最右侧,且在麦克风隐藏时,礼物、菜单、消息 3 个入口会自动向右收拢成一组,避免中间留空。
- 已将语言房左侧输入入口改为独立组件:当前已撤回自绘聊天气泡图标方案,直接恢复使用项目原有的 `sc_images/room/icon_room_input_t.png` 资源作为左侧输入 icon并继续放入约三分之一屏宽的圆角矩形中右侧保留多语言问候文案 `roomBottomGreeting`,避免继续在自绘气泡尾巴形态上反复调整。 - 已将语言房左侧输入入口改为独立组件:当前已撤回自绘聊天气泡图标方案,直接恢复使用项目原有的 `sc_images/room/icon_room_input_t.png` 资源作为左侧输入 icon并继续放入约三分之一屏宽的圆角矩形中右侧保留多语言问候文案 `roomBottomGreeting`,避免继续在自绘气泡尾巴形态上反复调整。
- 已将语言房礼物入口拆成独立组件:礼物图标缩小后当前尺寸只略大于右侧 3 个圆按钮,并保留常驻轻量摇晃动画;根据最新反馈,当前已移除礼物按钮的粒子特效,并进一步去掉礼物入口外层 `container`,直接使用与原先按钮壳同尺寸的礼物图片本体作为可点击 UI避免额外底壳干扰观感。 - 已将语言房礼物入口拆成独立组件:礼物图标缩小后当前尺寸只略大于右侧 3 个圆按钮,并保留常驻轻量摇晃动画;根据最新反馈,当前已移除礼物按钮的粒子特效,并进一步去掉礼物入口外层 `container`,直接使用与原先按钮壳同尺寸的礼物图片本体作为可点击 UI避免额外底壳干扰观感。
@ -45,6 +46,7 @@
- 已通过 2026-04-16 真机最新控制台日志拿到更直接的证据H5 侧明确打印了 `{"msgId":"Connect","errCode":1015,"errMsg":"1015-SSToken接口错误","data":null}`,并伴随 `WebSocket Error / WebSocket Close / Reconnect` 日志,说明当前问题已可进一步收敛为百顺游戏在建立长连接前获取 `SSToken` 失败;这表明主要矛盾已经不在 Flutter 容器桥接,而在百顺服务端访问商户侧 `/v1/api/get_sstoken` 的链路、该接口的业务结果、或其返回格式与文档约定不一致。 - 已通过 2026-04-16 真机最新控制台日志拿到更直接的证据H5 侧明确打印了 `{"msgId":"Connect","errCode":1015,"errMsg":"1015-SSToken接口错误","data":null}`,并伴随 `WebSocket Error / WebSocket Close / Reconnect` 日志,说明当前问题已可进一步收敛为百顺游戏在建立长连接前获取 `SSToken` 失败;这表明主要矛盾已经不在 Flutter 容器桥接,而在百顺服务端访问商户侧 `/v1/api/get_sstoken` 的链路、该接口的业务结果、或其返回格式与文档约定不一致。
- 已继续定位“游戏画面可见但触摸无响应”的 Flutter 侧原因:当前实现此前一直把 BAISHUN H5 页面放在 `showGeneralDialog` 底部弹层里承载,同时 App 启动时又全局锁定了 `portraitUp`;对于 `FishingStar` 这类横版游戏,这会叠加出“平台视图在弹层容器中的命中不稳定 + iPhone 端实际不支持横屏”的问题。现已改为用独立路由承载 BAISHUN 游戏页,并按启动数据里的 `orientation` 动态切换横竖屏;同时 iOS `Info.plist` 已补充手机横屏支持,便于继续验证横版游戏触摸是否恢复。上述调整仍属于本轮 BAISHUN 联调修正,不是新的正式产品交互。 - 已继续定位“游戏画面可见但触摸无响应”的 Flutter 侧原因:当前实现此前一直把 BAISHUN H5 页面放在 `showGeneralDialog` 底部弹层里承载,同时 App 启动时又全局锁定了 `portraitUp`;对于 `FishingStar` 这类横版游戏,这会叠加出“平台视图在弹层容器中的命中不稳定 + iPhone 端实际不支持横屏”的问题。现已改为用独立路由承载 BAISHUN 游戏页,并按启动数据里的 `orientation` 动态切换横竖屏;同时 iOS `Info.plist` 已补充手机横屏支持,便于继续验证横版游戏触摸是否恢复。上述调整仍属于本轮 BAISHUN 联调修正,不是新的正式产品交互。
- 已按最新联调反馈回撤 BAISHUN 页面的横屏适配:当前后续不会接横屏游戏,因此已去掉游戏页按 `orientation` 切换系统方向的逻辑,并撤回 iPhone 端新增的横屏声明,只保留独立路由承载 BAISHUN 页面这一项 Flutter 容器修正。同时已继续增强临时调试,在 H5 侧补充 `pointerdown / touchstart / mousedown / click` 事件回传,用于下一轮真机直接确认“点击是否真的进到了 H5 DOM”这些输入事件日志依旧只用于本轮排障后续需要删除。 - 已按最新联调反馈回撤 BAISHUN 页面的横屏适配:当前后续不会接横屏游戏,因此已去掉游戏页按 `orientation` 切换系统方向的逻辑,并撤回 iPhone 端新增的横屏声明,只保留独立路由承载 BAISHUN 页面这一项 Flutter 容器修正。同时已继续增强临时调试,在 H5 侧补充 `pointerdown / touchstart / mousedown / click` 事件回传,用于下一轮真机直接确认“点击是否真的进到了 H5 DOM”这些输入事件日志依旧只用于本轮排障后续需要删除。
- 已按当前收尾需求移除 BAISHUN 临时调试面板:`BS DEBUG` 组件和相关调试入口现已从游戏页删除,不再作为正式功能保留;同时游戏页展示高度已恢复为接近半屏的底部弹出样式,回到最初设定的房内游戏视觉形态。
- 创建并持续维护进度跟踪文件。 - 创建并持续维护进度跟踪文件。
- 已继续排查语言房 gift 动画链路:确认送礼后会同时走本地房间消息、滚屏礼物条和大额礼物全局飘屏三条路径,并修复动画管理器在控制器尚未绑定完成时提前消费队列导致后续动画不再播放的问题。 - 已继续排查语言房 gift 动画链路:确认送礼后会同时走本地房间消息、滚屏礼物条和大额礼物全局飘屏三条路径,并修复动画管理器在控制器尚未绑定完成时提前消费队列导致后续动画不再播放的问题。
- 已定位并修复语言房礼物飘屏资源引用错误:代码里误写 `sc_icon_gift_flosc_bg` / `sc_icon_luck_gift_flosc_*`,实际资源文件名为 `float`,导致点击送礼后飘屏背景图加载失败,相关动画无法正常显示。 - 已定位并修复语言房礼物飘屏资源引用错误:代码里误写 `sc_icon_gift_flosc_bg` / `sc_icon_luck_gift_flosc_*`,实际资源文件名为 `float`,导致点击送礼后飘屏背景图加载失败,相关动画无法正常显示。