import 'dart:io'; import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:path_provider/path_provider.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:open_filex/open_filex.dart'; import 'package:gal/gal.dart'; import 'package:taxglide/services/notification_service.dart'; class DownloadHelper { static const String API_BASE_URL = "https://www.taxglide.amrithaa.net/api/"; /// Request storage permission based on Android version static Future requestStoragePermission() async { if (!Platform.isAndroid) return true; try { final androidInfo = await DeviceInfoPlugin().androidInfo; final sdkInt = androidInfo.version.sdkInt; print("📱 Android SDK: $sdkInt"); // Android 13+ (API 33+) if (sdkInt >= 33) { final photoStatus = await Permission.photos.request(); final videoStatus = await Permission.videos.request(); print("✅ Android 13+ - Media permissions requested"); return photoStatus.isGranted || videoStatus.isGranted; } // Android 11-12 (API 30-32) if (sdkInt >= 30) { if (await Permission.manageExternalStorage.isGranted) { return true; } var status = await Permission.manageExternalStorage.request(); if (status.isGranted) return true; status = await Permission.storage.request(); return status.isGranted; } // Android 10 and below (API 29 and below) final status = await Permission.storage.request(); return status.isGranted; } catch (e) { print("❌ Permission error: $e"); return false; } } /// ⭐ NEW: Get TaxGlide folder in PUBLIC Downloads static Future getTaxGlideFolder() async { try { await requestStoragePermission(); if (Platform.isAndroid) { // Use public Downloads folder for all Android versions final publicTaxGlide = Directory( '/storage/emulated/0/Download/TaxGlide', ); if (!await publicTaxGlide.exists()) { await publicTaxGlide.create(recursive: true); print( "✅ TaxGlide folder created in public Downloads: ${publicTaxGlide.path}", ); } return publicTaxGlide; } else { // iOS - use app documents final baseDirectory = await getApplicationDocumentsDirectory(); final taxGlideFolder = Directory('${baseDirectory.path}/TaxGlide'); if (!await taxGlideFolder.exists()) { await taxGlideFolder.create(recursive: true); } return taxGlideFolder; } } catch (e) { print("❌ Error creating TaxGlide folder: $e"); // Fallback to app-specific storage if public storage fails final fallbackDir = await getExternalStorageDirectory(); final taxGlideFolder = Directory('${fallbackDir!.path}/TaxGlide'); if (!await taxGlideFolder.exists()) { await taxGlideFolder.create(recursive: true); print("⚠️ Using app-specific storage as fallback"); } return taxGlideFolder; } } /// ⭐ Get Received folder in PUBLIC Downloads static Future getReceivedFolder() async { final taxGlideFolder = await getTaxGlideFolder(); final receivedFolder = Directory('${taxGlideFolder.path}/Received'); if (!await receivedFolder.exists()) { await receivedFolder.create(recursive: true); print("✅ Received folder created: ${receivedFolder.path}"); } return receivedFolder; } /// ⭐ Get Send folder in PUBLIC Downloads static Future getSendFolder() async { final taxGlideFolder = await getTaxGlideFolder(); final sendFolder = Directory('${taxGlideFolder.path}/Send'); if (!await sendFolder.exists()) { await sendFolder.create(recursive: true); print("✅ Send folder created: ${sendFolder.path}"); } return sendFolder; } /// Save file to Gallery (for images/videos only) static Future saveToGallery(String filePath) async { try { final file = File(filePath); if (!await file.exists()) { print("❌ File does not exist: $filePath"); return false; } final fileName = filePath.split('/').last; // Request permissions final hasPermission = await requestStoragePermission(); if (!hasPermission) { print("⚠️ No permission to save to gallery"); return false; } // Only save images/videos to Gallery if (isImageFile(filePath)) { await Gal.putImage(filePath, album: 'TaxGlide'); print("✅ Image saved to Gallery: $fileName"); return true; } else if (isVideoFile(filePath)) { await Gal.putVideo(filePath, album: 'TaxGlide'); print("✅ Video saved to Gallery: $fileName"); return true; } return false; } catch (e) { print("❌ Error saving to gallery: $e"); return false; } } /// Build download URL static String buildDownloadUrl(String filePath) { if (filePath.startsWith('http://') || filePath.startsWith('https://')) { return filePath; } return '${API_BASE_URL}chat/download/$filePath'; } /// Check if file exists in Received folder static Future checkFileExistsInReceived(String url) async { try { final fileName = url.contains('/') ? url.split('/').last.split('?').first : url.split('?').first; final receivedFolder = await getReceivedFolder(); final filePath = '${receivedFolder.path}/$fileName'; final file = File(filePath); if (await file.exists()) { print("✅ File already exists in Received: $filePath"); return filePath; } return null; } catch (e) { print("❌ Error checking file existence: $e"); return null; } } /// Backward compatibility static Future checkFileExists(String url) async { return await checkFileExistsInReceived(url); } /// ⭐ Download file to Received folder (now in public Downloads) and Gallery static Future> downloadFileToReceived(String url) async { try { final fullUrl = buildDownloadUrl(url); print("🔗 Downloading from: $fullUrl"); // Check if file already exists final existingFilePath = await checkFileExistsInReceived(fullUrl); if (existingFilePath != null) { print("📂 File already exists, opening it..."); await openDownloadedFile(existingFilePath); return { 'filePath': existingFilePath, 'isNewDownload': false, 'success': true, }; } // Request permissions await requestStoragePermission(); // Get filename and prepare save path String fileName = fullUrl.split('/').last.split('?').first; fileName = fileName.replaceAll(RegExp(r'[^\w\s\-\.]'), '_'); final receivedFolder = await getReceivedFolder(); final savePath = "${receivedFolder.path}/$fileName"; print("Download Started: Downloading $fileName..."); // Download file to Received folder (now in public Downloads) Dio dio = Dio(); await dio.download( fullUrl, savePath, onReceiveProgress: (received, total) { if (total != -1) { final progress = (received / total * 100).toStringAsFixed(0); print("📊 Download progress: $progress%"); } }, ); // Also save images/videos to Gallery bool savedToGallery = false; try { savedToGallery = await saveToGallery(savePath); } catch (e) { print("⚠️ Could not save to gallery: $e"); } if (savedToGallery) { print( "Download Complete: $fileName saved to Gallery & Downloads/TaxGlide/Received", ); } else { print( "Download Complete: $fileName saved to Downloads/TaxGlide/Received", ); } print("✅ File downloaded to: $savePath"); // ⭐ Show Download Notification try { Get.find().showDownloadNotification(fileName, savePath); } catch (e) { debugPrint('⚠️ Could not show download notification: $e'); } // Open the downloaded file await openDownloadedFile(savePath); return { 'filePath': savePath, 'isNewDownload': true, 'success': true, 'savedToGallery': savedToGallery, }; } catch (e) { print("❌ Download error: $e"); print("Download Failed: Error: ${e.toString()}"); return {'success': false, 'error': e.toString()}; } } /// Backward compatibility static Future> downloadFile(String url) async { return await downloadFileToReceived(url); } /// ⭐ Save file to Send folder (now in public Downloads) static Future saveToSendFolder(File file) async { try { if (!await file.exists()) { throw Exception("Source file does not exist: ${file.path}"); } final sendFolder = await getSendFolder(); final originalFileName = file.path.split('/').last; final timestamp = DateTime.now().millisecondsSinceEpoch; final fileName = '${timestamp}_$originalFileName'; final savePath = '${sendFolder.path}/$fileName'; final savedFile = await file.copy(savePath); print("✅ File saved to Send folder (Downloads/TaxGlide/Send): $savePath"); // Also save images/videos to Gallery try { await saveToGallery(savePath); } catch (e) { print("⚠️ Could not save to gallery: $e"); } return savedFile.path; } catch (e) { print("❌ Error saving to Send folder: $e"); print("⚠️ Using original file path: ${file.path}"); return file.path; } } /// Save multiple files to Send folder static Future> saveMultipleToSendFolder(List files) async { List savedFiles = []; for (File file in files) { try { final savedPath = await saveToSendFolder(file); savedFiles.add(File(savedPath)); } catch (e) { print("❌ Error saving file: ${file.path}, using original"); savedFiles.add(file); } } return savedFiles; } /// Open file with appropriate viewer static Future openDownloadedFile(String path) async { print("🔓 Opening file: $path"); try { final file = File(path); if (!await file.exists()) { print("❌ File not found at $path"); print("Error: File not found"); return; } final result = await OpenFilex.open(path); if (result.type != ResultType.done) { print("⚠️ Could not open file: ${result.message}"); print("Notice: File saved but no app found to open it"); } } catch (e) { print("❌ Error opening file: $e"); print("Error: Could not open file"); } } /// ⭐ NEW: Open folder in system file manager static Future openFolder(String path) async { print("📂 Opening folder: $path"); try { final dir = Directory(path); if (!await dir.exists()) { print("❌ Folder not found at $path"); return; } // On Android, most file managers respond well to OpenFilex on a directory final result = await OpenFilex.open(path); if (result.type != ResultType.done) { print("⚠️ Could not open folder: ${result.message}"); } } catch (e) { print("❌ Error opening folder: $e"); } } /// Get all files from Send folder static Future> getSentFiles() async { try { final sendFolder = await getSendFolder(); if (await sendFolder.exists()) { return sendFolder.listSync().whereType().toList()..sort( (a, b) => b.lastModifiedSync().compareTo(a.lastModifiedSync()), ); } return []; } catch (e) { print("❌ Error getting sent files: $e"); return []; } } /// Get all files from Received folder static Future> getReceivedFiles() async { try { final receivedFolder = await getReceivedFolder(); if (await receivedFolder.exists()) { return receivedFolder.listSync().whereType().toList()..sort( (a, b) => b.lastModifiedSync().compareTo(a.lastModifiedSync()), ); } return []; } catch (e) { print("❌ Error getting received files: $e"); return []; } } /// Delete a file static Future deleteFile(String filePath) async { try { final file = File(filePath); if (await file.exists()) { await file.delete(); print("✅ File deleted: $filePath"); print("File Deleted: File removed successfully"); return true; } return false; } catch (e) { print("❌ Error deleting file: $e"); return false; } } /// Clear all files from Send folder static Future clearSendFolder() async { try { final files = await getSentFiles(); for (final file in files) { await file.delete(); } print("✅ Send folder cleared: ${files.length} files deleted"); } catch (e) { print("❌ Error clearing Send folder: $e"); } } /// Clear all files from Received folder static Future clearReceivedFolder() async { try { final files = await getReceivedFiles(); for (final file in files) { await file.delete(); } print("✅ Received folder cleared: ${files.length} files deleted"); } catch (e) { print("❌ Error clearing Received folder: $e"); } } /// Get file size in human-readable format static Future getFileSize(File file) async { try { final bytes = await file.length(); if (bytes < 1024) return '$bytes B'; if (bytes < 1024 * 1024) { return '${(bytes / 1024).toStringAsFixed(2)} KB'; } if (bytes < 1024 * 1024 * 1024) { return '${(bytes / (1024 * 1024)).toStringAsFixed(2)} MB'; } return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(2)} GB'; } catch (e) { return 'Unknown'; } } /// Get file extension static String getFileExtension(String path) { return path.split('.').last.toLowerCase(); } /// Check if file is image static bool isImageFile(String path) { final ext = getFileExtension(path); return ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp'].contains(ext); } /// Check if file is document static bool isDocumentFile(String path) { final ext = getFileExtension(path); return [ 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'txt', 'ppt', 'pptx', 'rtf', 'odt', 'rar', 'zip', '7z', ].contains(ext); } /// Check if file is an archive static bool isArchiveFile(String path) { final ext = getFileExtension(path); return ['zip', 'rar', '7z', 'tar', 'gz'].contains(ext); } /// Check if file is video static bool isVideoFile(String path) { final ext = getFileExtension(path); return ['mp4', 'avi', 'mov', 'wmv', 'flv', 'mkv', 'webm'].contains(ext); } /// Check if file is audio static bool isAudioFile(String path) { final ext = getFileExtension(path); return ['mp3', 'wav', 'aac', 'flac', 'ogg', 'm4a', 'wma'].contains(ext); } /// Get file icon based on type static String getFileIcon(String path) { if (isImageFile(path)) return '🖼️'; if (isVideoFile(path)) return '🎥'; if (isAudioFile(path)) return '🎵'; if (isDocumentFile(path)) { final ext = getFileExtension(path); if (ext == 'pdf') return '📄'; if (['doc', 'docx'].contains(ext)) return '📝'; if (['xls', 'xlsx'].contains(ext)) return '📊'; if (['ppt', 'pptx'].contains(ext)) return '📽️'; if (isArchiveFile(path)) return '📦'; } return '📎'; } /// Get storage info static Future> getStorageInfo() async { try { final taxGlideFolder = await getTaxGlideFolder(); final sentFiles = await getSentFiles(); final receivedFiles = await getReceivedFiles(); int totalSentSize = 0; int totalReceivedSize = 0; for (final file in sentFiles) { totalSentSize += await file.length(); } for (final file in receivedFiles) { totalReceivedSize += await file.length(); } return { 'taxGlidePath': taxGlideFolder.path, 'sentFiles': sentFiles.length.toString(), 'receivedFiles': receivedFiles.length.toString(), 'sentSize': _formatBytes(totalSentSize), 'receivedSize': _formatBytes(totalReceivedSize), 'totalSize': _formatBytes(totalSentSize + totalReceivedSize), }; } catch (e) { print("❌ Error getting storage info: $e"); return {}; } } static String _formatBytes(int bytes) { if (bytes < 1024) return '$bytes B'; if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(2)} KB'; if (bytes < 1024 * 1024 * 1024) { return '${(bytes / (1024 * 1024)).toStringAsFixed(2)} MB'; } return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(2)} GB'; } }