Dependencies :

				
					file_picker: ^8.0.0
path: ^1.9.0
permission_handler: ^11.3.0
				
			

Custom Code Files :

download_globals.dart

				
					int activeDownloads = 0;
				
			

Custom Actions :

pickSavePath

				
					import 'dart:io';
import 'package:permission_handler/permission_handler.dart';
import 'package:file_picker/file_picker.dart';

Future<String?> pickSavePath() async {
  // --- 1. Request storage permission ONLY on Android ---
  if (Platform.isAndroid) {
    final perm = await Permission.storage.request();
    if (!perm.isGranted) {
      return Future.error('Permission Denied');
    }
  }

  // --- 2. Open folder picker (works on macOS, iOS, Windows, Android, Linux) ---
  String? selectedDirectory = await FilePicker.platform.getDirectoryPath();

  if (selectedDirectory == null) {
    return null; // user canceled
  }

  return selectedDirectory;
}
				
			

returnFileName

				
					import 'dart:convert';
import 'dart:io';

Future<String?> returnFileName(String url) async {
  try {
    final client = HttpClient();

    String currentUrl = url;
    int redirectCount = 0;

    HttpClientResponse response;

    // -------- MANUAL REDIRECT FOLLOWING --------
    while (true) {
      final req = await client.getUrl(Uri.parse(currentUrl));
      response = await req.close();

      if (response.statusCode == 301 ||
          response.statusCode == 302 ||
          response.statusCode == 303 ||
          response.statusCode == 307 ||
          response.statusCode == 308) {
        if (redirectCount >= 10) break;

        final location = response.headers.value("location");
        if (location == null) break;

        currentUrl = location;
        redirectCount++;
        continue;
      }

      break; // Got final response
    }

    // -------- EXTRACT CONTENT-DISPOSITION --------
    final cd = response.headers.value("content-disposition");

    if (cd != null) {
      // filename*=UTF-8''ActualName.xyz
      final utf8Match = RegExp(r"filename\*=(?:UTF-8'')?(.+)").firstMatch(cd);

      if (utf8Match != null) {
        return Uri.decodeFull(utf8Match.group(1)!);
      }

      // filename="ActualName.xyz"
      final normalMatch = RegExp(r'filename="?([^\";]+)"?').firstMatch(cd);

      if (normalMatch != null) {
        return normalMatch.group(1);
      }
    }

    // -------- FALLBACK: use final URL last segment --------
    return Uri.parse(currentUrl).pathSegments.last;
  } catch (e, st) {
    print("returnFileName ERROR → $e");
    print(st);
    return null;
  }
}
				
			

downloadFile

				
					import 'dart:io';
import 'dart:async';
import 'dart:typed_data';
import 'package:tutorials/custom_code/download_globals.dart';

