// SPDX-FileCopyrightText: 2026 TatuChat // // SPDX-License-Identifier: AGPL-3.0-or-later import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:material_ui/material_ui.dart'; import 'package:tatchat/l10n/l10n.dart'; /// Native implementation of [MiniAppShell] using an isolated [InAppWebView]. /// /// The mini app HTML is shipped with a small bridge shim injected before /// `` that forwards `window.parent.postMessage` calls to Dart through /// `flutter_inappwebview`'s JavaScript handler, and delivers the response /// back to the mini app via a `postMessage` with the same `requestId`. class MiniAppShell extends StatefulWidget { final String html; final String appId; final String title; final Future> Function(Map request) onRequest; const MiniAppShell({ super.key, required this.html, required this.appId, required this.title, required this.onRequest, }); @override State createState() => _MiniAppShellState(); } class _MiniAppShellState extends State { static const String _handlerName = 'com.tatuchat.miniapp.bridge'; InAppWebViewController? _webViewController; String get _injectedHtml { const shim = ''' '''; final bodyEnd = widget.html.lastIndexOf(''); if (bodyEnd == -1) { return '${widget.html}\n$shim'; } return widget.html.replaceRange(bodyEnd, bodyEnd, '\n$shim'); } @override Widget build(BuildContext context) { final theme = Theme.of(context); return Scaffold( appBar: AppBar( title: Text(widget.title), backgroundColor: theme.colorScheme.surface, surfaceTintColor: Colors.transparent, foregroundColor: theme.colorScheme.onSurface, actions: [ IconButton( tooltip: L10n.of(context).refresh, icon: const Icon(Icons.refresh_rounded), onPressed: () => _webViewController?.reload(), ), ], ), backgroundColor: theme.colorScheme.surface, body: InAppWebView( initialSettings: InAppWebViewSettings( javaScriptEnabled: true, javaScriptCanOpenWindowsAutomatically: false, domStorageEnabled: true, transparentBackground: false, verticalScrollBarEnabled: true, horizontalScrollBarEnabled: false, ), initialData: InAppWebViewInitialData( data: _injectedHtml, mimeType: 'text/html', encoding: 'utf8', baseUrl: WebUri('https://tatuchat.local'), ), onWebViewCreated: (controller) { _webViewController = controller; controller.addJavaScriptHandler( handlerName: _handlerName, callback: (args) async { if (args.isEmpty) return null; final raw = args.first; if (raw is Map) { return widget.onRequest(Map.from(raw)); } return null; }, ); }, ), ); } }