204 lines
5.4 KiB
Dart
204 lines
5.4 KiB
Dart
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<String> _webSchemes = {
|
|
'about',
|
|
'blob',
|
|
'data',
|
|
'file',
|
|
'http',
|
|
'https',
|
|
'javascript',
|
|
};
|
|
|
|
static Future<NavigationDecision> 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<bool> 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<bool> launchExternal(Uri uri) async {
|
|
for (final targetUri in [uri, ..._fallbackUris(uri)]) {
|
|
if (await _tryLaunch(targetUri)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static Future<bool> _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<bool> _tryLaunchNative(Uri uri) async {
|
|
if (defaultTargetPlatform != TargetPlatform.android) {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
final result = await _externalIntentChannel.invokeMethod<bool>(
|
|
'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<Uri> _fallbackUris(Uri uri) {
|
|
final fallbackUris = <Uri>[];
|
|
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<Uri> _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<Uri> _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.',
|
|
);
|
|
}
|
|
}
|