import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:url_launcher/url_launcher.dart'; import '../providers/update_provider.dart'; import '../services/update_service.dart'; class UpdateNotificationBanner extends StatelessWidget { const UpdateNotificationBanner({super.key}); @override Widget build(BuildContext context) { return Consumer( builder: (context, updateProvider, child) { if (!updateProvider.hasUpdate) { return const SizedBox.shrink(); } final update = updateProvider.availableUpdate!; return Container( width: double.infinity, padding: const EdgeInsets.all(12), margin: const EdgeInsets.all(8), decoration: BoxDecoration( color: const Color(0xFF1A4B3A), // Dark green background border: Border.all(color: const Color(0xFF50E3C2), width: 1), borderRadius: BorderRadius.circular(8), ), child: Row( children: [ const Icon( Icons.system_update, color: Color(0xFF50E3C2), size: 20, ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text( 'Update Available: v${update.latestVersion}', style: const TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 14, ), ), Text( 'Current: v${update.currentVersion}', style: const TextStyle( color: Colors.white70, fontSize: 12, ), ), ], ), ), TextButton( onPressed: () => _showUpdateDialog(context, update), style: TextButton.styleFrom( foregroundColor: const Color(0xFF50E3C2), ), child: const Text('View'), ), IconButton( onPressed: () => updateProvider.dismissUpdate(), icon: const Icon(Icons.close, color: Colors.white70, size: 18), tooltip: 'Dismiss', ), ], ), ); }, ); } void _showUpdateDialog(BuildContext context, UpdateInfo update) { showDialog( context: context, builder: (context) => UpdateDialog(update: update), ); } } class UpdateDialog extends StatelessWidget { final UpdateInfo update; const UpdateDialog({super.key, required this.update}); @override Widget build(BuildContext context) { final asset = update.getAssetForCurrentPlatform(); return AlertDialog( backgroundColor: const Color(0xFF1A1F3A), title: Row( children: [ const Icon(Icons.system_update, color: Color(0xFF50E3C2)), const SizedBox(width: 8), Text( 'Update Available', style: const TextStyle(color: Colors.white), ), ], ), content: SizedBox( width: 500, child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildVersionInfo(), const SizedBox(height: 16), _buildReleaseInfo(), if (update.releaseNotes.isNotEmpty) ...[ const SizedBox(height: 16), _buildReleaseNotes(), ], if (asset != null) ...[ const SizedBox(height: 16), _buildDownloadInfo(asset), ], ], ), ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('Later', style: TextStyle(color: Colors.white70)), ), if (asset != null) ElevatedButton( onPressed: () => _downloadUpdate(asset.downloadUrl), style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF50E3C2), foregroundColor: Colors.black, ), child: const Text('Download'), ), ElevatedButton( onPressed: () => _openReleasePage(update.releaseUrl), style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF50E3C2), foregroundColor: Colors.black, ), child: const Text('View Release'), ), ], ); } Widget _buildVersionInfo() { return Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: const Color(0xFF0A0E27), borderRadius: BorderRadius.circular(8), border: Border.all(color: const Color(0xFF50E3C2).withValues(alpha: 0.3)), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Current Version', style: TextStyle(color: Colors.white70, fontSize: 12), ), Text( 'v${update.currentVersion}', style: const TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold), ), ], ), const Icon(Icons.arrow_forward, color: Color(0xFF50E3C2)), Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ const Text( 'Latest Version', style: TextStyle(color: Colors.white70, fontSize: 12), ), Text( 'v${update.latestVersion}', style: const TextStyle(color: Color(0xFF50E3C2), fontSize: 16, fontWeight: FontWeight.bold), ), ], ), ], ), ); } Widget _buildReleaseInfo() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( update.releaseName, style: const TextStyle( color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 4), Text( 'Released ${_formatDate(update.publishedAt)}', style: const TextStyle(color: Colors.white70, fontSize: 12), ), ], ); } Widget _buildReleaseNotes() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Release Notes', style: TextStyle( color: Colors.white, fontSize: 14, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 8), Container( width: double.infinity, padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: const Color(0xFF0A0E27), borderRadius: BorderRadius.circular(8), border: Border.all(color: const Color(0xFF50E3C2).withValues(alpha: 0.3)), ), child: Text( update.releaseNotes, style: const TextStyle(color: Colors.white70, fontSize: 12), maxLines: 8, overflow: TextOverflow.ellipsis, ), ), ], ); } Widget _buildDownloadInfo(ReleaseAsset asset) { return Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: const Color(0xFF0A0E27), borderRadius: BorderRadius.circular(8), border: Border.all(color: const Color(0xFF50E3C2).withValues(alpha: 0.3)), ), child: Row( children: [ const Icon(Icons.download, color: Color(0xFF50E3C2), size: 20), const SizedBox(width: 8), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( asset.name, style: const TextStyle(color: Colors.white, fontSize: 14), ), Text( asset.formattedSize, style: const TextStyle(color: Colors.white70, fontSize: 12), ), ], ), ), ], ), ); } String _formatDate(DateTime date) { final now = DateTime.now(); final difference = now.difference(date); if (difference.inDays > 0) { return '${difference.inDays} day${difference.inDays == 1 ? '' : 's'} ago'; } else if (difference.inHours > 0) { return '${difference.inHours} hour${difference.inHours == 1 ? '' : 's'} ago'; } else { return '${difference.inMinutes} minute${difference.inMinutes == 1 ? '' : 's'} ago'; } } void _downloadUpdate(String downloadUrl) async { final uri = Uri.parse(downloadUrl); if (await canLaunchUrl(uri)) { await launchUrl(uri); } } void _openReleasePage(String releaseUrl) async { final uri = Uri.parse(releaseUrl); if (await canLaunchUrl(uri)) { await launchUrl(uri); } } }