feat: improve remaining time display during file transfer (#2765)

This commit is contained in:
Shlomo
2025-10-15 19:14:47 +03:00
committed by GitHub
parent 4305868970
commit 07650f4643
5 changed files with 115 additions and 9 deletions
+8
View File
@@ -239,6 +239,14 @@
"count": "Files: {curr} / {n}",
"size": "Size: {curr} / {n}",
"speed": "Speed: {speed}/s"
},
"remainingTime": {
"seconds": "{n}:{ss}",
"minutes": "{n}:{ss}",
"hours": "{h}h {m}m",
"days": "{d}d {h}h {m}m",
"@hours": "Use 'h' for hours abbreviation and 'm' for minutes",
"@days": "Use 'd' for days, 'h' for hours, and 'm' for minutes"
}
},
"webSharePage": {
+1 -1
View File
@@ -4,7 +4,7 @@
/// To regenerate, run: `dart run slang`
///
/// Locales: 53
/// Strings: 16908 (319 per locale)
/// Strings: 16912 (319 per locale)
// coverage:ignore-file
// ignore_for_file: type=lint, unused_import
+18
View File
@@ -281,6 +281,7 @@ class TranslationsProgressPageEn {
String get titleReceiving => 'Receiving files';
String get savedToGallery => 'Saved in Photos';
late final TranslationsProgressPageTotalEn total = TranslationsProgressPageTotalEn.internal(_root);
late final TranslationsProgressPageRemainingTimeEn remainingTime = TranslationsProgressPageRemainingTimeEn.internal(_root);
}
// Path: webSharePage
@@ -763,6 +764,23 @@ class TranslationsProgressPageTotalEn {
String speed({required Object speed}) => 'Speed: ${speed}/s';
}
// Path: progressPage.remainingTime
class TranslationsProgressPageRemainingTimeEn {
TranslationsProgressPageRemainingTimeEn.internal(this._root);
final Translations _root; // ignore: unused_field
// Translations
String seconds({required Object n, required Object ss}) => '${n}:${ss}';
String minutes({required Object n, required Object ss}) => '${n}:${ss}';
/// Use 'h' for hours abbreviation and 'm' for minutes
String hours({required Object h, required Object m}) => '${h}h ${m}m';
/// Use 'd' for days, 'h' for hours, and 'm' for minutes
String days({required Object d, required Object h, required Object m}) => '${d}d ${h}h ${m}m';
}
// Path: dialogs.addFile
class TranslationsDialogsAddFileEn {
TranslationsDialogsAddFileEn.internal(this._root);
+31 -8
View File
@@ -1,25 +1,48 @@
/// Returns bytes per second
import 'package:localsend_app/gen/strings.g.dart';
const _millisecondsPerSecond = 1000;
const _secondsPerMinute = 60;
const _secondsPerHour = 3600;
const _secondsPerDay = 86400;
int getFileSpeed({
required int start,
required int end,
required int bytes,
}) {
final deltaTime = end - start;
return (1000 * bytes) ~/ deltaTime; // multiply by 1000 to convert millis to seconds
return (_millisecondsPerSecond * bytes) ~/ deltaTime;
}
/// Returns remaining time in m:ss
String getRemainingTime({
required int bytesPerSeconds,
required int remainingBytes,
}) {
final totalSeconds = _getRemainingTime(bytesPerSeconds: bytesPerSeconds, remainingBytes: remainingBytes);
final minutes = totalSeconds ~/ 60;
final seconds = totalSeconds % 60;
return '$minutes:${seconds.toString().padLeft(2, '0')}';
if (bytesPerSeconds == 0) {
return remainingBytes == 0 ? t.progressPage.remainingTime.seconds(n: 0, ss: '00') : '';
}
final remainingTimeInSeconds = _getRemainingTime(bytesPerSeconds: bytesPerSeconds, remainingBytes: remainingBytes);
if (remainingTimeInSeconds < _secondsPerMinute) {
return t.progressPage.remainingTime.seconds(n: 0, ss: remainingTimeInSeconds.toString().padLeft(2, '0'));
} else if (remainingTimeInSeconds < _secondsPerHour) {
final minutes = remainingTimeInSeconds ~/ _secondsPerMinute;
final seconds = remainingTimeInSeconds % _secondsPerMinute;
return t.progressPage.remainingTime.minutes(n: minutes, ss: seconds.toString().padLeft(2, '0'));
} else if (remainingTimeInSeconds < _secondsPerDay) {
final hours = remainingTimeInSeconds ~/ _secondsPerHour;
final minutes = (remainingTimeInSeconds % _secondsPerHour) ~/ _secondsPerMinute;
return t.progressPage.remainingTime.hours(h: hours, m: minutes);
} else {
final days = remainingTimeInSeconds ~/ _secondsPerDay;
final remainingAfterDays = remainingTimeInSeconds % _secondsPerDay;
final hours = remainingAfterDays ~/ _secondsPerHour;
final minutes = (remainingAfterDays % _secondsPerHour) ~/ _secondsPerMinute;
return t.progressPage.remainingTime.days(d: days, h: hours, m: minutes);
}
}
/// Returns remaining time in seconds
int _getRemainingTime({
required int bytesPerSeconds,
required int remainingBytes,
@@ -0,0 +1,57 @@
import 'package:localsend_app/util/file_speed_helper.dart';
import 'package:test/test.dart';
void main() {
group('getRemainingTime', () {
test('shows seconds for duration less than 1 minute', () {
expect(getRemainingTime(bytesPerSeconds: 1000, remainingBytes: 30000), '0:30');
expect(getRemainingTime(bytesPerSeconds: 1000, remainingBytes: 45000), '0:45');
expect(getRemainingTime(bytesPerSeconds: 1000, remainingBytes: 5000), '0:05');
});
test('shows minutes and seconds for duration less than 1 hour', () {
expect(getRemainingTime(bytesPerSeconds: 1000, remainingBytes: 90000), '1:30');
expect(getRemainingTime(bytesPerSeconds: 1000, remainingBytes: 600000), '10:00');
expect(getRemainingTime(bytesPerSeconds: 1000, remainingBytes: 3540000), '59:00');
});
test('shows hours and minutes for duration 1 hour or more but less than 1 day', () {
expect(getRemainingTime(bytesPerSeconds: 1000, remainingBytes: 3600000), '1h 0m');
expect(getRemainingTime(bytesPerSeconds: 1000, remainingBytes: 3660000), '1h 1m');
expect(getRemainingTime(bytesPerSeconds: 1000, remainingBytes: 7200000), '2h 0m');
expect(getRemainingTime(bytesPerSeconds: 1000, remainingBytes: 12000000), '3h 20m');
expect(getRemainingTime(bytesPerSeconds: 1000, remainingBytes: 86340000), '23h 59m');
});
test('shows days, hours and minutes for duration 1 day or more', () {
expect(getRemainingTime(bytesPerSeconds: 1000, remainingBytes: 86400000), '1d 0h 0m');
expect(getRemainingTime(bytesPerSeconds: 1000, remainingBytes: 90000000), '1d 1h 0m');
expect(getRemainingTime(bytesPerSeconds: 1000, remainingBytes: 93660000), '1d 2h 1m');
expect(getRemainingTime(bytesPerSeconds: 1000, remainingBytes: 172800000), '2d 0h 0m');
expect(getRemainingTime(bytesPerSeconds: 1000, remainingBytes: 183780000), '2d 3h 3m');
});
test('handles zero remaining bytes', () {
expect(getRemainingTime(bytesPerSeconds: 1000, remainingBytes: 0), '0:00');
expect(getRemainingTime(bytesPerSeconds: 0, remainingBytes: 0), '0:00');
});
test('handles zero speed with remaining bytes', () {
expect(getRemainingTime(bytesPerSeconds: 0, remainingBytes: 1000), '');
expect(getRemainingTime(bytesPerSeconds: 0, remainingBytes: 1000000), '');
});
test('handles edge cases', () {
expect(getRemainingTime(bytesPerSeconds: 1000, remainingBytes: 1000), '0:01');
expect(getRemainingTime(bytesPerSeconds: 1, remainingBytes: 1), '0:01');
});
});
group('getFileSpeed', () {
test('calculates bytes per second correctly', () {
expect(getFileSpeed(start: 0, end: 1000, bytes: 1000), 1000);
expect(getFileSpeed(start: 0, end: 2000, bytes: 4000), 2000);
expect(getFileSpeed(start: 1000, end: 3000, bytes: 10000), 5000);
});
});
}