taxgilde/lib/consts/download_helper.dart
2026-04-11 10:21:31 +05:30

540 lines
16 KiB
Dart

import 'dart:io';
import 'package:dio/dio.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';
class DownloadHelper {
static const String API_BASE_URL = "https://www.taxglide.amrithaa.net/api/";
/// Request storage permission based on Android version
static Future<bool> 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<Directory> 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<Directory> 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<Directory> 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<bool> 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<String?> 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<String?> checkFileExists(String url) async {
return await checkFileExistsInReceived(url);
}
/// ⭐ Download file to Received folder (now in public Downloads) and Gallery
static Future<Map<String, dynamic>> 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");
// 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<Map<String, dynamic>> downloadFile(String url) async {
return await downloadFileToReceived(url);
}
/// ⭐ Save file to Send folder (now in public Downloads)
static Future<String> 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<List<File>> saveMultipleToSendFolder(List<File> files) async {
List<File> 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<void> 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");
}
}
/// Get all files from Send folder
static Future<List<File>> getSentFiles() async {
try {
final sendFolder = await getSendFolder();
if (await sendFolder.exists()) {
return sendFolder.listSync().whereType<File>().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<List<File>> getReceivedFiles() async {
try {
final receivedFolder = await getReceivedFolder();
if (await receivedFolder.exists()) {
return receivedFolder.listSync().whereType<File>().toList()..sort(
(a, b) => b.lastModifiedSync().compareTo(a.lastModifiedSync()),
);
}
return [];
} catch (e) {
print("❌ Error getting received files: $e");
return [];
}
}
/// Delete a file
static Future<bool> 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<void> 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<void> 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<String> 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',
].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 '📽️';
}
return '📎';
}
/// Get storage info
static Future<Map<String, String>> 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';
}
}