feat: 移除 H5ShellPage 中的多余媒体控制逻辑;优化 ShellBranding 加载方式

This commit is contained in:
Booker
2026-05-26 20:43:50 +07:00
parent b8159e3e35
commit 2857047df2
2 changed files with 28 additions and 143 deletions

View File

@@ -45,14 +45,6 @@ class AppConfig {
.toString();
}
static String withFreshShellCacheBust(String url) {
final uri = Uri.parse(url);
final queryParameters = Map<String, String>.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 =

View File

@@ -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<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
@@ -77,8 +24,7 @@ Future<void> 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<H5ShellPage> createState() => _H5ShellPageState();
}
class _H5ShellPageState extends State<H5ShellPage> with WidgetsBindingObserver {
class _H5ShellPageState extends State<H5ShellPage> {
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<H5ShellPage> 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<void> _runJavaScriptSafely(String source) async {
try {
await _controller.runJavaScript(source);
@@ -247,8 +160,8 @@ class _H5ShellPageState extends State<H5ShellPage> with WidgetsBindingObserver {
}
Future<void> _handlePageFinished() async {
await _loadShellBrandingIfNeeded();
await _syncShellBranding();
unawaited(_logLoadedH5Snapshot());
if (mounted) {
setState(() {
_progress = 100;
@@ -257,10 +170,27 @@ class _H5ShellPageState extends State<H5ShellPage> with WidgetsBindingObserver {
}
}
Future<void> _loadShellBrandingIfNeeded() async {
if (_shellBrandingLoaded) {
return;
}
final shellBranding = await ShellBranding.load();
_shellBrandingLoaded = true;
if (!mounted) {
_shellBranding = shellBranding;
return;
}
setState(() {
_shellBranding = shellBranding;
});
}
Future<void> _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<H5ShellPage> with WidgetsBindingObserver {
return _runJavaScriptSafely(script);
}
Future<void> _stopWebMedia() {
return _runJavaScriptSafely(_stopWebMediaScript);
}
Future<void> _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<void> _loadHome() async {
await _loadUrl(_freshHomeUrl());
}
@@ -327,8 +223,6 @@ class _H5ShellPageState extends State<H5ShellPage> with WidgetsBindingObserver {
Future<NavigationDecision> _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<H5ShellPage> with WidgetsBindingObserver {
}
Future<void> _handleBackNavigation() async {
await _stopWebMedia();
if (await _controller.canGoBack()) {
await _controller.goBack();
} else {