From 2857047df2d24b6f24367fd48de8bba9954e4f8a Mon Sep 17 00:00:00 2001 From: Booker Date: Tue, 26 May 2026 20:43:50 +0700 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=A7=BB=E9=99=A4=20H5ShellPage=20?= =?UTF-8?q?=E4=B8=AD=E7=9A=84=E5=A4=9A=E4=BD=99=E5=AA=92=E4=BD=93=E6=8E=A7?= =?UTF-8?q?=E5=88=B6=E9=80=BB=E8=BE=91=EF=BC=9B=E4=BC=98=E5=8C=96=20ShellB?= =?UTF-8?q?randing=20=E5=8A=A0=E8=BD=BD=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/config/app_config.dart | 8 -- lib/main.dart | 163 +++++++------------------------------ 2 files changed, 28 insertions(+), 143 deletions(-) diff --git a/lib/config/app_config.dart b/lib/config/app_config.dart index a7ffad8..8d831a4 100644 --- a/lib/config/app_config.dart +++ b/lib/config/app_config.dart @@ -45,14 +45,6 @@ class AppConfig { .toString(); } - static String withFreshShellCacheBust(String url) { - final uri = Uri.parse(url); - final queryParameters = Map.from(uri.queryParameters) - ..['shell_cache_bust'] = DateTime.now().millisecondsSinceEpoch.toString(); - - return uri.replace(queryParameters: queryParameters).toString(); - } - static String _normalizeHomeUrl(String host) { final value = host.trim(); final normalized = diff --git a/lib/main.dart b/lib/main.dart index e5da3a0..711bf58 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -11,61 +11,8 @@ import 'config/app_config.dart'; const _shellBackground = Color(0xFFF8FBFF); const _shellAccent = Color(0xFF0089FF); const _shellSubText = Color(0xFF8E9AB0); -const _resumeCoverDuration = Duration(milliseconds: 700); const _shellBrandingChannel = MethodChannel('io.openim.flutter.im_webview_app/shell_branding'); -const _inspectH5SnapshotScript = r''' -(() => { - const toAbsoluteUrl = (value) => { - try { - return new URL(value, window.location.href).href; - } catch (_) { - return value || ''; - } - }; - - const shrinkAssetUrl = (value) => { - const absolute = toAbsoluteUrl(value); - const match = absolute.match(/\/assets\/[^?#]+/); - return match ? match[0] : absolute; - }; - - const scripts = Array.from(document.scripts) - .map((script) => script.src) - .filter(Boolean) - .map(shrinkAssetUrl); - const links = Array.from(document.querySelectorAll('link[href]')) - .map((link) => link.href) - .filter(Boolean) - .map(shrinkAssetUrl); - const bodyText = (document.body?.innerText || '') - .replace(/\s+/g, ' ') - .slice(0, 300); - - return JSON.stringify({ - type: 'h5Snapshot', - href: window.location.href, - title: document.title, - scripts, - links, - bodyText, - userAgent: navigator.userAgent, - }); -})(); -'''; -const _stopWebMediaScript = r''' -(() => { - try { - window.__stopOpenIMVoicePlayback?.(); - } catch (_) {} - document.querySelectorAll('audio, video').forEach((media) => { - try { - media.pause(); - media.currentTime = 0; - } catch (_) {} - }); -})(); -'''; Future main() async { WidgetsFlutterBinding.ensureInitialized(); @@ -77,8 +24,7 @@ Future main() async { systemNavigationBarIconBrightness: Brightness.dark, ), ); - final shellBranding = await ShellBranding.load(); - runApp(ImWebViewApp(shellBranding: shellBranding)); + runApp(const ImWebViewApp()); } class ShellBranding { @@ -132,55 +78,37 @@ class ImWebViewApp extends StatelessWidget { scaffoldBackgroundColor: _shellBackground, useMaterial3: true, ), - home: H5ShellPage(shellBranding: shellBranding), + home: H5ShellPage(initialShellBranding: shellBranding), ); } } class H5ShellPage extends StatefulWidget { - const H5ShellPage({super.key, required this.shellBranding}); + const H5ShellPage({super.key, required this.initialShellBranding}); - final ShellBranding shellBranding; + final ShellBranding initialShellBranding; @override State createState() => _H5ShellPageState(); } -class _H5ShellPageState extends State with WidgetsBindingObserver { +class _H5ShellPageState extends State { late final WebViewController _controller; int _progress = 0; String? _loadError; bool _showShellCover = true; - Timer? _shellCoverTimer; + bool _shellBrandingLoaded = false; + late ShellBranding _shellBranding; @override void initState() { super.initState(); - WidgetsBinding.instance.addObserver(this); + _shellBranding = widget.initialShellBranding; _controller = _buildController(); unawaited(_loadHome()); } - @override - void dispose() { - _shellCoverTimer?.cancel(); - WidgetsBinding.instance.removeObserver(this); - super.dispose(); - } - - @override - void didChangeAppLifecycleState(AppLifecycleState state) { - if (state == AppLifecycleState.inactive || - state == AppLifecycleState.hidden || - state == AppLifecycleState.paused || - state == AppLifecycleState.detached) { - unawaited(_stopWebMedia()); - } else if (state == AppLifecycleState.resumed) { - _showTransientShellCover(); - } - } - WebViewController _buildController() { return WebViewController() ..setJavaScriptMode(JavaScriptMode.unrestricted) @@ -214,26 +142,11 @@ class _H5ShellPageState extends State with WidgetsBindingObserver { } } }, - onUrlChange: (_) { - unawaited(_stopWebMedia()); - }, onNavigationRequest: _handleNavigationRequest, ), ); } - void _showTransientShellCover() { - _shellCoverTimer?.cancel(); - if (mounted) { - setState(() => _showShellCover = true); - } - _shellCoverTimer = Timer(_resumeCoverDuration, () { - if (mounted) { - setState(() => _showShellCover = _progress < 100); - } - }); - } - Future _runJavaScriptSafely(String source) async { try { await _controller.runJavaScript(source); @@ -247,8 +160,8 @@ class _H5ShellPageState extends State with WidgetsBindingObserver { } Future _handlePageFinished() async { + await _loadShellBrandingIfNeeded(); await _syncShellBranding(); - unawaited(_logLoadedH5Snapshot()); if (mounted) { setState(() { _progress = 100; @@ -257,10 +170,27 @@ class _H5ShellPageState extends State with WidgetsBindingObserver { } } + Future _loadShellBrandingIfNeeded() async { + if (_shellBrandingLoaded) { + return; + } + + final shellBranding = await ShellBranding.load(); + _shellBrandingLoaded = true; + if (!mounted) { + _shellBranding = shellBranding; + return; + } + + setState(() { + _shellBranding = shellBranding; + }); + } + Future _syncShellBranding() { final payload = jsonEncode({ - 'name': widget.shellBranding.appName, - 'logo': widget.shellBranding.appLogo, + 'name': _shellBranding.appName, + 'logo': _shellBranding.appLogo, }); final script = ''' (() => { @@ -274,40 +204,6 @@ class _H5ShellPageState extends State with WidgetsBindingObserver { return _runJavaScriptSafely(script); } - Future _stopWebMedia() { - return _runJavaScriptSafely(_stopWebMediaScript); - } - - Future _logLoadedH5Snapshot() async { - try { - final result = await _controller.runJavaScriptReturningResult( - _inspectH5SnapshotScript, - ); - final snapshot = _decodeJavaScriptStringResult(result); - debugPrint( - '[H5Shell] loaded H5 snapshot: $snapshot', - ); - } catch (error) { - debugPrint('[H5Shell] loaded H5 snapshot failed: $error'); - } - } - - String _decodeJavaScriptStringResult(Object? result) { - if (result == null) { - return ''; - } - if (result is String) { - try { - final decoded = jsonDecode(result); - if (decoded is String) { - return decoded; - } - } catch (_) {} - return result; - } - return result.toString(); - } - Future _loadHome() async { await _loadUrl(_freshHomeUrl()); } @@ -327,8 +223,6 @@ class _H5ShellPageState extends State with WidgetsBindingObserver { Future _handleNavigationRequest( NavigationRequest request, ) async { - unawaited(_stopWebMedia()); - final uri = Uri.tryParse(request.url); if (uri == null) { return NavigationDecision.prevent; @@ -349,7 +243,6 @@ class _H5ShellPageState extends State with WidgetsBindingObserver { } Future _handleBackNavigation() async { - await _stopWebMedia(); if (await _controller.canGoBack()) { await _controller.goBack(); } else {