Future downloadFile(
  String? fileId,
  String? url,
  String? savePath,
) async {
  if (fileId == null || url == null || savePath == null) return;

  print("downloadFile START fileId=$fileId");

  // ---------------------------------------------
  // PARALLEL DOWNLOAD LIMIT CONTROL (FIXED)
  // ---------------------------------------------
  final maxParallel = FFAppState().parallelDownloads ?? 0;

  if (maxParallel > 0) {
    while (activeDownloads >= maxParallel) {
      print(
          "Parallel limit reached ($activeDownloads/$maxParallel). Waiting...");
      await Future.delayed(Duration(milliseconds: 300));
    }
  }

  // Reserve slot
  activeDownloads++;
  print("Slot taken. Active Downloads = $activeDownloads");

  // ---------------------------------------------
  // LOAD ITEM
  // ---------------------------------------------
  var list = FFAppState().downloadsList.toList();
  var index = list.indexWhere((d) => d.id == fileId);
  if (index == -1) {
    activeDownloads--;
    return;
  }

  var item = list[index];

  // ---------------------------------------------
  // AUTO FILENAME USING item.fileName + NO DUPLICATE
  // ---------------------------------------------
  String fileName = item.fileName ?? url.split("/").last;

  // Split base + extension
  String base = fileName;
  String? ext;
  int dotIndex = fileName.lastIndexOf(".");
  if (dotIndex != -1) {
    base = fileName.substring(0, dotIndex);
    ext = fileName.substring(dotIndex);
  }

  // Build file path
  String finalPath = "$savePath/$fileName";
  int counter = 1;

  // If file exists → append _1, _2, _3...
  while (File(finalPath).existsSync()) {
    finalPath = "$savePath/${base}_$counter${ext ?? ''}";
    counter++;
  }

  // Update state before download
  item.status = "downloading";
  item.downloaded = 0;
  item.speed = 0;
  item.savePath = finalPath;

  list[index] = item;
  FFAppState().downloadsList = list;
  FFAppState().update(() {});

  try {
    final file = File(finalPath);
    if (!file.parent.existsSync()) {
      file.parent.createSync(recursive: true);
    }

    final sink = file.openWrite(mode: FileMode.write);

    final client = HttpClient();
    final req = await client.getUrl(Uri.parse(url));
    final res = await req.close();

    int totalSize = res.contentLength;
    int downloaded = 0;

    // REAL SPEED VARIABLES
    int lastDownloaded = 0;
    int lastCheck = DateTime.now().millisecondsSinceEpoch;

    // ---------------------------------------------
    // MAIN DOWNLOAD LOOP
    // ---------------------------------------------
    await for (final chunk in res) {
      sink.add(chunk);
      downloaded += chunk.length;

      // REAL SPEED (bytes/sec)
      int now = DateTime.now().millisecondsSinceEpoch;
      int elapsed = now - lastCheck;

      if (elapsed >= 1000) {
        item.speed = downloaded - lastDownloaded;
        lastDownloaded = downloaded;
        lastCheck = now;
      }

      // SPEED LIMIT
      final limit = FFAppState().downloadSpeedLimit;
      if (!(limit == null || limit == 0)) {
        if (item.speed > limit) {
          int delayMs =
              (((item.speed - limit) / limit) * 1000).clamp(5, 300).toInt();
          await Future.delayed(Duration(milliseconds: delayMs));
        }
      }

      // REFRESH STATE
      list = FFAppState().downloadsList.toList();
      index = list.indexWhere((d) => d.id == fileId);
      if (index == -1) break;

      item = list[index];

      // PAUSE
      if (item.status == "paused") {
        await sink.flush();
        await sink.close();
        client.close(force: true);

        activeDownloads = (activeDownloads - 1).clamp(0, 9999);
        print("Paused. Active = $activeDownloads");

        return;
      }

      // CANCEL
      if (item.status == "cancelled") {
        await sink.flush();
        await sink.close();
        client.close(force: true);

        if (file.existsSync()) file.deleteSync();

        activeDownloads = (activeDownloads - 1).clamp(0, 9999);
        print("Cancelled. Active = $activeDownloads");

        return;
      }

      // UPDATE
      item.totalSize = totalSize;
      item.downloaded = downloaded;
      item.status = "downloading";

      list[index] = item;
      FFAppState().downloadsList = list;
      FFAppState().update(() {});
    }

    // FINISH DOWNLOAD
    await sink.flush();
    await sink.close();
    client.close(force: true);

    list = FFAppState().downloadsList.toList();
    index = list.indexWhere((d) => d.id == fileId);

    if (index != -1) {
      item = list[index];
      item.status = "completed";
      item.speed = 0;

      list[index] = item;
      FFAppState().downloadsList = list;
      FFAppState().update(() {});
    }

    activeDownloads = (activeDownloads - 1).clamp(0, 9999);
    print("Completed. Active = $activeDownloads");
  } catch (e, st) {
    print("downloadFile ERROR: $e");
    print(st);

    activeDownloads = (activeDownloads - 1).clamp(0, 9999);
    print("Error. Active = $activeDownloads");
  }
}
				
			

pauseDownload

				
					Future pauseDownload(String? fileId) async {
  if (fileId == null) return;

  var list = FFAppState().downloadsList.toList();
  var index = list.indexWhere((d) => d.id == fileId);
  if (index == -1) return;

  var item = list[index];
  item.status = "paused";
  item.speed = 0;

  list[index] = item;
  FFAppState().downloadsList = list;
  FFAppState().update(() {});

  print("Paused: $fileId");
}

				
			

resumeDownload

				
					import 'dart:io';
import 'dart:async';
import 'dart:typed_data';
import 'package:tutorials/custom_code/download_globals.dart';

