只做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(
BaishunBridgeConfigModel config, {
String? jsCallbackPath,
Map<String, dynamic> viewportPayload = const <String, dynamic>{},
}) {
final payload = jsonEncode(
_mergeViewportPayload(config.toJson(), viewportPayload),
);
final payload = jsonEncode(config.toJson());
final encodedCallbackPath = jsonEncode(jsCallbackPath?.trim() ?? '');
return '''
(function() {
@ -440,15 +437,8 @@ 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) {
@ -465,7 +455,6 @@ 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
});
}
@ -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}) {
final safePayload = jsonEncode(
payload ??

View File

@ -172,14 +172,6 @@ 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;
@ -187,21 +179,14 @@ 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;
@ -222,8 +207,7 @@ class LeaderJsBridge {
? String(launchConfig.code).slice(0, 6) + '***'
: '',
codeLength: launchConfig.code ? String(launchConfig.code).length : 0,
entryUrl: entry.entryUrl || '',
viewport: viewport
entryUrl: entry.entryUrl || ''
});
}
}

View File

@ -1,3 +1,5 @@
import 'dart:convert';
import 'package:flutter/material.dart';
const double roomGameDesignWidth = 750;
@ -62,79 +64,74 @@ String resolveRoomGameScreenMode(
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,
String buildRoomGameNativeFitScript({
required double visibleHeight,
required double expectedHeight,
}) {
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);
final options = jsonEncode(<String, dynamic>{
'visibleHeight': _roundMetric(visibleHeight),
'expectedHeight': _roundMetric(expectedHeight),
});
return '''
(function() {
const options = $options;
const fitKey = '__roomGameNativeViewportFit';
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,
};
}
function applyFit() {
try {
const visibleHeight = Number(options.visibleHeight) || window.innerHeight || 0;
const expectedHeight = Number(options.expectedHeight) || 0;
if (!document.body || !visibleHeight || !expectedHeight) {
return;
}
Map<String, dynamic> mergeRoomGameViewportPayload(
Map<String, dynamic> payload,
Map<String, dynamic> viewport,
) {
if (viewport.isEmpty) {
return payload;
}
const rawScale = visibleHeight / expectedHeight;
const scale = Math.min(1, rawScale);
window[fitKey] = {
scale: scale,
visibleHeight: visibleHeight,
expectedHeight: expectedHeight,
compact: scale < 0.999
};
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'],
};
if (scale >= 0.999) {
document.documentElement.style.overflow = '';
document.body.style.overflow = '';
document.body.style.transform = '';
document.body.style.transformOrigin = '';
document.body.style.width = '';
document.body.style.minHeight = '';
document.documentElement.removeAttribute('data-room-game-native-fit');
return;
}
document.documentElement.setAttribute('data-room-game-native-fit', 'true');
document.documentElement.style.overflow = 'hidden';
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) {

View File

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

View File

@ -215,10 +215,9 @@ class _LeaderGamePageState extends State<LeaderGamePage> {
}
try {
await _controller.runJavaScript(
LeaderJsBridge.bootstrapScript(
launchPayload: _buildLaunchPayload(viewport: _buildViewportPayload()),
),
LeaderJsBridge.bootstrapScript(launchPayload: _buildLaunchPayload()),
);
await _applyNativeViewportFit(reason: reason);
} catch (error) {
_log(
'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) {
final mediaQuery = MediaQuery.maybeOf(context);
final screenSize = mediaQuery?.size ?? Size.zero;
final screenMode = _resolveScreenMode();
final safeHeight = _resolveSafeHeight();
final fallback = screenSize.width;
final maxDialogHeight = resolveRoomGameAvailableHeight(
context,
reservedTop: 24.w,
@ -365,19 +360,58 @@ class _LeaderGamePageState extends State<LeaderGamePage> {
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) {
final ratio = roomGameDesignWidth / safeHeight;
final bodyHeight = screenSize.width / ratio;
return clampRoomGameHeight(bodyHeight, maxBodyHeight);
return screenSize.width / ratio;
}
if (screenMode == 'hd') {
final bodyHeight =
screenSize.width * _hdDesignHeight / roomGameDesignWidth;
return clampRoomGameHeight(bodyHeight, maxBodyHeight);
return screenSize.width * _hdDesignHeight / roomGameDesignWidth;
}
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) {
@ -440,20 +474,14 @@ class _LeaderGamePageState extends State<LeaderGamePage> {
}
}
Map<String, dynamic> _buildLaunchPayload({
Map<String, dynamic> viewport = const <String, dynamic>{},
}) {
final launchConfig = mergeRoomGameViewportPayload(
widget.launchModel.launchConfig.toJson(),
viewport,
);
final payload = <String, dynamic>{
Map<String, dynamic> _buildLaunchPayload() {
return <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,
'launchConfig': widget.launchModel.launchConfig.toJson(),
'roomState': widget.launchModel.roomState.toJson(),
'game': <String, dynamic>{
'gameId': widget.game.gameId,
@ -463,25 +491,6 @@ class _LeaderGamePageState extends State<LeaderGamePage> {
'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() {