feat: 移除 H5ShellPage 中的多余媒体控制逻辑;优化 ShellBranding 加载方式
This commit is contained in:
@@ -45,14 +45,6 @@ class AppConfig {
|
|||||||
.toString();
|
.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) {
|
static String _normalizeHomeUrl(String host) {
|
||||||
final value = host.trim();
|
final value = host.trim();
|
||||||
final normalized =
|
final normalized =
|
||||||
|
|||||||
163
lib/main.dart
163
lib/main.dart
@@ -11,61 +11,8 @@ import 'config/app_config.dart';
|
|||||||
const _shellBackground = Color(0xFFF8FBFF);
|
const _shellBackground = Color(0xFFF8FBFF);
|
||||||
const _shellAccent = Color(0xFF0089FF);
|
const _shellAccent = Color(0xFF0089FF);
|
||||||
const _shellSubText = Color(0xFF8E9AB0);
|
const _shellSubText = Color(0xFF8E9AB0);
|
||||||
const _resumeCoverDuration = Duration(milliseconds: 700);
|
|
||||||
const _shellBrandingChannel =
|
const _shellBrandingChannel =
|
||||||
MethodChannel('io.openim.flutter.im_webview_app/shell_branding');
|
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 {
|
Future<void> main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
@@ -77,8 +24,7 @@ Future<void> main() async {
|
|||||||
systemNavigationBarIconBrightness: Brightness.dark,
|
systemNavigationBarIconBrightness: Brightness.dark,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
final shellBranding = await ShellBranding.load();
|
runApp(const ImWebViewApp());
|
||||||
runApp(ImWebViewApp(shellBranding: shellBranding));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class ShellBranding {
|
class ShellBranding {
|
||||||
@@ -132,55 +78,37 @@ class ImWebViewApp extends StatelessWidget {
|
|||||||
scaffoldBackgroundColor: _shellBackground,
|
scaffoldBackgroundColor: _shellBackground,
|
||||||
useMaterial3: true,
|
useMaterial3: true,
|
||||||
),
|
),
|
||||||
home: H5ShellPage(shellBranding: shellBranding),
|
home: H5ShellPage(initialShellBranding: shellBranding),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class H5ShellPage extends StatefulWidget {
|
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
|
@override
|
||||||
State<H5ShellPage> createState() => _H5ShellPageState();
|
State<H5ShellPage> createState() => _H5ShellPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _H5ShellPageState extends State<H5ShellPage> with WidgetsBindingObserver {
|
class _H5ShellPageState extends State<H5ShellPage> {
|
||||||
late final WebViewController _controller;
|
late final WebViewController _controller;
|
||||||
|
|
||||||
int _progress = 0;
|
int _progress = 0;
|
||||||
String? _loadError;
|
String? _loadError;
|
||||||
bool _showShellCover = true;
|
bool _showShellCover = true;
|
||||||
Timer? _shellCoverTimer;
|
bool _shellBrandingLoaded = false;
|
||||||
|
late ShellBranding _shellBranding;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
WidgetsBinding.instance.addObserver(this);
|
_shellBranding = widget.initialShellBranding;
|
||||||
_controller = _buildController();
|
_controller = _buildController();
|
||||||
unawaited(_loadHome());
|
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() {
|
WebViewController _buildController() {
|
||||||
return WebViewController()
|
return WebViewController()
|
||||||
..setJavaScriptMode(JavaScriptMode.unrestricted)
|
..setJavaScriptMode(JavaScriptMode.unrestricted)
|
||||||
@@ -214,26 +142,11 @@ class _H5ShellPageState extends State<H5ShellPage> with WidgetsBindingObserver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onUrlChange: (_) {
|
|
||||||
unawaited(_stopWebMedia());
|
|
||||||
},
|
|
||||||
onNavigationRequest: _handleNavigationRequest,
|
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 {
|
Future<void> _runJavaScriptSafely(String source) async {
|
||||||
try {
|
try {
|
||||||
await _controller.runJavaScript(source);
|
await _controller.runJavaScript(source);
|
||||||
@@ -247,8 +160,8 @@ class _H5ShellPageState extends State<H5ShellPage> with WidgetsBindingObserver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _handlePageFinished() async {
|
Future<void> _handlePageFinished() async {
|
||||||
|
await _loadShellBrandingIfNeeded();
|
||||||
await _syncShellBranding();
|
await _syncShellBranding();
|
||||||
unawaited(_logLoadedH5Snapshot());
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_progress = 100;
|
_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() {
|
Future<void> _syncShellBranding() {
|
||||||
final payload = jsonEncode({
|
final payload = jsonEncode({
|
||||||
'name': widget.shellBranding.appName,
|
'name': _shellBranding.appName,
|
||||||
'logo': widget.shellBranding.appLogo,
|
'logo': _shellBranding.appLogo,
|
||||||
});
|
});
|
||||||
final script = '''
|
final script = '''
|
||||||
(() => {
|
(() => {
|
||||||
@@ -274,40 +204,6 @@ class _H5ShellPageState extends State<H5ShellPage> with WidgetsBindingObserver {
|
|||||||
return _runJavaScriptSafely(script);
|
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 {
|
Future<void> _loadHome() async {
|
||||||
await _loadUrl(_freshHomeUrl());
|
await _loadUrl(_freshHomeUrl());
|
||||||
}
|
}
|
||||||
@@ -327,8 +223,6 @@ class _H5ShellPageState extends State<H5ShellPage> with WidgetsBindingObserver {
|
|||||||
Future<NavigationDecision> _handleNavigationRequest(
|
Future<NavigationDecision> _handleNavigationRequest(
|
||||||
NavigationRequest request,
|
NavigationRequest request,
|
||||||
) async {
|
) async {
|
||||||
unawaited(_stopWebMedia());
|
|
||||||
|
|
||||||
final uri = Uri.tryParse(request.url);
|
final uri = Uri.tryParse(request.url);
|
||||||
if (uri == null) {
|
if (uri == null) {
|
||||||
return NavigationDecision.prevent;
|
return NavigationDecision.prevent;
|
||||||
@@ -349,7 +243,6 @@ class _H5ShellPageState extends State<H5ShellPage> with WidgetsBindingObserver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _handleBackNavigation() async {
|
Future<void> _handleBackNavigation() async {
|
||||||
await _stopWebMedia();
|
|
||||||
if (await _controller.canGoBack()) {
|
if (await _controller.canGoBack()) {
|
||||||
await _controller.goBack();
|
await _controller.goBack();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user