diff --git a/lib/main.dart b/lib/main.dart index 2ec5216..180daa8 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -16,6 +16,8 @@ const _shellBackground = Color(0xFFF8FBFF); const _shellAccent = Color(0xFF0089FF); const _shellSubText = Color(0xFF8E9AB0); const _resumeCoverDuration = Duration(milliseconds: 700); +const _urlOnlyShellMode = true; +const _urlOnlyWebViewUrl = 'https://h5-im.imharry.work/'; const _noCacheHeaders = { 'Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate', 'Pragma': 'no-cache', @@ -263,6 +265,10 @@ Future main() async { systemNavigationBarIconBrightness: Brightness.dark, ), ); + if (_urlOnlyShellMode) { + runApp(const UrlOnlyWebViewApp()); + return; + } final shellBranding = await ShellBranding.load(); runApp(ImWebViewApp(shellBranding: shellBranding)); } @@ -300,6 +306,121 @@ class ShellBranding { static String _trim(String? value) => value?.trim() ?? ''; } +class UrlOnlyWebViewApp extends StatelessWidget { + const UrlOnlyWebViewApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: _urlOnlyWebViewUrl, + debugShowCheckedModeBanner: false, + theme: ThemeData(scaffoldBackgroundColor: Colors.white), + home: const UrlOnlyWebViewPage(), + ); + } +} + +class UrlOnlyWebViewPage extends StatefulWidget { + const UrlOnlyWebViewPage({super.key}); + + @override + State createState() => _UrlOnlyWebViewPageState(); +} + +class _UrlOnlyWebViewPageState extends State { + late final WebViewController _controller; + + int _progress = 0; + String? _loadError; + + @override + void initState() { + super.initState(); + _controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setBackgroundColor(Colors.white) + ..setNavigationDelegate( + NavigationDelegate( + onProgress: (progress) { + if (mounted) { + setState(() => _progress = progress); + } + }, + onPageStarted: (_) { + if (mounted) { + setState(() { + _loadError = null; + _progress = 0; + }); + } + }, + onPageFinished: (url) { + debugPrint('[UrlOnlyWebView] finished: $url'); + if (mounted) { + setState(() => _progress = 100); + } + }, + onWebResourceError: (error) { + if (error.isForMainFrame ?? true) { + if (mounted) { + setState(() => _loadError = error.description); + } + } + }, + onNavigationRequest: _handleUrlOnlyNavigationRequest, + ), + ) + ..loadRequest(Uri.parse(_urlOnlyWebViewUrl)); + } + + Future _handleUrlOnlyNavigationRequest( + NavigationRequest request, + ) async { + final uri = Uri.tryParse(request.url); + if (uri == null) { + return NavigationDecision.prevent; + } + + const webSchemes = {'http', 'https', 'about', 'data'}; + if (webSchemes.contains(uri.scheme)) { + return NavigationDecision.navigate; + } + + try { + await launchUrl(uri, mode: LaunchMode.externalApplication); + } catch (_) {} + return NavigationDecision.prevent; + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + body: SafeArea( + bottom: false, + child: Stack( + children: [ + WebViewWidget(controller: _controller), + if (_progress < 100) + LinearProgressIndicator( + value: _progress == 0 ? null : _progress / 100, + minHeight: 2, + ), + if (_loadError != null) + _ErrorPanel( + message: _loadError!, + onRetry: () { + setState(() => _loadError = null); + _controller.loadRequest(Uri.parse(_urlOnlyWebViewUrl)); + }, + ), + ], + ), + ), + ); + } +} + class ImWebViewApp extends StatelessWidget { const ImWebViewApp({ super.key,