From 4cf895b11ec3936df9a678c211632c4bd46a650c Mon Sep 17 00:00:00 2001 From: roxy Date: Mon, 27 Apr 2026 14:11:26 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8F=AA=E5=81=9Aflutter=E4=BE=A7=E5=85=9C?= =?UTF-8?q?=E5=BA=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../room_game/bridge/baishun_js_bridge.dart | 37 +---- .../room_game/bridge/leader_js_bridge.dart | 18 +-- .../room_game/utils/room_game_viewport.dart | 135 +++++++++--------- .../room_game/views/baishun_game_page.dart | 65 +++++---- .../room_game/views/leader_game_page.dart | 91 ++++++------ 5 files changed, 154 insertions(+), 192 deletions(-) diff --git a/lib/modules/room_game/bridge/baishun_js_bridge.dart b/lib/modules/room_game/bridge/baishun_js_bridge.dart index e42efa7..ad15a0f 100644 --- a/lib/modules/room_game/bridge/baishun_js_bridge.dart +++ b/lib/modules/room_game/bridge/baishun_js_bridge.dart @@ -388,11 +388,8 @@ class BaishunJsBridge { static String buildConfigScript( BaishunBridgeConfigModel config, { String? jsCallbackPath, - Map viewportPayload = const {}, }) { - 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 _mergeViewportPayload( - Map payload, - Map viewport, - ) { - if (viewport.isEmpty) { - return payload; - } - return { - ...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? payload}) { final safePayload = jsonEncode( payload ?? diff --git a/lib/modules/room_game/bridge/leader_js_bridge.dart b/lib/modules/room_game/bridge/leader_js_bridge.dart index 943d518..23effc1 100644 --- a/lib/modules/room_game/bridge/leader_js_bridge.dart +++ b/lib/modules/room_game/bridge/leader_js_bridge.dart @@ -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 || '' }); } } diff --git a/lib/modules/room_game/utils/room_game_viewport.dart b/lib/modules/room_game/utils/room_game_viewport.dart index 73aa5e3..7f9f1d9 100644 --- a/lib/modules/room_game/utils/room_game_viewport.dart +++ b/lib/modules/room_game/utils/room_game_viewport.dart @@ -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 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({ + 'visibleHeight': _roundMetric(visibleHeight), + 'expectedHeight': _roundMetric(expectedHeight), + }); + return ''' + (function() { + const options = $options; + const fitKey = '__roomGameNativeViewportFit'; - return { - '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 mergeRoomGameViewportPayload( - Map payload, - Map 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 { - ...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) { diff --git a/lib/modules/room_game/views/baishun_game_page.dart b/lib/modules/room_game/views/baishun_game_page.dart index a26a401..909fc1f 100644 --- a/lib/modules/room_game/views/baishun_game_page.dart +++ b/lib/modules/room_game/views/baishun_game_page.dart @@ -105,6 +105,7 @@ class _BaishunGamePageState extends State { _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 { _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), '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 { _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 { 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 _buildViewportPayload() { + Future _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 diff --git a/lib/modules/room_game/views/leader_game_page.dart b/lib/modules/room_game/views/leader_game_page.dart index 48fab93..07f3027 100644 --- a/lib/modules/room_game/views/leader_game_page.dart +++ b/lib/modules/room_game/views/leader_game_page.dart @@ -215,10 +215,9 @@ class _LeaderGamePageState extends State { } 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 { } 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 { 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 _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 { } } - Map _buildLaunchPayload({ - Map viewport = const {}, - }) { - final launchConfig = mergeRoomGameViewportPayload( - widget.launchModel.launchConfig.toJson(), - viewport, - ); - final payload = { + Map _buildLaunchPayload() { + return { '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': { 'gameId': widget.game.gameId, @@ -463,25 +491,6 @@ class _LeaderGamePageState extends State { 'launchMode': widget.game.launchMode, }, }; - return mergeRoomGameViewportPayload(payload, viewport); - } - - Map _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 _buildLaunchPayloadForLog() {