diff --git a/CHANGELOG.md b/CHANGELOG.md
index 31d4e2074..568674e5f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
## [Unreleased]
+### Added
+
+- DDM coordinate format option
+
### Changed
- Video: use `media-kit` instead of `ffmpeg-kit` for metadata fetch
diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb
index 7ab3fc430..2cced4ee7 100644
--- a/lib/l10n/app_en.arb
+++ b/lib/l10n/app_en.arb
@@ -210,6 +210,7 @@
"albumTierRegular": "Others",
"coordinateFormatDms": "DMS",
+ "coordinateFormatDdm": "DDM",
"coordinateFormatDecimal": "Decimal degrees",
"coordinateDms": "{coordinate} {direction}",
"@coordinateDms": {
diff --git a/lib/model/filters/coordinate.dart b/lib/model/filters/coordinate.dart
index c8ba7f2a7..3eea4e5d8 100644
--- a/lib/model/filters/coordinate.dart
+++ b/lib/model/filters/coordinate.dart
@@ -60,12 +60,15 @@ class CoordinateFilter extends CollectionFilter {
@override
String getLabel(BuildContext context) {
- return _formatBounds((latLng) => settings.coordinateFormat.format(
- context,
- latLng,
- minuteSecondPadding: minuteSecondPadding,
- dmsSecondDecimals: 0,
- ));
+ return _formatBounds((latLng) {
+ final format = settings.coordinateFormat;
+ return format.format(
+ context,
+ latLng,
+ minuteSecondPadding: minuteSecondPadding,
+ dmsSecondDecimals: format == CoordinateFormat.ddm ? 2 : 0,
+ );
+ });
}
@override
diff --git a/lib/model/settings/enums/coordinate_format.dart b/lib/model/settings/enums/coordinate_format.dart
index bf3a5b6ec..2572bacfb 100644
--- a/lib/model/settings/enums/coordinate_format.dart
+++ b/lib/model/settings/enums/coordinate_format.dart
@@ -8,15 +8,17 @@ import 'package:latlong2/latlong.dart';
extension ExtraCoordinateFormat on CoordinateFormat {
static const _separator = ', ';
- String format(BuildContext context, LatLng latLng, {bool minuteSecondPadding = false, int dmsSecondDecimals = 2}) {
+ String format(BuildContext context, LatLng latLng, {bool minuteSecondPadding = false, int? dmsSecondDecimals}) {
final text = formatWithoutDirectionality(context.l10n, latLng, minuteSecondPadding: minuteSecondPadding, dmsSecondDecimals: dmsSecondDecimals);
return context.applyDirectionality(text);
}
- String formatWithoutDirectionality(AppLocalizations l10n, LatLng latLng, {bool minuteSecondPadding = false, int dmsSecondDecimals = 2}) {
+ String formatWithoutDirectionality(AppLocalizations l10n, LatLng latLng, {bool minuteSecondPadding = false, int? dmsSecondDecimals}) {
switch (this) {
case CoordinateFormat.dms:
- return toDMS(l10n, latLng, minuteSecondPadding: minuteSecondPadding, secondDecimals: dmsSecondDecimals).join(_separator);
+ return toDMS(l10n, latLng, minuteSecondPadding: minuteSecondPadding, secondDecimals: dmsSecondDecimals ?? 2).join(_separator);
+ case CoordinateFormat.ddm:
+ return toDDM(l10n, latLng, minutePadding: minuteSecondPadding, minuteDecimals: dmsSecondDecimals ?? 4).join(_separator);
case CoordinateFormat.decimal:
return _toDecimal(l10n, latLng).join(_separator);
}
@@ -35,6 +37,19 @@ extension ExtraCoordinateFormat on CoordinateFormat {
];
}
+ // returns coordinates formatted as DDM, e.g. ['41° 24.2028′ N', '2° 10.4418′ E']
+ static List toDDM(AppLocalizations l10n, LatLng latLng, {bool minutePadding = false, int minuteDecimals = 4}) {
+ final locale = l10n.localeName;
+ final lat = latLng.latitude;
+ final lng = latLng.longitude;
+ final latSexa = _decimal2ddm(lat, minutePadding, minuteDecimals, locale);
+ final lngSexa = _decimal2ddm(lng, minutePadding, minuteDecimals, locale);
+ return [
+ l10n.coordinateDms(latSexa, lat < 0 ? l10n.coordinateDmsSouth : l10n.coordinateDmsNorth),
+ l10n.coordinateDms(lngSexa, lng < 0 ? l10n.coordinateDmsWest : l10n.coordinateDmsEast),
+ ];
+ }
+
static String _decimal2sexagesimal(
double degDecimal,
bool minuteSecondPadding,
@@ -54,6 +69,22 @@ extension ExtraCoordinateFormat on CoordinateFormat {
return '$degText° $minText′ $secText″';
}
+ static String _decimal2ddm(
+ double degDecimal,
+ bool minutePadding,
+ int minuteDecimals,
+ String locale,
+ ) {
+ final degAbs = degDecimal.abs();
+ final deg = degAbs.toInt();
+ final min = (degAbs - deg) * 60;
+
+ final degText = NumberFormat('0', locale).format(deg);
+ final minText = NumberFormat('${'0' * (minutePadding ? 2 : 1)}${minuteDecimals > 0 ? '.${'0' * minuteDecimals}' : ''}', locale).format(min);
+
+ return '$degText° $minText′';
+ }
+
static List _toDecimal(AppLocalizations l10n, LatLng latLng) {
final coordinateFormatter = NumberFormat('0.000000°', l10n.localeName);
return [
diff --git a/lib/view/src/settings/enums.dart b/lib/view/src/settings/enums.dart
index 10854be4c..93aec5091 100644
--- a/lib/view/src/settings/enums.dart
+++ b/lib/view/src/settings/enums.dart
@@ -45,6 +45,7 @@ extension ExtraCoordinateFormatView on CoordinateFormat {
final l10n = context.l10n;
return switch (this) {
CoordinateFormat.dms => l10n.coordinateFormatDms,
+ CoordinateFormat.ddm => l10n.coordinateFormatDdm,
CoordinateFormat.decimal => l10n.coordinateFormatDecimal,
};
}
diff --git a/plugins/aves_model/lib/src/settings/enums.dart b/plugins/aves_model/lib/src/settings/enums.dart
index 196d57774..73bf6dbaa 100644
--- a/plugins/aves_model/lib/src/settings/enums.dart
+++ b/plugins/aves_model/lib/src/settings/enums.dart
@@ -8,7 +8,7 @@ enum AvesThemeColorMode { monochrome, polychrome }
enum ConfirmationDialog { createVault, deleteForever, moveToBin, moveUndatedItems }
-enum CoordinateFormat { dms, decimal }
+enum CoordinateFormat { dms, ddm, decimal }
enum DisplayRefreshRateMode { auto, highest, lowest }
diff --git a/plugins/aves_video_mpv/lib/src/controller.dart b/plugins/aves_video_mpv/lib/src/controller.dart
index 621f23506..b16920865 100644
--- a/plugins/aves_video_mpv/lib/src/controller.dart
+++ b/plugins/aves_video_mpv/lib/src/controller.dart
@@ -162,6 +162,12 @@ class MpvVideoController extends AvesVideoController {
Future _init({int startMillis = 0}) async {
final playing = _instance.state.playing;
+ // Audio quality is better with `audiotrack` than `opensles` (the default).
+ // Calling `setAudioDevice` does not seem to work.
+ // As of 2025/01/13, directly setting audio output via property works for some files but not all,
+ // and switching from a supported file to an unsupported file crashes:
+ // cf https://github.com/media-kit/media-kit/issues/1061
+
await _applyLoop();
await _instance.open(Media(entry.uri), play: playing);
await _instance.setSubtitleTrack(SubtitleTrack.no());
diff --git a/test/utils/geo_utils_test.dart b/test/utils/geo_utils_test.dart
index 11f862a32..c34381364 100644
--- a/test/utils/geo_utils_test.dart
+++ b/test/utils/geo_utils_test.dart
@@ -18,6 +18,18 @@ void main() {
expect(ExtraCoordinateFormat.toDMS(l10n, const LatLng(0, 0), secondDecimals: 4), ['0° 0′ 0.0000″ N', '0° 0′ 0.0000″ E']);
});
+ test('Decimal degrees to DDM', () {
+ final l10n = lookupAppLocalizations(AvesApp.supportedLocales.first);
+ expect(ExtraCoordinateFormat.toDDM(l10n, const LatLng(37.496667, 127.0275)), ['37° 29.8000′ N', '127° 1.6500′ E']); // Gangnam
+ expect(ExtraCoordinateFormat.toDDM(l10n, const LatLng(78.9243503, 11.9230465)), ['78° 55.4610′ N', '11° 55.3828′ E']); // Ny-Ålesund
+ expect(ExtraCoordinateFormat.toDDM(l10n, const LatLng(-38.6965891, 175.9830047)), ['38° 41.7953′ S', '175° 58.9803′ E']); // Taupo
+ expect(ExtraCoordinateFormat.toDDM(l10n, const LatLng(-64.249391, -56.6556145)), ['64° 14.9635′ S', '56° 39.3369′ W']); // Marambio
+ expect(ExtraCoordinateFormat.toDDM(l10n, const LatLng(0, 0)), ['0° 0.0000′ N', '0° 0.0000′ E']);
+ expect(ExtraCoordinateFormat.toDDM(l10n, const LatLng(0, 0), minutePadding: true), ['0° 00.0000′ N', '0° 00.0000′ E']);
+ expect(ExtraCoordinateFormat.toDDM(l10n, const LatLng(0, 0), minuteDecimals: 0), ['0° 0′ N', '0° 0′ E']);
+ expect(ExtraCoordinateFormat.toDDM(l10n, const LatLng(0, 0), minuteDecimals: 6), ['0° 0.000000′ N', '0° 0.000000′ E']);
+ });
+
test('bounds center', () {
expect(GeoUtils.getLatLngCenter(const [LatLng(10, 30), LatLng(30, 50)]), const LatLng(20.28236664671092, 39.351653000319956));
expect(GeoUtils.getLatLngCenter(const [LatLng(10, -179), LatLng(30, 179)]), const LatLng(20.00279344048298, -179.9358157370226));