l10n: number format

This commit is contained in:
Thibault Deckers 2021-12-01 17:25:42 +09:00
parent 2628192e06
commit 94659ae8af
14 changed files with 105 additions and 102 deletions

View file

@ -1,20 +1,23 @@
import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart';
import 'package:intl/intl.dart';
class OverlayMetadata {
final String? aperture, exposureTime, focalLength, iso;
@immutable
class OverlayMetadata extends Equatable {
final double? aperture, focalLength;
final String? exposureTime;
final int? iso;
static final apertureFormat = NumberFormat('0.0', 'en_US');
static final focalLengthFormat = NumberFormat('0.#', 'en_US');
@override
List<Object?> get props => [aperture, exposureTime, focalLength, iso];
OverlayMetadata({
double? aperture,
bool get isEmpty => aperture == null && exposureTime == null && focalLength == null && iso == null;
const OverlayMetadata({
this.aperture,
this.exposureTime,
double? focalLength,
int? iso,
}) : aperture = aperture != null ? 'ƒ/${apertureFormat.format(aperture)}' : null,
focalLength = focalLength != null ? '${focalLengthFormat.format(focalLength)} mm' : null,
iso = iso != null ? 'ISO$iso' : null;
this.focalLength,
this.iso,
});
factory OverlayMetadata.fromMap(Map map) {
return OverlayMetadata(
@ -24,9 +27,4 @@ class OverlayMetadata {
iso: map['iso'] as int?,
);
}
bool get isEmpty => aperture == null && exposureTime == null && focalLength == null && iso == null;
@override
String toString() => '$runtimeType#${shortHash(this)}{aperture=$aperture, exposureTime=$exposureTime, focalLength=$focalLength, iso=$iso}';
}

View file

@ -2,6 +2,7 @@ import 'package:aves/utils/geo_utils.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:intl/intl.dart';
import 'package:latlong2/latlong.dart';
import 'enums.dart';
@ -16,24 +17,36 @@ extension ExtraCoordinateFormat on CoordinateFormat {
}
}
static const _separator = ', ';
String format(AppLocalizations l10n, LatLng latLng, {bool minuteSecondPadding = false, int dmsSecondDecimals = 2}) {
switch (this) {
case CoordinateFormat.dms:
return toDMS(l10n, latLng, minuteSecondPadding: minuteSecondPadding, secondDecimals: dmsSecondDecimals).join(', ');
return toDMS(l10n, latLng, minuteSecondPadding: minuteSecondPadding, secondDecimals: dmsSecondDecimals).join(_separator);
case CoordinateFormat.decimal:
return [latLng.latitude, latLng.longitude].map((n) => n.toStringAsFixed(6)).join(', ');
return _toDecimal(l10n, latLng).join(_separator);
}
}
// returns coordinates formatted as DMS, e.g. ['41° 24 12.2″ N', '2° 10 26.5″ E']
static List<String> toDMS(AppLocalizations l10n, LatLng latLng, {bool minuteSecondPadding = false, int secondDecimals = 2}) {
final locale = l10n.localeName;
final lat = latLng.latitude;
final lng = latLng.longitude;
final latSexa = GeoUtils.decimal2sexagesimal(lat, minuteSecondPadding, secondDecimals);
final lngSexa = GeoUtils.decimal2sexagesimal(lng, minuteSecondPadding, secondDecimals);
final latSexa = GeoUtils.decimal2sexagesimal(lat, minuteSecondPadding, secondDecimals, locale);
final lngSexa = GeoUtils.decimal2sexagesimal(lng, minuteSecondPadding, secondDecimals, locale);
return [
l10n.coordinateDms(latSexa, lat < 0 ? l10n.coordinateDmsSouth : l10n.coordinateDmsNorth),
l10n.coordinateDms(lngSexa, lng < 0 ? l10n.coordinateDmsWest : l10n.coordinateDmsEast),
];
}
static List<String> _toDecimal(AppLocalizations l10n, LatLng latLng) {
final locale = l10n.localeName;
final formatter = NumberFormat('0.000000°', locale);
return [
formatter.format(latLng.latitude),
formatter.format(latLng.longitude),
];
}
}

