import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:webview_flutter/webview_flutter.dart'; import 'package:im_app/app/di/network_provider.dart'; import 'package:im_app/core/foundation/config.dart'; /// 小程序 WebView 页 /// /// 对应 Gitea issue #25 / iOS ChatRoomMiniAppFloatButton → WebView /// /// ## URL 构造 /// /// `{apiBaseUrl}/miniapp/{appId}/index.html?gameId={gameId}&token={token}&chatId={chatId}&chatType={chatType}` /// /// ## 使用 /// /// ```dart /// MiniAppRouter.open( /// context, ref, /// gameId: state.gameId, /// appId: state.appId, /// chatId: chatId, /// chatType: chatType, /// ); /// ``` class MiniAppWebViewPage extends ConsumerStatefulWidget { const MiniAppWebViewPage({ super.key, required this.gameId, required this.appId, required this.chatId, required this.chatType, this.title = '游戏', }); final String gameId; final String appId; final int chatId; final int chatType; final String title; @override ConsumerState createState() => _MiniAppWebViewPageState(); } class _MiniAppWebViewPageState extends ConsumerState { late final WebViewController _controller; bool _isLoading = true; String? _errorMsg; @override void initState() { super.initState(); _initController(); } void _initController() { final token = ref.read(apiConfigProvider).token ?? ''; final url = _buildUrl(token); _controller = WebViewController() ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setBackgroundColor(Colors.black) ..setNavigationDelegate( NavigationDelegate( onPageStarted: (_) { if (mounted) setState(() { _isLoading = true; _errorMsg = null; }); }, onPageFinished: (_) { if (mounted) setState(() => _isLoading = false); }, onWebResourceError: (error) { if (mounted) { setState(() { _isLoading = false; _errorMsg = '加载失败:${error.description}'; }); } }, ), ) ..loadRequest(Uri.parse(url)); } String _buildUrl(String token) { var base = AppConfig.apiBaseUrl; if (base.endsWith('/')) base = base.substring(0, base.length - 1); final appId = widget.appId.isNotEmpty ? widget.appId : widget.gameId; return '$base/miniapp/$appId/index.html' '?gameId=${Uri.encodeComponent(widget.gameId)}' '&token=${Uri.encodeComponent(token)}' '&chatId=${widget.chatId}' '&chatType=${widget.chatType}'; } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), actions: [ IconButton( icon: const Icon(Icons.refresh), onPressed: _initController, ), ], ), body: Stack( children: [ WebViewWidget(controller: _controller), if (_isLoading) const Center(child: CircularProgressIndicator()), if (_errorMsg != null) Center( child: Padding( padding: const EdgeInsets.all(24), child: Column( mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.error_outline, size: 48, color: Colors.grey), const SizedBox(height: 12), Text(_errorMsg!, textAlign: TextAlign.center), const SizedBox(height: 16), ElevatedButton( onPressed: _initController, child: const Text('重试'), ), ], ), ), ), ], ), ); } } /// 小程序路由入口 /// /// 通过 [Navigator] push [MiniAppWebViewPage],保留在当前 ProviderScope 中。 class MiniAppRouter { MiniAppRouter._(); static void open( BuildContext context, WidgetRef ref, { required String gameId, String appId = '', required int chatId, required int chatType, String title = '游戏', }) { Navigator.of(context).push( MaterialPageRoute( builder: (_) => MiniAppWebViewPage( gameId: gameId, appId: appId.isNotEmpty ? appId : gameId, chatId: chatId, chatType: chatType, title: title, ), ), ); } }