Future resumeDownload(
  String? fileId,
  String? url,
  String? savePath,
) async {
  if (fileId == null || url == null || savePath == null) return;

  print("resumeDownload START → $fileId");

  // ----------------------------------------------------
  // PARALLEL DOWNLOAD CONTROL (FIXED)
  // ----------------------------------------------------
  final maxParallel = FFAppState().parallelDownloads ?? 0;

  if (maxParallel > 0) {
    while (activeDownloads >= maxParallel) {
      print(
          "Parallel limit reached ($activeDownloads/$maxParallel). Waiting...");
      await Future.delayed(Duration(milliseconds: 300));
    }
  }

  // Reserve the slot
  activeDownloads++;
  print("Slot taken → Active Downloads: $activeDownloads");

  // ----------------------------------------------------
  // LOAD ITEM
  // ----------------------------------------------------
  var list = FFAppState().downloadsList.toList();
  var index = list.indexWhere((d) => d.id == fileId);
  if (index == -1) {
    activeDownloads--;
    return;
  }

  var item = list[index];

  final file = File(savePath);
  final alreadyDownloaded = file.existsSync() ? file.lengthSync() : 0;

  print("Resuming from $alreadyDownloaded bytes");

  // Update AppState
  item.status = "downloading";
  item.downloaded = alreadyDownloaded;
  item.speed = 0;

  list[index] = item;
  FFAppState().downloadsList = list;
  FFAppState().update(() {});

  try {
    // HTTP request with Range header
    final client = HttpClient();
    final req = await client.getUrl(Uri.parse(url));
    req.headers.add("Range", "bytes=$alreadyDownloaded-");

    final res = await req.close();
    final sink = file.openWrite(mode: FileMode.append);

    int downloaded = alreadyDownloaded;
    int totalSize = alreadyDownloaded + res.contentLength;

    // REAL SPEED VARIABLES
    int lastDownloaded = downloaded;
    int lastCheck = DateTime.now().millisecondsSinceEpoch;

    // ----------------------------------------------------
    // MAIN RESUME LOOP
    // ----------------------------------------------------
    await for (final chunk in res) {
      sink.add(chunk);
      downloaded += chunk.length;

      // REAL SPEED CALCULATION
      final now = DateTime.now().millisecondsSinceEpoch;
      final elapsed = now - lastCheck;

      if (elapsed >= 1000) {
        final bytesPerSecond = downloaded - lastDownloaded;
        item.speed = bytesPerSecond;

        lastDownloaded = downloaded;
        lastCheck = now;
      }

      // SPEED LIMITING (if any)
      final limit = FFAppState().downloadSpeedLimit;
      if (!(limit == null || limit == 0)) {
        if (item.speed > limit) {
          int delayMs =
              (((item.speed - limit) / limit) * 1000).clamp(5, 300).toInt();
          await Future.delayed(Duration(milliseconds: delayMs));
        }
      }

      // Refresh AppState
      list = FFAppState().downloadsList.toList();
      index = list.indexWhere((d) => d.id == fileId);
      if (index == -1) break;

      item = list[index];

      // ---------------- PAUSE ----------------
      if (item.status == "paused") {
        await sink.flush();
        await sink.close();
        client.close(force: true);

        activeDownloads = (activeDownloads - 1).clamp(0, 9999);
        print("Paused → Active: $activeDownloads");

        return;
      }

      // ---------------- CANCEL ----------------
      if (item.status == "cancelled") {
        await sink.flush();
        await sink.close();
        client.close(force: true);

        if (file.existsSync()) file.deleteSync();

        activeDownloads = (activeDownloads - 1).clamp(0, 9999);
        print("Cancelled → Active: $activeDownloads");

        return;
      }

      // UPDATE STATS
      item.downloaded = downloaded;
      item.totalSize = totalSize;
      item.status = "downloading";

      list[index] = item;
      FFAppState().downloadsList = list;
      FFAppState().update(() {});
    }

    // ----------------------------------------------------
    // COMPLETED
    // ----------------------------------------------------
    await sink.flush();
    await sink.close();
    client.close(force: true);

    list = FFAppState().downloadsList.toList();
    index = list.indexWhere((d) => d.id == fileId);

    if (index != -1) {
      item = list[index];
      item.status = "completed";
      item.speed = 0;

      list[index] = item;
      FFAppState().downloadsList = list;
      FFAppState().update(() {});
    }

    activeDownloads = (activeDownloads - 1).clamp(0, 9999);
    print("Completed → Active: $activeDownloads");
  } catch (e, st) {
    print("resumeDownload ERROR: $e");
    print(st);

    activeDownloads = (activeDownloads - 1).clamp(0, 9999);
    print("Error → Active: $activeDownloads");
  }
}
				
			

