diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index e6feef825..581201d0a 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -176,6 +176,25 @@ "@coordinateFormatDms": {}, "coordinateFormatDecimal": "Decimal degrees", "@coordinateFormatDecimal": {}, + "coordinateDms": "{coordinate} {direction}", + "@coordinateDms": { + "placeholders": { + "coordinate": { + "type": "String" + }, + "direction": { + "type": "String" + } + } + }, + "coordinateDmsNorth": "N", + "@coordinateDmsNorth": {}, + "coordinateDmsSouth": "S", + "@coordinateDmsSouth": {}, + "coordinateDmsEast": "E", + "@coordinateDmsEast": {}, + "coordinateDmsWest": "W", + "@coordinateDmsWest": {}, "unitSystemMetric": "Metric", "@unitSystemMetric": {}, diff --git a/lib/l10n/app_ko.arb b/lib/l10n/app_ko.arb index 93b061629..2dc6783a7 100644 --- a/lib/l10n/app_ko.arb +++ b/lib/l10n/app_ko.arb @@ -87,6 +87,11 @@ "coordinateFormatDms": "도분초", "coordinateFormatDecimal": "소수점", + "coordinateDms": "{direction} {coordinate}", + "coordinateDmsNorth": "북위", + "coordinateDmsSouth": "남위", + "coordinateDmsEast": "동경", + "coordinateDmsWest": "서경", "unitSystemMetric": "미터법", "unitSystemImperial": "야드파운드법", diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index d68e9bc10..16b7a475d 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -87,6 +87,11 @@ "coordinateFormatDms": "Градусы, минуты и секунды", "coordinateFormatDecimal": "Десятичные градусы", + "coordinateDms": "{coordinate} {direction}", + "coordinateDmsNorth": "с. ш.", + "coordinateDmsSouth": "ю. ш.", + "coordinateDmsEast": "в. д.", + "coordinateDmsWest": "з. д.", "unitSystemMetric": "Метрические", "unitSystemImperial": "Имперские", diff --git a/lib/model/filters/coordinate.dart b/lib/model/filters/coordinate.dart index fed0f5702..5f733ffc3 100644 --- a/lib/model/filters/coordinate.dart +++ b/lib/model/filters/coordinate.dart @@ -4,8 +4,10 @@ import 'package:aves/model/settings/enums.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/utils/geo_utils.dart'; +import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:latlong2/latlong.dart'; import 'package:provider/provider.dart'; @@ -37,8 +39,9 @@ class CoordinateFilter extends CollectionFilter { @override EntryFilter get test => (entry) => GeoUtils.contains(sw, ne, entry.latLng); - String _formatBounds(CoordinateFormat format) { + String _formatBounds(AppLocalizations l10n, CoordinateFormat format) { String s(LatLng latLng) => format.format( + l10n, latLng, minuteSecondPadding: minuteSecondPadding, dmsSecondDecimals: 0, @@ -47,10 +50,10 @@ class CoordinateFilter extends CollectionFilter { } @override - String get universalLabel => _formatBounds(CoordinateFormat.decimal); + String get universalLabel => _formatBounds(lookupAppLocalizations(AppLocalizations.supportedLocales.first), CoordinateFormat.decimal); @override - String getLabel(BuildContext context) => _formatBounds(context.read().coordinateFormat); + String getLabel(BuildContext context) => _formatBounds(context.l10n, context.read().coordinateFormat); @override Widget iconBuilder(BuildContext context, double size, {bool showGenericIcon = true, bool embossed = false}) => Icon(AIcons.geoBounds, size: size); diff --git a/lib/model/settings/coordinate_format.dart b/lib/model/settings/coordinate_format.dart index b65619a06..5a65e6818 100644 --- a/lib/model/settings/coordinate_format.dart +++ b/lib/model/settings/coordinate_format.dart @@ -1,6 +1,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:latlong2/latlong.dart'; import 'enums.dart'; @@ -15,12 +16,24 @@ extension ExtraCoordinateFormat on CoordinateFormat { } } - String format(LatLng latLng, {bool minuteSecondPadding = false, int dmsSecondDecimals = 2}) { + String format(AppLocalizations l10n, LatLng latLng, {bool minuteSecondPadding = false, int dmsSecondDecimals = 2}) { switch (this) { case CoordinateFormat.dms: - return GeoUtils.toDMS(latLng, minuteSecondPadding: minuteSecondPadding, secondDecimals: dmsSecondDecimals).join(', '); + return toDMS(l10n, latLng, minuteSecondPadding: minuteSecondPadding, secondDecimals: dmsSecondDecimals).join(', '); case CoordinateFormat.decimal: return [latLng.latitude, latLng.longitude].map((n) => n.toStringAsFixed(6)).join(', '); } } + + // returns coordinates formatted as DMS, e.g. ['41° 24′ 12.2″ N', '2° 10′ 26.5″ E'] + static List toDMS(AppLocalizations l10n, LatLng latLng, {bool minuteSecondPadding = false, int secondDecimals = 2}) { + final lat = latLng.latitude; + final lng = latLng.longitude; + final latSexa = GeoUtils.decimal2sexagesimal(lat, minuteSecondPadding, secondDecimals); + final lngSexa = GeoUtils.decimal2sexagesimal(lng, minuteSecondPadding, secondDecimals); + return [ + l10n.coordinateDms(latSexa, lat < 0 ? l10n.coordinateDmsSouth : l10n.coordinateDmsNorth), + l10n.coordinateDms(lngSexa, lng < 0 ? l10n.coordinateDmsWest : l10n.coordinateDmsEast), + ]; + } } diff --git a/lib/utils/geo_utils.dart b/lib/utils/geo_utils.dart index 64d8ed44d..7316abf5f 100644 --- a/lib/utils/geo_utils.dart +++ b/lib/utils/geo_utils.dart @@ -5,7 +5,7 @@ import 'package:intl/intl.dart'; import 'package:latlong2/latlong.dart'; class GeoUtils { - static String _decimal2sexagesimal(final double degDecimal, final bool minuteSecondPadding, final int secondDecimals) { + static String decimal2sexagesimal(final double degDecimal, final bool minuteSecondPadding, final int secondDecimals) { List _split(final double value) { // NumberFormat is necessary to create digit after comma if the value // has no decimal point (only necessary for browser) @@ -32,16 +32,6 @@ class GeoUtils { return '$deg° $minText′ $secText″'; } - // returns coordinates formatted as DMS, e.g. ['41° 24′ 12.2″ N', '2° 10′ 26.5″ E'] - static List toDMS(LatLng latLng, {bool minuteSecondPadding = false, int secondDecimals = 2}) { - final lat = latLng.latitude; - final lng = latLng.longitude; - return [ - '${_decimal2sexagesimal(lat, minuteSecondPadding, secondDecimals)} ${lat < 0 ? 'S' : 'N'}', - '${_decimal2sexagesimal(lng, minuteSecondPadding, secondDecimals)} ${lng < 0 ? 'W' : 'E'}', - ]; - } - static LatLng getLatLngCenter(List points) { double x = 0; double y = 0; diff --git a/lib/widgets/map/map_info_row.dart b/lib/widgets/map/map_info_row.dart index aa589a510..b4d540a78 100644 --- a/lib/widgets/map/map_info_row.dart +++ b/lib/widgets/map/map_info_row.dart @@ -121,7 +121,7 @@ class _AddressRowState extends State<_AddressRow> { ? Constants.overlayUnknown : entry.hasAddress ? entry.shortAddress - : settings.coordinateFormat.format(entry.latLng!)); + : settings.coordinateFormat.format(context.l10n, entry.latLng!)); return Text( location, strutStyle: Constants.overflowStrutStyle, diff --git a/lib/widgets/settings/language/language.dart b/lib/widgets/settings/language/language.dart index 066ec33f1..61c702bf7 100644 --- a/lib/widgets/settings/language/language.dart +++ b/lib/widgets/settings/language/language.dart @@ -23,6 +23,7 @@ class LanguageSection extends StatelessWidget { @override Widget build(BuildContext context) { + final l10n = context.l10n; final currentCoordinateFormat = context.select((s) => s.coordinateFormat); final currentUnitSystem = context.select((s) => s.unitSystem); @@ -34,13 +35,13 @@ class LanguageSection extends StatelessWidget { icon: AIcons.language, color: stringToColor('Language'), ), - title: context.l10n.settingsSectionLanguage, + title: l10n.settingsSectionLanguage, expandedNotifier: expandedNotifier, showHighlight: false, children: [ const LocaleTile(), ListTile( - title: Text(context.l10n.settingsCoordinateFormatTile), + title: Text(l10n.settingsCoordinateFormatTile), subtitle: Text(currentCoordinateFormat.getName(context)), onTap: () async { final value = await showDialog( @@ -48,8 +49,8 @@ class LanguageSection extends StatelessWidget { builder: (context) => AvesSelectionDialog( initialValue: currentCoordinateFormat, options: Map.fromEntries(CoordinateFormat.values.map((v) => MapEntry(v, v.getName(context)))), - optionSubtitleBuilder: (value) => value.format(Constants.pointNemo), - title: context.l10n.settingsCoordinateFormatTitle, + optionSubtitleBuilder: (value) => value.format(l10n, Constants.pointNemo), + title: l10n.settingsCoordinateFormatTitle, ), ); if (value != null) { @@ -58,7 +59,7 @@ class LanguageSection extends StatelessWidget { }, ), ListTile( - title: Text(context.l10n.settingsUnitSystemTile), + title: Text(l10n.settingsUnitSystemTile), subtitle: Text(currentUnitSystem.getName(context)), onTap: () async { final value = await showDialog( @@ -66,7 +67,7 @@ class LanguageSection extends StatelessWidget { builder: (context) => AvesSelectionDialog( initialValue: currentUnitSystem, options: Map.fromEntries(UnitSystem.values.map((v) => MapEntry(v, v.getName(context)))), - title: context.l10n.settingsUnitSystemTitle, + title: l10n.settingsUnitSystemTitle, ), ); if (value != null) { diff --git a/lib/widgets/viewer/info/location_section.dart b/lib/widgets/viewer/info/location_section.dart index f830ccdbb..fa78e6da9 100644 --- a/lib/widgets/viewer/info/location_section.dart +++ b/lib/widgets/viewer/info/location_section.dart @@ -174,7 +174,7 @@ class _AddressInfoGroupState extends State<_AddressInfoGroup> { final l10n = context.l10n; return InfoRowGroup( info: { - l10n.viewerInfoLabelCoordinates: settings.coordinateFormat.format(entry.latLng!), + l10n.viewerInfoLabelCoordinates: settings.coordinateFormat.format(l10n, entry.latLng!), if (address.isNotEmpty) l10n.viewerInfoLabelAddress: address, }, ); diff --git a/lib/widgets/viewer/overlay/bottom/common.dart b/lib/widgets/viewer/overlay/bottom/common.dart index 7762d43c2..fa9ec0861 100644 --- a/lib/widgets/viewer/overlay/bottom/common.dart +++ b/lib/widgets/viewer/overlay/bottom/common.dart @@ -323,7 +323,7 @@ class _LocationRow extends AnimatedWidget { @override Widget build(BuildContext context) { - final location = entry.hasAddress ? entry.shortAddress : settings.coordinateFormat.format(entry.latLng!); + final location = entry.hasAddress ? entry.shortAddress : settings.coordinateFormat.format(context.l10n, entry.latLng!); return Row( children: [ const DecoratedIcon(AIcons.location, shadows: Constants.embossShadows, size: _iconSize), diff --git a/test/utils/geo_utils_test.dart b/test/utils/geo_utils_test.dart index 29e242bed..2c3a87eb7 100644 --- a/test/utils/geo_utils_test.dart +++ b/test/utils/geo_utils_test.dart @@ -1,14 +1,17 @@ +import 'package:aves/model/settings/coordinate_format.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('Decimal degrees to DMS (sexagesimal)', () { - expect(GeoUtils.toDMS(LatLng(37.496667, 127.0275)), ['37° 29′ 48.00″ N', '127° 1′ 39.00″ E']); // Gangnam - expect(GeoUtils.toDMS(LatLng(78.9243503, 11.9230465)), ['78° 55′ 27.66″ N', '11° 55′ 22.97″ E']); // Ny-Ålesund - expect(GeoUtils.toDMS(LatLng(-38.6965891, 175.9830047)), ['38° 41′ 47.72″ S', '175° 58′ 58.82″ E']); // Taupo - expect(GeoUtils.toDMS(LatLng(-64.249391, -56.6556145)), ['64° 14′ 57.81″ S', '56° 39′ 20.21″ W']); // Marambio - expect(GeoUtils.toDMS(LatLng(0, 0)), ['0° 0′ 0.00″ N', '0° 0′ 0.00″ E']); + final l10n = lookupAppLocalizations(AppLocalizations.supportedLocales.first); + expect(ExtraCoordinateFormat.toDMS(l10n, LatLng(37.496667, 127.0275)), ['37° 29′ 48.00″ N', '127° 1′ 39.00″ E']); // Gangnam + expect(ExtraCoordinateFormat.toDMS(l10n, LatLng(78.9243503, 11.9230465)), ['78° 55′ 27.66″ N', '11° 55′ 22.97″ E']); // Ny-Ålesund + 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']); }); test('bounds center', () {