mirror of
https://github.com/localsend/localsend.git
synced 2026-06-23 04:10:07 +00:00
feat: improve remaining time display during file transfer (#2765)
This commit is contained in:
@@ -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": {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user