199 lines
7.0 KiB
Dart
199 lines
7.0 KiB
Dart
import 'package:duration/duration.dart';
|
|
import 'package:duration/locale.dart';
|
|
import 'package:intl/intl.dart';
|
|
import '../i18n/strings.g.dart';
|
|
|
|
/// Formats a number with a minimum number of digits using leading zeros.
|
|
///
|
|
/// Example: `padNumber(5, 3)` returns "005"
|
|
String padNumber(int number, int width) {
|
|
return number.toString().padLeft(width, '0');
|
|
}
|
|
|
|
/// Utility class for formatting byte sizes and speeds
|
|
class ByteFormatter {
|
|
ByteFormatter._();
|
|
|
|
static const int _kb = 1024;
|
|
static const int _mb = _kb * 1024;
|
|
static const int _gb = _mb * 1024;
|
|
|
|
/// Format bytes to human-readable string (e.g., "1.5 GB", "256.3 MB")
|
|
///
|
|
/// [bytes] The number of bytes to format
|
|
/// [decimals] Number of decimal places (default: 1 for KB/MB, 2 for GB)
|
|
static String formatBytes(int bytes, {int? decimals}) {
|
|
if (bytes < _kb) return '$bytes B';
|
|
if (bytes < _mb) {
|
|
return '${(bytes / _kb).toStringAsFixed(decimals ?? 1)} KB';
|
|
}
|
|
if (bytes < _gb) {
|
|
return '${(bytes / _mb).toStringAsFixed(decimals ?? 1)} MB';
|
|
}
|
|
return '${(bytes / _gb).toStringAsFixed(decimals ?? 2)} GB';
|
|
}
|
|
|
|
/// Format speed in bytes per second to human-readable string
|
|
///
|
|
/// [bytesPerSecond] The speed in bytes per second
|
|
static String formatSpeed(double bytesPerSecond) {
|
|
if (bytesPerSecond < _kb) {
|
|
return '${bytesPerSecond.toStringAsFixed(0)} B/s';
|
|
}
|
|
if (bytesPerSecond < _mb) {
|
|
return '${(bytesPerSecond / _kb).toStringAsFixed(1)} KB/s';
|
|
}
|
|
return '${(bytesPerSecond / _mb).toStringAsFixed(1)} MB/s';
|
|
}
|
|
|
|
/// Format bitrate in kbps to human-readable string
|
|
///
|
|
/// [kbps] The bitrate in kilobits per second
|
|
static String formatBitrate(int kbps) {
|
|
if (kbps < 1000) return '$kbps kbps';
|
|
return '${(kbps / 1000).toStringAsFixed(1)} Mbps';
|
|
}
|
|
|
|
}
|
|
|
|
/// Formats a duration in human-readable textual format (e.g., "1h 23m" or "1 hour 23 minutes").
|
|
/// Uses localized unit names based on the current app locale.
|
|
/// Shows hours and minutes only (no seconds).
|
|
///
|
|
/// Used for: media cards, media details, playlists.
|
|
String formatDurationTextual(int milliseconds, {bool abbreviated = true}) {
|
|
final duration = Duration(milliseconds: milliseconds);
|
|
|
|
// Get the appropriate locale for the duration package
|
|
final durationLocale = _getDurationLocale();
|
|
|
|
// Format with abbreviated or full units (h, m) but no seconds
|
|
return prettyDuration(
|
|
duration,
|
|
abbreviated: abbreviated,
|
|
locale: durationLocale,
|
|
delimiter: abbreviated ? ' ' : ', ',
|
|
spacer: '',
|
|
// Configure to show only hours and minutes
|
|
tersity: DurationTersity.minute,
|
|
);
|
|
}
|
|
|
|
/// Formats a duration in human-readable textual format with seconds (e.g., "1h 23m 45s").
|
|
/// Uses localized unit names based on the current app locale.
|
|
/// Shows hours, minutes, and seconds.
|
|
///
|
|
/// Used for: sleep timer countdown.
|
|
String formatDurationWithSeconds(Duration duration) {
|
|
// Get the appropriate locale for the duration package
|
|
final durationLocale = _getDurationLocale();
|
|
|
|
// Format with abbreviated units (h, m, s) including seconds
|
|
return prettyDuration(
|
|
duration,
|
|
abbreviated: true,
|
|
locale: durationLocale,
|
|
delimiter: ' ',
|
|
spacer: '',
|
|
// Show all non-zero units
|
|
tersity: DurationTersity.second,
|
|
);
|
|
}
|
|
|
|
/// Formats a duration in timestamp format (e.g., "1:23:45" or "23:45").
|
|
/// This format is not localized as it follows universal digital clock conventions.
|
|
/// Shows H:MM:SS or M:SS depending on duration.
|
|
///
|
|
/// Used for: video controls, chapters, episode durations.
|
|
String formatDurationTimestamp(Duration duration) {
|
|
// Handle negative durations
|
|
final isNegative = duration.isNegative;
|
|
final absoluteDuration = duration.abs();
|
|
|
|
final hours = absoluteDuration.inHours;
|
|
final minutes = absoluteDuration.inMinutes.remainder(60);
|
|
final seconds = absoluteDuration.inSeconds.remainder(60);
|
|
|
|
final result = hours > 0
|
|
? '$hours:${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}'
|
|
: '$minutes:${seconds.toString().padLeft(2, '0')}';
|
|
|
|
return isNegative ? '-$result' : result;
|
|
}
|
|
|
|
/// Formats a sync offset in milliseconds with sign indicator (e.g., "+150ms", "-15.1s").
|
|
/// Shows milliseconds for values < 10s, decimal seconds for larger values.
|
|
///
|
|
/// Used for: audio sync sheet, sync offset controls.
|
|
String formatSyncOffset(double offsetMs) {
|
|
final sign = offsetMs >= 0 ? '+' : '-';
|
|
final absMs = offsetMs.abs().round();
|
|
final durationLocale = _getDurationLocale();
|
|
|
|
if (absMs >= 10000) {
|
|
// For values >= 10s, show decimal seconds (e.g., "+15.1s")
|
|
final seconds = (offsetMs.abs() / 1000).toStringAsFixed(1);
|
|
final unit = durationLocale.second(1, true);
|
|
return '$sign$seconds$unit';
|
|
}
|
|
|
|
// For values < 10s, show milliseconds (e.g., "+7300ms")
|
|
final unit = durationLocale.millisecond(1, true);
|
|
return '$sign$absMs$unit';
|
|
}
|
|
|
|
/// Gets the duration package locale based on the current app locale.
|
|
/// Falls back to English if the locale is not supported by the duration package.
|
|
DurationLocale _getDurationLocale() {
|
|
// Get the current locale from slang's LocaleSettings
|
|
final appLocale = LocaleSettings.currentLocale;
|
|
final languageCode = appLocale.languageCode;
|
|
|
|
// Map supported locales to duration package locales
|
|
// The duration package supports many languages, but we'll focus on the ones
|
|
// that our app supports: en, de, it, nl, sv, zh
|
|
try {
|
|
return DurationLocale.fromLanguageCode(languageCode) ?? const EnglishDurationLocale();
|
|
} catch (e) {
|
|
// Fallback to English if language code is not supported
|
|
return const EnglishDurationLocale();
|
|
}
|
|
}
|
|
|
|
/// Formats a [DateTime] as a clock time string, respecting the system 24-hour preference.
|
|
/// Uses `DateFormat.Hm` for 24-hour format and `DateFormat.jm` for 12-hour format.
|
|
String formatClockTime(DateTime time, {required bool is24Hour}) {
|
|
final locale = LocaleSettings.currentLocale.languageCode;
|
|
final formatter = is24Hour ? DateFormat.Hm(locale) : DateFormat.jm(locale);
|
|
return formatter.format(time);
|
|
}
|
|
|
|
/// Formats the clock time at which media will finish playing, given the remaining duration.
|
|
/// Returns a localized time string like "6:12 PM" or "18:12" depending on system setting.
|
|
String formatFinishTime(Duration remaining, {double rate = 1.0, required bool is24Hour}) {
|
|
final adjustedRemaining = remaining * (1.0 / rate);
|
|
final finishTime = DateTime.now().add(adjustedRemaining);
|
|
return formatClockTime(finishTime, is24Hour: is24Hour);
|
|
}
|
|
|
|
/// Takes a list of strings and returns one long string with each item in the list concatenated by a bullet
|
|
String toBulletedString(List<String> parts) {
|
|
return parts.join(' · ');
|
|
}
|
|
|
|
/// Takes a date string in the format "YYYY-MM-DD" and returns a localized full date string
|
|
/// If there is any error, `dateString` is returned as is
|
|
String formatFullDate(String dateString) {
|
|
try {
|
|
// Parse the date
|
|
final date = DateTime.parse(dateString);
|
|
|
|
// Create a DateFormat with the full date pattern for the current locale
|
|
final formatter = DateFormat.yMMMMd(LocaleSettings.currentLocale.languageCode);
|
|
|
|
return formatter.format(date);
|
|
} catch (e) {
|
|
return dateString;
|
|
}
|
|
}
|