[feat]: Add download speed and progress details to downloads

This commit is contained in:
nahnah
2026-01-24 22:49:32 +00:00
parent 011b3ccb1c
commit d2870b348a
4 changed files with 81 additions and 17 deletions
+52 -2
View File
@@ -19,6 +19,9 @@ class DownloadInfo {
final double progress;
final String? filePath;
final String? error;
final int bytesDownloaded;
final int totalBytes;
final double downloadSpeed; // bytes per second
const DownloadInfo({
required this.packageName,
@@ -27,6 +30,9 @@ class DownloadInfo {
this.progress = 0.0,
this.filePath,
this.error,
this.bytesDownloaded = 0,
this.totalBytes = 0,
this.downloadSpeed = 0.0,
});
DownloadInfo copyWith({
@@ -34,6 +40,9 @@ class DownloadInfo {
double? progress,
String? filePath,
String? error,
int? bytesDownloaded,
int? totalBytes,
double? downloadSpeed,
}) {
return DownloadInfo(
packageName: packageName,
@@ -42,10 +51,25 @@ class DownloadInfo {
progress: progress ?? this.progress,
filePath: filePath ?? this.filePath,
error: error ?? this.error,
bytesDownloaded: bytesDownloaded ?? this.bytesDownloaded,
totalBytes: totalBytes ?? this.totalBytes,
downloadSpeed: downloadSpeed ?? this.downloadSpeed,
);
}
String get key => '${packageName}_$versionName';
String get formattedBytesDownloaded => _formatBytes(bytesDownloaded);
String get formattedTotalBytes => _formatBytes(totalBytes);
String get formattedSpeed => '${_formatBytes(downloadSpeed.toInt())}/s';
static String _formatBytes(int bytes) {
if (bytes < 1024) return '$bytes B';
if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(1)} KB';
if (bytes < 1024 * 1024 * 1024)
return '${(bytes / (1024 * 1024)).toStringAsFixed(1)} MB';
return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(2)} GB';
}
}
class DownloadProvider extends ChangeNotifier {
@@ -197,13 +221,39 @@ class DownloadProvider extends ChangeNotifier {
maxProgress: 100,
);
// Track download speed with 5-second sampling intervals
int lastSpeedBytes = 0;
DateTime lastSpeedTime = DateTime.now();
double currentSpeed = 0.0;
try {
final filePath = await _apiService.downloadApk(
version,
app.packageName,
app.repositoryUrl,
onProgress: (progress) {
_downloads[key] = _downloads[key]!.copyWith(progress: progress);
onProgress: (received, total) {
final now = DateTime.now();
final timeSinceLastSpeedUpdate =
now.difference(lastSpeedTime).inMilliseconds / 1000.0;
// Update speed every 5 seconds for smoother display
if (timeSinceLastSpeedUpdate >= 1.0) {
final bytesDiff = received - lastSpeedBytes;
if (timeSinceLastSpeedUpdate > 0) {
currentSpeed = bytesDiff / timeSinceLastSpeedUpdate;
}
lastSpeedBytes = received;
lastSpeedTime = now;
}
final progress = total > 0 ? received / total : 0.0;
_downloads[key] = _downloads[key]!.copyWith(
progress: progress,
bytesDownloaded: received,
totalBytes: total,
downloadSpeed: currentSpeed,
);
notifyListeners();
// Update notification with progress
+26 -5
View File
@@ -803,12 +803,33 @@ class _DownloadSectionState extends State<_DownloadSection> {
),
if (isDownloading) ...[
const SizedBox(height: 16),
Text(
'Downloading... ${(progress * 100).toInt()}%',
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Downloading... ${(progress * 100).toInt()}%',
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
if (downloadInfo != null && downloadInfo.totalBytes > 0)
Text(
'${downloadInfo.formattedBytesDownloaded} / ${downloadInfo.formattedTotalBytes}',
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
],
),
const SizedBox(height: 4),
if (downloadInfo != null)
Text(
downloadInfo.formattedSpeed,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.primary,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 8),
LinearProgressIndicator(value: progress, year2023: false),
],
+1 -8
View File
@@ -101,11 +101,7 @@ class _RepositoriesScreenState extends State<RepositoriesScreen> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Presets',
style: Theme.of(context).textTheme.titleSmall,
),
const SizedBox(height: 8),
MListHeader(title: 'Preset'),
MListViewBuilder(
itemCount: _presets.length,
itemBuilder: (index) {
@@ -181,9 +177,6 @@ class _RepositoriesScreenState extends State<RepositoriesScreen> {
);
},
),
const SizedBox(height: 8),
const Divider(),
const SizedBox(height: 8),
],
),
),
+2 -2
View File
@@ -518,7 +518,7 @@ class FDroidApiService {
FDroidVersion version,
String packageName,
String repositoryUrl, {
Function(double)? onProgress,
Function(int received, int total)? onProgress,
CancelToken? cancelToken,
}) async {
try {
@@ -549,7 +549,7 @@ class FDroidApiService {
filePath,
onReceiveProgress: (received, total) {
if (total > 0 && onProgress != null) {
onProgress(received / total);
onProgress(received, total);
}
},
cancelToken: token,