cancelDownload

				
					Future cancelDownload(String? fileId) async {
  if (fileId == null) return;

  var list = FFAppState().downloadsList.toList();
  var index = list.indexWhere((d) => d.id == fileId);

  if (index == -1) return;

  var item = list[index];

  // Set status to cancelled
  item.status = "cancelled";

  // Update AppState
  list[index] = item;
  FFAppState().downloadsList = list;
  FFAppState().update(() {});

  print("Cancel requested for fileId=$fileId");
}
				
			

deleteLocalFile

				
					import 'dart:io';

Future deleteLocalFile(String? filePath) async {
  if (filePath == null || filePath.isEmpty) {
    print("deleteLocalFile: filePath is null or empty");
    return false;
  }

  try {
    final file = File(filePath);

    if (!file.existsSync()) {
      print("deleteLocalFile: File does not exist -> $filePath");
      return false;
    }

    await file.delete();
    print("deleteLocalFile: File deleted -> $filePath");

    return true;
  } catch (e) {
    print("deleteLocalFile ERROR: $e");
    return false;
  }
}

				
			

openFolderPath

				
					import 'dart:io';
import 'package:path/path.dart' as p;

Future<void> openFolderPath(String filePath) async {
  try {
    final directory = Directory(p.dirname(filePath));

    if (!directory.existsSync()) {
      print("Folder does NOT exist → ${directory.path}");
      return;
    }

    print("Opening folder → ${directory.path}");

    if (Platform.isWindows) {
      // Opens Windows Explorer
      await Process.run('explorer', [directory.path.replaceAll('/', '\\')]);
    } else if (Platform.isMacOS) {
      // Opens Finder on macOS
      await Process.run('open', [directory.path]);
    } else if (Platform.isLinux) {
      // Opens default Linux file manager
      await Process.run('xdg-open', [directory.path]);
    } else {
      print("Folder opening not supported on this platform.");
    }
  } catch (e) {
    print("Error opening folder: $e");
  }
}



				
			

Custom Functions :

formatBytes

				
					String formatBytes(int bytes) {
  /// MODIFY CODE ONLY BELOW THIS LINE

  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";

  /// MODIFY CODE ONLY ABOVE THIS LINE
}
				
			

formatSpeed

				
					String formatSpeed(int bytesPerSec) {
  /// MODIFY CODE ONLY BELOW THIS LINE

  if (bytesPerSec < 1024) return "$bytesPerSec B/s";
  if (bytesPerSec < 1024 * 1024)
    return "${(bytesPerSec / 1024).toStringAsFixed(2)} KB/s";
  return "${(bytesPerSec / (1024 * 1024)).toStringAsFixed(2)} MB/s";

  /// MODIFY CODE ONLY ABOVE THIS LINE
}

				
			

formatPercentage

				
					String formatPercentage(double value) {
  /// MODIFY CODE ONLY BELOW THIS LINE

  try {
    // Convert decimal → percentage
    double percent = value * 100;

    // Format to 2 decimal places
    String formatted = percent.toStringAsFixed(2);

    return "$formatted%";
  } catch (e) {
    print("formatPercentage ERROR: $e");
    return "0%";
  }

  /// MODIFY CODE ONLY ABOVE THIS LINE
}
				
			

calculateRemainingTime

				
					String calculateRemainingTime(
  double totalSize,
  double downloaded,
  double speed,
) {
  /// MODIFY CODE ONLY BELOW THIS LINE

  try {
    if (speed <= 0) return "--"; // cannot calculate

    double remainingBytes = totalSize - downloaded;
    if (remainingBytes <= 0) return "0s";

    double secondsLeft = remainingBytes / speed;

    int sec = secondsLeft.round();

    int hours = sec ~/ 3600;
    sec %= 3600;
    int minutes = sec ~/ 60;
    int seconds = sec % 60;

    if (hours > 0) {
      return "${hours}h ${minutes}m ${seconds}s";
    } else if (minutes > 0) {
      return "${minutes}m ${seconds}s";
    } else {
      return "${seconds}s";
    }
  } catch (e) {
    print("calculateRemainingTime ERROR: $e");
    return "--";
  }

  /// MODIFY CODE ONLY ABOVE THIS LINE
}