View file

@ -374,7 +374,7 @@ class VideoMetadataFormatter {
static String _formatFilesize(String value) {
final size = int.tryParse(value);
return size != null ? formatFilesize(size) : value;
return size != null ? formatFileSize('en_US', size) : value;
}
static String _formatLanguage(String value) {
@ -399,20 +399,10 @@ class VideoMetadataFormatter {
if (parsed == null) return size;
size = parsed;
}
const divider = 1000;
if (size < divider) return '$size $unit';
if (size < divider * divider && size % divider == 0) {
return '${(size / divider).toStringAsFixed(0)} K$unit';
}
if (size < divider * divider) {
return '${(size / divider).toStringAsFixed(round)} K$unit';
}
if (size < divider * divider * divider && size % divider == 0) {
return '${(size / (divider * divider)).toStringAsFixed(0)} M$unit';
}
if (size < divider * divider) return '${(size / divider).toStringAsFixed(round)} K$unit';
return '${(size / divider / divider).toStringAsFixed(round)} M$unit';
}
}

View file

@ -1,38 +1,16 @@
String formatFilesize(int size, {int round = 2}) {
var divider = 1024;
import 'package:intl/intl.dart';
if (size < divider) return '$size B';
const _kiloDivider = 1024;
const _megaDivider = _kiloDivider * _kiloDivider;
const _gigaDivider = _megaDivider * _kiloDivider;
const _teraDivider = _gigaDivider * _kiloDivider;
if (size < divider * divider && size % divider == 0) {
return '${(size / divider).toStringAsFixed(0)} KB';
}
if (size < divider * divider) {
return '${(size / divider).toStringAsFixed(round)} KB';
}
String formatFileSize(String locale, int size, {int round = 2}) {
if (size < _kiloDivider) return '$size B';
if (size < divider * divider * divider && size % divider == 0) {
return '${(size / (divider * divider)).toStringAsFixed(0)} MB';
}
if (size < divider * divider * divider) {
return '${(size / divider / divider).toStringAsFixed(round)} MB';
}
if (size < divider * divider * divider * divider && size % divider == 0) {
return '${(size / (divider * divider * divider)).toStringAsFixed(0)} GB';
}
if (size < divider * divider * divider * divider) {
return '${(size / divider / divider / divider).toStringAsFixed(round)} GB';
}
if (size < divider * divider * divider * divider * divider && size % divider == 0) {
return '${(size / divider / divider / divider / divider).toStringAsFixed(0)} TB';
}
if (size < divider * divider * divider * divider * divider) {
return '${(size / divider / divider / divider / divider).toStringAsFixed(round)} TB';
}
if (size < divider * divider * divider * divider * divider * divider && size % divider == 0) {
return '${(size / divider / divider / divider / divider / divider).toStringAsFixed(0)} PB';
}
return '${(size / divider / divider / divider / divider / divider).toStringAsFixed(round)} PB';
final formatter = NumberFormat('0${round > 0 ? '.${'0' * round}' : ''}', locale);
if (size < _megaDivider) return '${formatter.format(size / _kiloDivider)} KB';
if (size < _gigaDivider) return '${formatter.format(size / _megaDivider)} MB';
if (size < _teraDivider) return '${formatter.format(size / _gigaDivider)} GB';
return '${formatter.format(size / _teraDivider)} TB';
}

View file

@ -1,33 +1,23 @@
import 'dart:math';
import 'package:aves/utils/math_utils.dart';
import 'package:intl/intl.dart';
import 'package:latlong2/latlong.dart';
class GeoUtils {
static String decimal2sexagesimal(final double degDecimal, final bool minuteSecondPadding, final int secondDecimals) {
List<int> _split(final double value) {
// NumberFormat is necessary to create digit after comma if the value
// has no decimal point (only necessary for browser)
final tmp = NumberFormat('0.0#####').format(roundToPrecision(value, decimals: 10)).split('.');
return <int>[
int.parse(tmp[0]).abs(),
int.parse(tmp[1]),
];
}
final deg = _split(degDecimal)[0];
final minDecimal = (degDecimal.abs() - deg) * 60;
final min = _split(minDecimal)[0];
static String decimal2sexagesimal(
double degDecimal,
bool minuteSecondPadding,
int secondDecimals,
String locale,
) {
final degAbs = degDecimal.abs();
final deg = degAbs.toInt();
final minDecimal = (degAbs - deg) * 60;
final min = minDecimal.toInt();
final sec = (minDecimal - min) * 60;
final secRounded = roundToPrecision(sec, decimals: secondDecimals);
var minText = '$min';
var secText = secRounded.toStringAsFixed(secondDecimals);
if (minuteSecondPadding) {
minText = minText.padLeft(2, '0');
secText = secText.padLeft(secondDecimals > 0 ? 3 + secondDecimals : 2, '0');
}
var minText = NumberFormat('0' * (minuteSecondPadding ? 2 : 1), locale).format(min);
var secText = NumberFormat('${'0' * (minuteSecondPadding ? 2 : 1)}${secondDecimals > 0 ? '.${'0' * secondDecimals}' : ''}', locale).format(sec);
return '$deg° $minText $secText';
}

View file

@ -3,6 +3,7 @@ import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/model/source/collection_source.dart';
import 'package:aves/model/source/enums.dart';
import 'package:aves/utils/file_utils.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/grid/draggable_thumb_label.dart';
import 'package:aves/widgets/common/grid/section_layout.dart';
import 'package:flutter/material.dart';
@ -48,7 +49,7 @@ class CollectionDraggableThumbLabel extends StatelessWidget {
];
case EntrySortFactor.size:
return [
if (entry.sizeBytes != null) formatFilesize(entry.sizeBytes!, round: 0),
if (entry.sizeBytes != null) formatFileSize(context.l10n.localeName, entry.sizeBytes!, round: 0),
];
}
},

View file

@ -75,13 +75,15 @@ mixin SizeAwareMixin {
await showDialog(
context: context,
builder: (context) {
final neededSize = formatFilesize(needed);
final freeSize = formatFilesize(free);
final l10n = context.l10n;
final locale = l10n.localeName;
final neededSize = formatFileSize(locale, needed);
final freeSize = formatFileSize(locale, free);
final volume = destinationVolume.getDescription(context);
return AvesDialog(
context: context,
title: context.l10n.notEnoughSpaceDialogTitle,
content: Text(context.l10n.notEnoughSpaceDialogMessage(neededSize, freeSize, volume)),
title: l10n.notEnoughSpaceDialogTitle,
content: Text(l10n.notEnoughSpaceDialogMessage(neededSize, freeSize, volume)),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),

View file

@ -25,7 +25,7 @@ class _DebugCacheSectionState extends State<DebugCacheSection> with AutomaticKee
Row(
children: [
Expanded(
child: Text('Image cache:\n\t${imageCache!.currentSize}/${imageCache!.maximumSize} items\n\t${formatFilesize(imageCache!.currentSizeBytes)}/${formatFilesize(imageCache!.maximumSizeBytes)}'),
child: Text('Image cache:\n\t${imageCache!.currentSize}/${imageCache!.maximumSize} items\n\t${formatFileSize('en_US', imageCache!.currentSizeBytes)}/${formatFileSize('en_US', imageCache!.maximumSizeBytes)}'),
),
const SizedBox(width: 8),
ElevatedButton(

View file

@ -53,7 +53,7 @@ class _DebugAppDatabaseSectionState extends State<DebugAppDatabaseSection> with
return Row(
children: [
Expanded(
child: Text('DB file size: ${formatFilesize(snapshot.data!)}'),
child: Text('DB file size: ${formatFileSize('en_US', snapshot.data!)}'),
),
const SizedBox(width: 8),
ElevatedButton(

View file

@ -46,7 +46,7 @@ class _DebugStorageSectionState extends State<DebugStorageSection> with Automati
'isPrimary': '${v.isPrimary}',
'isRemovable': '${v.isRemovable}',
'state': v.state,
if (freeSpace != null) 'freeSpace': formatFilesize(freeSpace),
if (freeSpace != null) 'freeSpace': formatFileSize('en_US', freeSpace),
},
),
),

View file

@ -60,7 +60,7 @@ class BasicSection extends StatelessWidget {
final date = entry.bestDate;
final dateText = date != null ? formatDateTime(date, locale, use24hour) : infoUnknown;
final showResolution = !entry.isSvg && entry.isSized;
final sizeText = entry.sizeBytes != null ? formatFilesize(entry.sizeBytes!) : infoUnknown;
final sizeText = entry.sizeBytes != null ? formatFileSize(locale, entry.sizeBytes!) : infoUnknown;
final path = entry.path;
return Column(

View file

@ -20,6 +20,7 @@ import 'package:aves/widgets/viewer/page_entry_builder.dart';
import 'package:decorated_icon/decorated_icon.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
import 'package:tuple/tuple.dart';
@ -423,14 +424,25 @@ class _ShootingRow extends StatelessWidget {
@override
Widget build(BuildContext context) {
final locale = context.l10n.localeName;
final aperture = details.aperture;
final apertureText = aperture != null ? 'ƒ/${NumberFormat('0.0', locale).format(aperture)}' : Constants.overlayUnknown;
final focalLength = details.focalLength;
final focalLengthText = focalLength != null ? '${NumberFormat('0.#', locale).format(focalLength)} mm' : Constants.overlayUnknown;
final iso = details.iso;
final isoText = iso != null ? 'ISO$iso' : Constants.overlayUnknown;
return Row(
children: [
const DecoratedIcon(AIcons.shooting, shadows: Constants.embossShadows, size: _iconSize),
const SizedBox(width: _iconPadding),
Expanded(child: Text(details.aperture ?? Constants.overlayUnknown, strutStyle: Constants.overflowStrutStyle)),
Expanded(child: Text(apertureText, strutStyle: Constants.overflowStrutStyle)),
Expanded(child: Text(details.exposureTime ?? Constants.overlayUnknown, strutStyle: Constants.overflowStrutStyle)),
Expanded(child: Text(details.focalLength ?? Constants.overlayUnknown, strutStyle: Constants.overflowStrutStyle)),
Expanded(child: Text(details.iso ?? Constants.overlayUnknown, strutStyle: Constants.overflowStrutStyle)),
Expanded(child: Text(focalLengthText, strutStyle: Constants.overflowStrutStyle)),
Expanded(child: Text(isoText, strutStyle: Constants.overflowStrutStyle)),
],
);
}

View file

@ -0,0 +1,16 @@
import 'package:aves/model/settings/coordinate_format.dart';
import 'package:aves/utils/file_utils.dart';
import 'package:aves/utils/geo_utils.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:latlong2/latlong.dart';
import 'package:test/test.dart';
void main() {
test('format file size', () {
final l10n = lookupAppLocalizations(AppLocalizations.supportedLocales.first);
final locale = l10n.localeName;
expect(formatFileSize(locale, 1024), '1.00 KB');
expect(formatFileSize(locale, 1536), '1.50 KB');
expect(formatFileSize(locale, 1073741824), '1.00 GB');
});
}

View file

@ -12,6 +12,9 @@ void main() {
expect(ExtraCoordinateFormat.toDMS(l10n, LatLng(-38.6965891, 175.9830047)), ['38° 41 47.72″ S', '175° 58 58.82″ E']); // Taupo
expect(ExtraCoordinateFormat.toDMS(l10n, LatLng(-64.249391, -56.6556145)), ['64° 14 57.81″ S', '56° 39 20.21″ W']); // Marambio
expect(ExtraCoordinateFormat.toDMS(l10n, LatLng(0, 0)), ['0° 0 0.00″ N', '0° 0 0.00″ E']);
expect(ExtraCoordinateFormat.toDMS(l10n, LatLng(0, 0), minuteSecondPadding: true), ['0° 00 00.00″ N', '0° 00 00.00″ E']);
expect(ExtraCoordinateFormat.toDMS(l10n, LatLng(0, 0), secondDecimals: 0), ['0° 0 0″ N', '0° 0 0″ E']);
expect(ExtraCoordinateFormat.toDMS(l10n, LatLng(0, 0), secondDecimals: 4), ['0° 0 0.0000″ N', '0° 0 0.0000″ E']);
});
test('bounds center', () {