只做flutter侧兜底

This commit is contained in:
roxy 2026-04-27 14:11:26 +08:00
parent 83fb6465f9
commit 4cf895b11e
5 changed files with 154 additions and 192 deletions

View File

@ -388,11 +388,8 @@ class BaishunJsBridge {
static String buildConfigScript( static String buildConfigScript(
BaishunBridgeConfigModel config, { BaishunBridgeConfigModel config, {
String? jsCallbackPath, String? jsCallbackPath,
Map<String, dynamic> viewportPayload = const <String, dynamic>{},
}) { }) {
final payload = jsonEncode( final payload = jsonEncode(config.toJson());
_mergeViewportPayload(config.toJson(), viewportPayload),
);
final encodedCallbackPath = jsonEncode(jsCallbackPath?.trim() ?? ''); final encodedCallbackPath = jsonEncode(jsCallbackPath?.trim() ?? '');
return ''' return '''
(function() { (function() {
@ -440,15 +437,8 @@ class BaishunJsBridge {
} }
window.__baishunLastConfig = config; window.__baishunLastConfig = config;
window.baishunBridgeConfig = config; window.baishunBridgeConfig = config;
const viewport = config.viewport || config.nativeViewport || {};
window.__baishunViewport = viewport;
window.baishunViewport = viewport;
window.NativeBridge = window.NativeBridge || {}; window.NativeBridge = window.NativeBridge || {};
window.NativeBridge.config = config; window.NativeBridge.config = config;
window.NativeBridge.viewport = viewport;
window.NativeBridge.getViewport = function() {
return viewport;
};
const callbackPath = explicitCallbackPath || window.__baishunLastJsCallback || ''; const callbackPath = explicitCallbackPath || window.__baishunLastJsCallback || '';
let callbackInvoked = false; let callbackInvoked = false;
if (explicitCallbackPath) { if (explicitCallbackPath) {
@ -465,7 +455,6 @@ class BaishunJsBridge {
gsp: config.gsp, gsp: config.gsp,
code: maskText(config.code), code: maskText(config.code),
codeLength: config.code ? String(config.code).length : 0, codeLength: config.code ? String(config.code).length : 0,
viewport: config.viewport || config.nativeViewport || null,
callbackPath: callbackPath callbackPath: callbackPath
}); });
} }
@ -497,30 +486,6 @@ 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}) { static String buildWalletUpdateScript({Map<String, dynamic>? payload}) {
final safePayload = jsonEncode( final safePayload = jsonEncode(
payload ?? payload ??

View File

@ -172,14 +172,6 @@ class LeaderJsBridge {
normalizedPayload.entry && typeof normalizedPayload.entry === 'object' normalizedPayload.entry && typeof normalizedPayload.entry === 'object'
? normalizedPayload.entry ? normalizedPayload.entry
: {}; : {};
const viewport =
normalizedPayload.viewport && typeof normalizedPayload.viewport === 'object'
? normalizedPayload.viewport
: (
launchConfig.viewport && typeof launchConfig.viewport === 'object'
? launchConfig.viewport
: {}
);
window.__leaderLaunchPayload = normalizedPayload; window.__leaderLaunchPayload = normalizedPayload;
window.__leaderLaunchPayloadText = payloadText; window.__leaderLaunchPayloadText = payloadText;
window.leaderLaunchPayload = normalizedPayload; window.leaderLaunchPayload = normalizedPayload;
@ -187,21 +179,14 @@ class LeaderJsBridge {
window.leaderLaunchConfig = launchConfig; window.leaderLaunchConfig = launchConfig;
window.__leaderLaunchEntry = entry; window.__leaderLaunchEntry = entry;
window.leaderLaunchEntry = entry; window.leaderLaunchEntry = entry;
window.__leaderViewport = viewport;
window.leaderViewport = viewport;
window.NativeBridge = window.NativeBridge || {}; window.NativeBridge = window.NativeBridge || {};
window.NativeBridge.config = launchConfig; window.NativeBridge.config = launchConfig;
window.NativeBridge.launchConfig = launchConfig; window.NativeBridge.launchConfig = launchConfig;
window.NativeBridge.entry = entry; window.NativeBridge.entry = entry;
window.NativeBridge.viewport = viewport;
window.NativeBridge.getConfig = function(callback) { window.NativeBridge.getConfig = function(callback) {
invokeCallback(callback, launchConfig); invokeCallback(callback, launchConfig);
return launchConfig; return launchConfig;
}; };
window.NativeBridge.getViewport = function(callback) {
invokeCallback(callback, viewport);
return viewport;
};
window.NativeBridge.getLaunchPayload = function(callback) { window.NativeBridge.getLaunchPayload = function(callback) {
invokeCallback(callback, normalizedPayload); invokeCallback(callback, normalizedPayload);
return normalizedPayload; return normalizedPayload;
@ -222,8 +207,7 @@ class LeaderJsBridge {
? String(launchConfig.code).slice(0, 6) + '***' ? String(launchConfig.code).slice(0, 6) + '***'
: '', : '',
codeLength: launchConfig.code ? String(launchConfig.code).length : 0, codeLength: launchConfig.code ? String(launchConfig.code).length : 0,
entryUrl: entry.entryUrl || '', entryUrl: entry.entryUrl || ''
viewport: viewport
}); });
} }
} }

View File

@ -1,3 +1,5 @@
import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
const double roomGameDesignWidth = 750; const double roomGameDesignWidth = 750;
@ -62,79 +64,74 @@ String resolveRoomGameScreenMode(
return fullScreen ? 'full' : 'half'; return fullScreen ? 'full' : 'half';
} }
Map<String, dynamic> buildRoomGameViewportPayload( String buildRoomGameNativeFitScript({
BuildContext context, { required double visibleHeight,
required double containerHeight, required double expectedHeight,
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 options = jsonEncode(<String, dynamic>{
final size = mediaQuery?.size ?? Size.zero; 'visibleHeight': _roundMetric(visibleHeight),
final padding = mediaQuery?.padding ?? EdgeInsets.zero; 'expectedHeight': _roundMetric(expectedHeight),
final viewInsets = mediaQuery?.viewInsets ?? EdgeInsets.zero; });
final targetDesignHeight = return '''
designHeight ?? (safeHeight > 0 ? safeHeight.toDouble() : null); (function() {
final expectedHeight = const options = $options;
targetDesignHeight != null && size.width > 0 && designWidth > 0 const fitKey = '__roomGameNativeViewportFit';
? size.width * targetDesignHeight / designWidth
: null;
final availableHeight = resolveRoomGameAvailableHeight(context);
return <String, dynamic>{ function applyFit() {
'containerWidth': _roundMetric(size.width), try {
'containerHeight': _roundMetric(containerHeight), const visibleHeight = Number(options.visibleHeight) || window.innerHeight || 0;
'visibleHeight': _roundMetric(containerHeight), const expectedHeight = Number(options.expectedHeight) || 0;
'webViewHeight': _roundMetric(actualWebViewHeight ?? containerHeight), if (!document.body || !visibleHeight || !expectedHeight) {
'dialogHeight': _roundMetric( return;
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( const rawScale = visibleHeight / expectedHeight;
Map<String, dynamic> payload, const scale = Math.min(1, rawScale);
Map<String, dynamic> viewport, window[fitKey] = {
) { scale: scale,
if (viewport.isEmpty) { visibleHeight: visibleHeight,
return payload; expectedHeight: expectedHeight,
} compact: scale < 0.999
};
return <String, dynamic>{ if (scale >= 0.999) {
...payload, document.documentElement.style.overflow = '';
'viewport': viewport, document.body.style.overflow = '';
'nativeViewport': viewport, document.body.style.transform = '';
'containerWidth': viewport['containerWidth'], document.body.style.transformOrigin = '';
'containerHeight': viewport['containerHeight'], document.body.style.width = '';
'visibleHeight': viewport['visibleHeight'], document.body.style.minHeight = '';
'webViewHeight': viewport['webViewHeight'], document.documentElement.removeAttribute('data-room-game-native-fit');
'dialogHeight': viewport['dialogHeight'], return;
'safeAreaTop': viewport['safeAreaTop'], }
'safeAreaBottom': viewport['safeAreaBottom'],
'devicePixelRatio': viewport['devicePixelRatio'], document.documentElement.setAttribute('data-room-game-native-fit', 'true');
'screenMode': viewport['screenMode'], document.documentElement.style.overflow = 'hidden';
'isCompactViewport': viewport['isCompactViewport'], document.body.style.overflow = 'hidden';
}; document.body.style.transform = 'scale(' + scale + ')';
document.body.style.transformOrigin = 'top left';
document.body.style.width = (100 / scale) + '%';
document.body.style.minHeight = expectedHeight + 'px';
} catch (_) {}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', applyFit, { once: true });
} else {
applyFit();
}
[50, 250, 800, 1600, 3000].forEach(function(delay) {
window.setTimeout(applyFit, delay);
});
if (!window.__roomGameNativeFitResizeReady) {
window.__roomGameNativeFitResizeReady = true;
window.addEventListener('resize', applyFit, true);
window.addEventListener('orientationchange', function() {
window.setTimeout(applyFit, 120);
}, true);
}
})();
''';
} }
double _roundMetric(double value) { double _roundMetric(double value) {

View File

@ -105,6 +105,7 @@ class _BaishunGamePageState extends State<BaishunGamePage> {
_didFinishPageLoad = true; _didFinishPageLoad = true;
_log('page_finished url=${_clip(url, 240)}'); _log('page_finished url=${_clip(url, 240)}');
await _injectBridge(reason: 'page_finished'); await _injectBridge(reason: 'page_finished');
await _applyNativeViewportFit(reason: 'page_finished');
}, },
onWebResourceError: (WebResourceError error) { onWebResourceError: (WebResourceError error) {
_log( _log(
@ -260,19 +261,18 @@ class _BaishunGamePageState extends State<BaishunGamePage> {
_hasDeliveredLaunchConfig = true; _hasDeliveredLaunchConfig = true;
final rawConfig = widget.launchModel.bridgeConfig; final rawConfig = widget.launchModel.bridgeConfig;
final config = _buildEffectiveBridgeConfig(rawConfig); final config = _buildEffectiveBridgeConfig(rawConfig);
final viewport = _buildViewportPayload();
_log( _log(
'send_config jsCallback=${jsCallbackPath ?? ''} ' 'send_config jsCallback=${jsCallbackPath ?? ''} '
'force=$force config=${_stringifyForLog(<String, dynamic>{..._buildConfigSummary(config, rawConfig: rawConfig), 'viewport': viewport})}', 'force=$force config=${_stringifyForLog(_buildConfigSummary(config, rawConfig: rawConfig))}',
); );
try { try {
await _controller.runJavaScript( await _controller.runJavaScript(
BaishunJsBridge.buildConfigScript( BaishunJsBridge.buildConfigScript(
config, config,
jsCallbackPath: jsCallbackPath, jsCallbackPath: jsCallbackPath,
viewportPayload: viewport,
), ),
); );
await _applyNativeViewportFit(reason: 'send_config');
} catch (error) { } catch (error) {
_log('send_config_error error=${_clip(error.toString(), 300)}'); _log('send_config_error error=${_clip(error.toString(), 300)}');
} }
@ -330,6 +330,7 @@ class _BaishunGamePageState extends State<BaishunGamePage> {
_isLoading = false; _isLoading = false;
_errorMessage = null; _errorMessage = null;
}); });
await _applyNativeViewportFit(reason: 'game_loaded');
break; break;
case BaishunBridgeActions.gameRecharge: case BaishunBridgeActions.gameRecharge:
_log('game_recharge open_wallet'); _log('game_recharge open_wallet');
@ -611,45 +612,51 @@ class _BaishunGamePageState extends State<BaishunGamePage> {
return 0; return 0;
} }
String _resolveScreenMode() {
return resolveRoomGameScreenMode(
widget.game.launchParams,
fullScreen: widget.game.fullScreen,
);
}
double _calculateWebViewHeight(BuildContext context) { double _calculateWebViewHeight(BuildContext context) {
final mediaQuery = MediaQuery.maybeOf(context); final preferredHeight = _calculatePreferredWebViewHeight(context);
final screenSize = mediaQuery?.size ?? Size.zero;
final safeHeight = _resolveSafeHeight();
final fallbackHeight = screenSize.height * 0.5;
final maxHeight = resolveRoomGameAvailableHeight( final maxHeight = resolveRoomGameAvailableHeight(
context, context,
reservedTop: 12.w, reservedTop: 12.w,
); );
return clampRoomGameHeight(preferredHeight, maxHeight);
}
double _calculatePreferredWebViewHeight(BuildContext context) {
final mediaQuery = MediaQuery.maybeOf(context);
final screenSize = mediaQuery?.size ?? Size.zero;
final safeHeight = _resolveSafeHeight();
final fallbackHeight = screenSize.height * 0.5;
if (safeHeight <= 0 || screenSize.width <= 0) { if (safeHeight <= 0 || screenSize.width <= 0) {
return clampRoomGameHeight(fallbackHeight, maxHeight); return fallbackHeight;
} }
final ratio = roomGameDesignWidth / safeHeight; final ratio = roomGameDesignWidth / safeHeight;
final webViewHeight = screenSize.width / ratio; return screenSize.width / ratio;
return clampRoomGameHeight(webViewHeight, maxHeight);
} }
Map<String, dynamic> _buildViewportPayload() { Future<void> _applyNativeViewportFit({String reason = 'manual'}) async {
final topCrop = 28.w; final topCrop = 28.w;
final webViewHeight = _calculateWebViewHeight(context); final visibleHeight = _calculateWebViewHeight(context) + topCrop;
final safeHeight = _resolveSafeHeight(); final expectedHeight = _calculatePreferredWebViewHeight(context);
return buildRoomGameViewportPayload( if (expectedHeight <= visibleHeight + 1) {
context, return;
containerHeight: webViewHeight, }
dialogHeight: webViewHeight, _log(
topCrop: topCrop, 'native_fit reason=$reason visible=${visibleHeight.toStringAsFixed(1)} '
screenMode: _resolveScreenMode(), 'expected=${expectedHeight.toStringAsFixed(1)}',
safeHeight: safeHeight,
designHeight: safeHeight > 0 ? safeHeight.toDouble() : null,
actualWebViewHeight: webViewHeight + topCrop,
); );
try {
await _controller.runJavaScript(
buildRoomGameNativeFitScript(
visibleHeight: visibleHeight,
expectedHeight: expectedHeight,
),
);
} catch (error) {
_log(
'native_fit_error reason=$reason error=${_clip(error.toString(), 300)}',
);
}
} }
@override @override

View File

@ -215,10 +215,9 @@ class _LeaderGamePageState extends State<LeaderGamePage> {
} }
try { try {
await _controller.runJavaScript( await _controller.runJavaScript(
LeaderJsBridge.bootstrapScript( LeaderJsBridge.bootstrapScript(launchPayload: _buildLaunchPayload()),
launchPayload: _buildLaunchPayload(viewport: _buildViewportPayload()),
),
); );
await _applyNativeViewportFit(reason: reason);
} catch (error) { } catch (error) {
_log( _log(
'inject_bridge_error reason=$reason error=${_clip(error.toString(), 300)}', 'inject_bridge_error reason=$reason error=${_clip(error.toString(), 300)}',
@ -347,11 +346,7 @@ class _LeaderGamePageState extends State<LeaderGamePage> {
} }
double _calculateBodyHeight(BuildContext context, double headerHeight) { double _calculateBodyHeight(BuildContext context, double headerHeight) {
final mediaQuery = MediaQuery.maybeOf(context);
final screenSize = mediaQuery?.size ?? Size.zero;
final screenMode = _resolveScreenMode(); final screenMode = _resolveScreenMode();
final safeHeight = _resolveSafeHeight();
final fallback = screenSize.width;
final maxDialogHeight = resolveRoomGameAvailableHeight( final maxDialogHeight = resolveRoomGameAvailableHeight(
context, context,
reservedTop: 24.w, reservedTop: 24.w,
@ -365,19 +360,58 @@ class _LeaderGamePageState extends State<LeaderGamePage> {
return maxBodyHeight; return maxBodyHeight;
} }
return clampRoomGameHeight(
_calculatePreferredBodyHeight(context),
maxBodyHeight,
);
}
double _calculatePreferredBodyHeight(BuildContext context) {
final mediaQuery = MediaQuery.maybeOf(context);
final screenSize = mediaQuery?.size ?? Size.zero;
final screenMode = _resolveScreenMode();
final safeHeight = _resolveSafeHeight();
final fallback = screenSize.width;
if (safeHeight > 0 && screenSize.width > 0) { if (safeHeight > 0 && screenSize.width > 0) {
final ratio = roomGameDesignWidth / safeHeight; final ratio = roomGameDesignWidth / safeHeight;
final bodyHeight = screenSize.width / ratio; return screenSize.width / ratio;
return clampRoomGameHeight(bodyHeight, maxBodyHeight);
} }
if (screenMode == 'hd') { if (screenMode == 'hd') {
final bodyHeight = return screenSize.width * _hdDesignHeight / roomGameDesignWidth;
screenSize.width * _hdDesignHeight / roomGameDesignWidth;
return clampRoomGameHeight(bodyHeight, maxBodyHeight);
} }
return clampRoomGameHeight(fallback, maxBodyHeight); return fallback;
}
Future<void> _applyNativeViewportFit({String reason = 'manual'}) async {
final screenMode = _resolveScreenMode();
if (screenMode == 'full') {
return;
}
final headerHeight = 44.w;
final visibleHeight = _calculateBodyHeight(context, headerHeight);
final expectedHeight = _calculatePreferredBodyHeight(context);
if (expectedHeight <= visibleHeight + 1) {
return;
}
_log(
'native_fit reason=$reason visible=${visibleHeight.toStringAsFixed(1)} '
'expected=${expectedHeight.toStringAsFixed(1)}',
);
try {
await _controller.runJavaScript(
buildRoomGameNativeFitScript(
visibleHeight: visibleHeight,
expectedHeight: expectedHeight,
),
);
} catch (error) {
_log(
'native_fit_error reason=$reason error=${_clip(error.toString(), 300)}',
);
}
} }
void _log(String message) { void _log(String message) {
@ -440,20 +474,14 @@ class _LeaderGamePageState extends State<LeaderGamePage> {
} }
} }
Map<String, dynamic> _buildLaunchPayload({ Map<String, dynamic> _buildLaunchPayload() {
Map<String, dynamic> viewport = const <String, dynamic>{}, return <String, dynamic>{
}) {
final launchConfig = mergeRoomGameViewportPayload(
widget.launchModel.launchConfig.toJson(),
viewport,
);
final payload = <String, dynamic>{
'provider': widget.launchModel.provider, 'provider': widget.launchModel.provider,
'gameId': widget.launchModel.gameId, 'gameId': widget.launchModel.gameId,
'providerGameId': widget.launchModel.providerGameId, 'providerGameId': widget.launchModel.providerGameId,
'gameSessionId': widget.launchModel.gameSessionId, 'gameSessionId': widget.launchModel.gameSessionId,
'entry': widget.launchModel.entry.toJson(), 'entry': widget.launchModel.entry.toJson(),
'launchConfig': launchConfig, 'launchConfig': widget.launchModel.launchConfig.toJson(),
'roomState': widget.launchModel.roomState.toJson(), 'roomState': widget.launchModel.roomState.toJson(),
'game': <String, dynamic>{ 'game': <String, dynamic>{
'gameId': widget.game.gameId, 'gameId': widget.game.gameId,
@ -463,25 +491,6 @@ class _LeaderGamePageState extends State<LeaderGamePage> {
'launchMode': widget.game.launchMode, '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() { Map<String, dynamic> _buildLaunchPayloadForLog() {