import 'package:flutter/material.dart'; import 'package:url_launcher/url_launcher.dart'; import '../theme/app_theme.dart'; /// Helper class for safely launching URLs with user warnings for unknown sites class UrlLauncherHelper { // List of known safe domains static const List _safeDomains = [ 'mp.ls', 'www.mp.ls', 'patrick.mp.ls' 'gosojorn.com', 'www.gosojorn.com' 'youtube.com', 'www.youtube.com', 'youtu.be', 'instagram.com', 'www.instagram.com', 'twitter.com', 'www.twitter.com', 'x.com', 'www.x.com', 'facebook.com', 'www.facebook.com', 'tiktok.com', 'www.tiktok.com', 'linkedin.com', 'www.linkedin.com', 'github.com', 'www.github.com', 'twitch.tv', 'www.twitch.tv', 'reddit.com', 'www.reddit.com', 'medium.com', 'www.medium.com', 'substack.com', 'patreon.com', 'www.patreon.com', 'discord.com', 'www.discord.com', 'discord.gg', 'spotify.com', 'www.spotify.com', 'pinterest.com', 'www.pinterest.com', 'snapchat.com', 'www.snapchat.com', 'telegram.org', 'www.telegram.org', 't.me', ]; /// Check if a URL is from a known safe domain static bool isKnownSafeDomain(String url) { try { final uri = Uri.parse(url.startsWith('http') ? url : 'https://$url'); final host = uri.host.toLowerCase(); return _safeDomains.any((domain) => host == domain || host.endsWith('.$domain') ); } catch (e) { return false; } } /// Safely launch a URL with user confirmation for unknown sites static Future launchUrlSafely( BuildContext context, String url, { bool forceWarning = false, }) async { // Validate URL scheme to prevent shell escaping try { final uri = Uri.parse(url.startsWith('http') ? url : 'https://$url'); if (!['http:', 'https:'].contains(uri.scheme)) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Invalid URL scheme. Only HTTP and HTTPS URLs are allowed.'), backgroundColor: AppTheme.error, ), ); return; } } catch (e) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Invalid URL format.'), backgroundColor: AppTheme.error, ), ); return; } final isSafe = isKnownSafeDomain(url); // Show warning dialog for unknown sites if (!isSafe || forceWarning) { final shouldLaunch = await showDialog( context: context, builder: (context) => AlertDialog( title: const Text('External Link'), content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'You are about to visit an external website:', style: TextStyle(fontWeight: FontWeight.w600), ), const SizedBox(height: 12), Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: AppTheme.egyptianBlue.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(8), ), child: Text( url, style: const TextStyle( fontFamily: 'monospace', fontSize: 12, ), ), ), const SizedBox(height: 16), if (!isSafe) ...[ Row( children: [ Icon( Icons.warning_amber, color: Colors.orange, size: 20, ), const SizedBox(width: 8), const Expanded( child: Text( 'This site is not recognized as a known safe website. Proceed with caution.', style: TextStyle( fontSize: 13, color: Colors.orange, ), ), ), ], ), const SizedBox(height: 8), ], const Text( 'Always be careful when visiting external links and never share your password or personal information.', style: TextStyle(fontSize: 13), ), ], ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(false), child: const Text('Cancel'), ), ElevatedButton( onPressed: () => Navigator.of(context).pop(true), style: ElevatedButton.styleFrom( backgroundColor: isSafe ? AppTheme.royalPurple : Colors.orange, ), child: const Text('Continue'), ), ], ), ); if (shouldLaunch != true) return; } // Launch the URL try { final uri = Uri.parse(url.startsWith('http') ? url : 'https://$url'); if (await canLaunchUrl(uri)) { await launchUrl( uri, mode: LaunchMode.externalApplication, ); } else { if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Could not open link: $url'), backgroundColor: AppTheme.error, ), ); } } } catch (e) { if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Error opening link: ${e.toString()}'), backgroundColor: AppTheme.error, ), ); } } } }