- 全局字体统一(Outfit/DM Sans), 头部/按钮/Toast规范化 - 故事详情页: Genie Suck吸入动画(标题+卡片一起缩小模糊消失) - 书架页: bookPop弹出+粒子效果(三段式动画完整链路) - 音乐页面: 心情卡片emoji换Material图标+彩色圆块横排布局 - 音乐页面: 进度条胶囊宽度对齐, 播放按钮位置修复, 间距均匀化 - 音乐播放: 接入just_audio, 支持播放暂停进度拖拽自动切歌 - 新增: iOS风格毛玻璃Toast, 渐变背景组件, 通知页面 - 阶段总结文档更新 Co-authored-by: Cursor <cursoragent@cursor.com>
111 lines
4.1 KiB
Dart
111 lines
4.1 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:go_router/go_router.dart';
|
|
import 'package:webview_flutter/webview_flutter.dart';
|
|
import 'package:webview_flutter_android/webview_flutter_android.dart';
|
|
import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart';
|
|
|
|
class WebViewPage extends StatefulWidget {
|
|
const WebViewPage({super.key});
|
|
|
|
@override
|
|
State<WebViewPage> createState() => _WebViewPageState();
|
|
}
|
|
|
|
class _WebViewPageState extends State<WebViewPage> {
|
|
late final WebViewController _controller;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
|
|
late final PlatformWebViewControllerCreationParams params;
|
|
if (WebViewPlatform.instance is WebKitWebViewPlatform) {
|
|
params = WebKitWebViewControllerCreationParams(
|
|
allowsInlineMediaPlayback: true,
|
|
mediaTypesRequiringUserAction: const <PlaybackMediaTypes>{},
|
|
);
|
|
} else {
|
|
params = const PlatformWebViewControllerCreationParams();
|
|
}
|
|
|
|
final WebViewController controller =
|
|
WebViewController.fromPlatformCreationParams(params);
|
|
|
|
controller
|
|
..setJavaScriptMode(JavaScriptMode.unrestricted)
|
|
..setBackgroundColor(const Color(0x00000000))
|
|
..setNavigationDelegate(
|
|
NavigationDelegate(
|
|
onProgress: (int progress) {
|
|
debugPrint('WebView is loading (progress : $progress%)');
|
|
},
|
|
onPageStarted: (String url) {
|
|
debugPrint('Page started loading: $url');
|
|
},
|
|
onPageFinished: (String url) {
|
|
debugPrint('Page finished loading: $url');
|
|
},
|
|
onWebResourceError: (WebResourceError error) {
|
|
debugPrint('''
|
|
Page resource error:
|
|
code: ${error.errorCode}
|
|
description: ${error.description}
|
|
errorType: ${error.errorType}
|
|
isForMainFrame: ${error.isForMainFrame}
|
|
''');
|
|
},
|
|
onNavigationRequest: (NavigationRequest request) {
|
|
if (request.url.contains('bluetooth.html')) {
|
|
// Intercept bluetooth.html and navigate to native BluetoothPage
|
|
debugPrint(
|
|
'Intercepting navigation to bluetooth.html -> Native Route',
|
|
);
|
|
// We need context to navigate, but initState doesn't have it easily available
|
|
// inside this callback unless we store a reference or use a GlobalKey.
|
|
// However, since we are in a State object, we can use 'context' if mounted?
|
|
// Actually, NavigationDelegate callbacks are not bound to context directly.
|
|
// We should probably move the controller creation or use a helper.
|
|
// BUT, since this is a callback, 'context' of the State is available in the closure!
|
|
|
|
// Warning: don't use 'context' across async gaps without checking mounted.
|
|
// Since this is synchronous, it should be fine to schedule a navigation.
|
|
|
|
// We must return NavigationDecision.prevent to stop WebView.
|
|
// And execute navigation asynchronously to avoid blocking.
|
|
Future.microtask(() {
|
|
if (mounted) {
|
|
context.go('/bluetooth');
|
|
}
|
|
});
|
|
return NavigationDecision.prevent;
|
|
}
|
|
return NavigationDecision.navigate;
|
|
},
|
|
),
|
|
)
|
|
..loadFlutterAsset(
|
|
'assets/www/index.html',
|
|
); // CHANGED: Load Home directly
|
|
|
|
if (controller.platform is AndroidWebViewController) {
|
|
AndroidWebViewController.enableDebugging(true);
|
|
(controller.platform as AndroidWebViewController)
|
|
.setMediaPlaybackRequiresUserGesture(false);
|
|
}
|
|
|
|
_controller = controller;
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
// We want the WebView to control the full screen, including status bar usually,
|
|
// but SafeArea might be needed if the Web content doesn't handle padding.
|
|
// Our CSS handles env(safe-area-inset-top), so we can disable SafeArea here
|
|
// or keep top:false.
|
|
return Scaffold(
|
|
backgroundColor: Colors.white,
|
|
body: WebViewWidget(controller: _controller),
|
|
);
|
|
}
|
|
}
|