import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:aslan/chatvibe_ui/components/at_tts.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:webview_flutter/webview_flutter.dart'; class ATWebViewNavigationHandler { static const MethodChannel _externalIntentChannel = MethodChannel( 'com.chat.auu.webview/external_intent', ); static const Set _webSchemes = { 'about', 'blob', 'data', 'file', 'http', 'https', 'javascript', }; static Future handleNavigationRequest( NavigationRequest request, ) async { final uri = Uri.tryParse(request.url); if (uri == null) return NavigationDecision.prevent; final scheme = uri.scheme.toLowerCase(); if (scheme.isEmpty || _webSchemes.contains(scheme)) { return NavigationDecision.navigate; } final launched = await launchExternal(uri); if (!launched) { showOpenAppFailedTip(uri); } return NavigationDecision.prevent; } static Future handleWebResourceError(WebResourceError error) async { final errorUrl = error.url; if (errorUrl == null || errorUrl.isEmpty) return false; final uri = Uri.tryParse(errorUrl); if (uri == null) return false; final scheme = uri.scheme.toLowerCase(); if (scheme.isEmpty || _webSchemes.contains(scheme)) { return false; } final launched = await launchExternal(uri); if (!launched) { showOpenAppFailedTip(uri); } return launched; } static Future launchExternal(Uri uri) async { for (final targetUri in [uri, ..._fallbackUris(uri)]) { if (await _tryLaunch(targetUri)) { return true; } } return false; } static Future _tryLaunch(Uri uri) async { if (await _tryLaunchNative(uri)) { return true; } if (defaultTargetPlatform == TargetPlatform.android && _isPaymentUri(uri)) { return false; } try { return await launchUrl(uri, mode: LaunchMode.externalApplication); } catch (e) { debugPrint('Open external app failed: $uri, error: $e'); return false; } } static Future _tryLaunchNative(Uri uri) async { if (defaultTargetPlatform != TargetPlatform.android) { return false; } try { final result = await _externalIntentChannel.invokeMethod( 'openExternalUrl', uri.toString(), ); return result ?? false; } on MissingPluginException { return false; } catch (e) { debugPrint('Open native external app failed: $uri, error: $e'); return false; } } static List _fallbackUris(Uri uri) { final fallbackUris = []; final browserFallbackUrl = _intentFallbackUrl(uri); if (browserFallbackUrl != null) { fallbackUris.add(browserFallbackUrl); } final upiFallbackUrl = _upiFallbackUrl(uri); if (upiFallbackUrl != null) { fallbackUris.add(upiFallbackUrl); } fallbackUris.addAll(_paymentAppSchemeFallbackUrls(upiFallbackUrl ?? uri)); fallbackUris.addAll(_upiIntentFallbackUrls(upiFallbackUrl ?? uri)); return fallbackUris; } static Uri? _intentFallbackUrl(Uri uri) { if (uri.scheme.toLowerCase() != 'intent') return null; final fallback = RegExp( r'S\.browser_fallback_url=([^;]+)', ).firstMatch(uri.toString())?.group(1); if (fallback == null || fallback.isEmpty) return null; return Uri.tryParse(Uri.decodeComponent(fallback)); } static Uri? _upiFallbackUrl(Uri uri) { final scheme = uri.scheme.toLowerCase(); if (scheme != 'phonepe' && scheme != 'paytmmp' && scheme != 'bhim' && scheme != 'bhimupi') { return null; } final path = uri.path.isNotEmpty ? uri.path : uri.host; if (!path.split('/').contains('pay')) return null; return uri.replace(scheme: 'upi', host: 'pay', path: ''); } static Iterable _paymentAppSchemeFallbackUrls(Uri uri) sync* { if (uri.scheme.toLowerCase() != 'upi') return; final path = uri.path.isNotEmpty ? uri.path : uri.host; if (path != 'pay') return; const schemes = ['phonepe', 'paytmmp', 'bhim']; for (final scheme in schemes) { yield uri.replace(scheme: scheme, host: 'pay', path: ''); } } static Iterable _upiIntentFallbackUrls(Uri uri) sync* { if (!_isPaymentUri(uri)) return; final upiUri = uri.scheme.toLowerCase() == 'upi' ? uri : _upiFallbackUrl(uri); if (upiUri == null) return; final path = upiUri.path.isNotEmpty ? upiUri.path : upiUri.host; if (path != 'pay') return; const packageNames = [ 'com.phonepe.app', 'net.one97.paytm', 'com.google.android.apps.nbu.paisa.user', 'in.org.npci.upiapp', ]; final query = upiUri.hasQuery ? '?${upiUri.query}' : ''; for (final packageName in packageNames) { yield Uri.parse( 'intent://pay$query#Intent;scheme=upi;package=$packageName;end', ); } } static bool _isPaymentUri(Uri uri) { final scheme = uri.scheme.toLowerCase(); return scheme == 'phonepe' || scheme == 'paytmmp' || scheme == 'bhim' || scheme == 'bhimupi' || scheme == 'upi' || scheme == 'intent'; } static void showOpenAppFailedTip(Uri uri) { ATTts.show( _isPaymentUri(uri) ? 'Please install or set up the payment app first.' : 'App is not installed.', ); } }