From 718b4749a0a9aa2cf5b81bf2c1b4166302e4e5e0 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Thu, 7 Oct 2021 18:07:42 +0900 Subject: [PATCH] settings: unit system --- lib/l10n/app_en.arb | 9 +++++ lib/l10n/app_ko.arb | 5 +++ lib/model/settings/defaults.dart | 1 + lib/model/settings/enums.dart | 2 + lib/model/settings/settings.dart | 6 +++ lib/model/settings/unit_system.dart | 15 +++++++ lib/widgets/common/map/leaflet/map.dart | 5 ++- .../common/map/leaflet/scale_layer.dart | 39 ++++++++++++++++--- .../common/map/leaflet/scalebar_utils.dart | 4 +- lib/widgets/settings/language/language.dart | 19 +++++++++ 10 files changed, 96 insertions(+), 9 deletions(-) create mode 100644 lib/model/settings/unit_system.dart diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index b57fb4ae0..9abf30939 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -173,6 +173,11 @@ "coordinateFormatDecimal": "Decimal degrees", "@coordinateFormatDecimal": {}, + "unitSystemMetric": "Metric", + "@unitSystemMetric": {}, + "unitSystemImperial": "Imperial", + "@unitSystemImperial": {}, + "videoLoopModeNever": "Never", "@videoLoopModeNever": {}, "videoLoopModeShortOnly": "Short videos only", @@ -835,6 +840,10 @@ "@settingsCoordinateFormatTile": {}, "settingsCoordinateFormatTitle": "Coordinate Format", "@settingsCoordinateFormatTitle": {}, + "settingsUnitSystemTile": "Units", + "@settingsUnitSystemTile": {}, + "settingsUnitSystemTitle": "Units", + "@settingsUnitSystemTitle": {}, "statsPageTitle": "Stats", "@statsPageTitle": {}, diff --git a/lib/l10n/app_ko.arb b/lib/l10n/app_ko.arb index cff16555c..f02446993 100644 --- a/lib/l10n/app_ko.arb +++ b/lib/l10n/app_ko.arb @@ -86,6 +86,9 @@ "coordinateFormatDms": "도분초", "coordinateFormatDecimal": "소수점", + "unitSystemMetric": "미터법", + "unitSystemImperial": "야드파운드법", + "videoLoopModeNever": "반복 안 함", "videoLoopModeShortOnly": "짧은 동영상만 반복", "videoLoopModeAlways": "항상 반복", @@ -409,6 +412,8 @@ "settingsLanguage": "언어", "settingsCoordinateFormatTile": "좌표 표현", "settingsCoordinateFormatTitle": "좌표 표현", + "settingsUnitSystemTile": "단위법", + "settingsUnitSystemTitle": "단위법", "statsPageTitle": "통계", "statsImage": "{count, plural, other{사진}}", diff --git a/lib/model/settings/defaults.dart b/lib/model/settings/defaults.dart index 9ebec1704..e91319ecd 100644 --- a/lib/model/settings/defaults.dart +++ b/lib/model/settings/defaults.dart @@ -82,6 +82,7 @@ class SettingsDefaults { static const infoMapStyle = EntryMapStyle.stamenWatercolor; // `infoMapStyle` has a contextual default value static const infoMapZoom = 12.0; static const coordinateFormat = CoordinateFormat.dms; + static const unitSystem = UnitSystem.metric; // rendering static const imageBackground = EntryBackground.white; diff --git a/lib/model/settings/enums.dart b/lib/model/settings/enums.dart index 1085d1107..f4678d208 100644 --- a/lib/model/settings/enums.dart +++ b/lib/model/settings/enums.dart @@ -13,4 +13,6 @@ enum EntryMapStyle { googleNormal, googleHybrid, googleTerrain, osmHot, stamenTo enum KeepScreenOn { never, viewerOnly, always } +enum UnitSystem { metric, imperial } + enum VideoLoopMode { never, shortOnly, always } diff --git a/lib/model/settings/settings.dart b/lib/model/settings/settings.dart index d461d42d0..0699d257a 100644 --- a/lib/model/settings/settings.dart +++ b/lib/model/settings/settings.dart @@ -98,6 +98,7 @@ class Settings extends ChangeNotifier { static const infoMapStyleKey = 'info_map_style'; static const infoMapZoomKey = 'info_map_zoom'; static const coordinateFormatKey = 'coordinates_format'; + static const unitSystemKey = 'unit_system'; // rendering static const imageBackgroundKey = 'image_background'; @@ -379,6 +380,10 @@ class Settings extends ChangeNotifier { set coordinateFormat(CoordinateFormat newValue) => setAndNotify(coordinateFormatKey, newValue.toString()); + UnitSystem get unitSystem => getEnumOrDefault(unitSystemKey, SettingsDefaults.unitSystem, UnitSystem.values); + + set unitSystem(UnitSystem newValue) => setAndNotify(unitSystemKey, newValue.toString()); + // rendering EntryBackground get imageBackground => getEnumOrDefault(imageBackgroundKey, SettingsDefaults.imageBackground, EntryBackground.values); @@ -574,6 +579,7 @@ class Settings extends ChangeNotifier { case subtitleTextAlignmentKey: case infoMapStyleKey: case coordinateFormatKey: + case unitSystemKey: case imageBackgroundKey: case accessibilityAnimationsKey: case timeToTakeActionKey: diff --git a/lib/model/settings/unit_system.dart b/lib/model/settings/unit_system.dart new file mode 100644 index 000000000..cde99328b --- /dev/null +++ b/lib/model/settings/unit_system.dart @@ -0,0 +1,15 @@ +import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:flutter/widgets.dart'; + +import 'enums.dart'; + +extension ExtraUnitSystem on UnitSystem { + String getName(BuildContext context) { + switch (this) { + case UnitSystem.metric: + return context.l10n.unitSystemMetric; + case UnitSystem.imperial: + return context.l10n.unitSystemImperial; + } + } +} diff --git a/lib/widgets/common/map/leaflet/map.dart b/lib/widgets/common/map/leaflet/map.dart index f48aba615..baf91536f 100644 --- a/lib/widgets/common/map/leaflet/map.dart +++ b/lib/widgets/common/map/leaflet/map.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:aves/model/entry.dart'; import 'package:aves/model/settings/enums.dart'; +import 'package:aves/model/settings/settings.dart'; import 'package:aves/theme/durations.dart'; import 'package:aves/utils/debouncer.dart'; import 'package:aves/widgets/common/map/buttons.dart'; @@ -163,7 +164,9 @@ class _EntryLeafletMapState extends State with TickerProviderSt mapController: _leafletMapController, nonRotatedChildren: [ ScaleLayerWidget( - options: ScaleLayerOptions(), + options: ScaleLayerOptions( + unitSystem: settings.unitSystem, + ), ), ], children: [ diff --git a/lib/widgets/common/map/leaflet/scale_layer.dart b/lib/widgets/common/map/leaflet/scale_layer.dart index d87005bcc..64e8321b2 100644 --- a/lib/widgets/common/map/leaflet/scale_layer.dart +++ b/lib/widgets/common/map/leaflet/scale_layer.dart @@ -1,5 +1,4 @@ -import 'dart:math'; - +import 'package:aves/model/settings/enums.dart'; import 'package:aves/widgets/common/basic/outlined_text.dart'; import 'package:aves/widgets/common/map/leaflet/scalebar_utils.dart'; import 'package:flutter/material.dart'; @@ -7,10 +6,12 @@ import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map/plugin_api.dart'; class ScaleLayerOptions extends LayerOptions { + final UnitSystem unitSystem; final Widget Function(double width, String distance) builder; ScaleLayerOptions({ Key? key, + this.unitSystem = UnitSystem.metric, this.builder = defaultBuilder, rebuild, }) : super(key: key, rebuild: rebuild); @@ -41,7 +42,7 @@ class ScaleLayer extends StatelessWidget { // ignore: prefer_void_to_null final Stream stream; - final scale = [ + static const List scaleMeters = [ 25000000, 15000000, 8000000, @@ -67,6 +68,10 @@ class ScaleLayer extends StatelessWidget { 5, ]; + static const double metersInAKilometer = 1000; + static const double metersInAMile = 1609.344; + static const double metersInAFoot = 0.3048; + ScaleLayer(this.scaleLayerOpts, this.map, this.stream) : super(key: scaleLayerOpts.key); @override @@ -83,11 +88,33 @@ class ScaleLayer extends StatelessWidget { : latitude > 60 ? 3 : 2); - final distance = scale[max(0, min(20, level))].toDouble(); + final scaleLevel = level.clamp(0, 20); + late final double distanceMeters; + late final String displayDistance; + switch (scaleLayerOpts.unitSystem) { + case UnitSystem.metric: + // meters + distanceMeters = scaleMeters[scaleLevel]; + displayDistance = distanceMeters >= metersInAKilometer ? '${(distanceMeters / metersInAKilometer).toStringAsFixed(0)} km' : '${distanceMeters.toStringAsFixed(0)} m'; + break; + case UnitSystem.imperial: + if (scaleLevel < 15) { + // miles + final distanceMiles = scaleMeters[scaleLevel + 1] / 1000; + distanceMeters = distanceMiles * metersInAMile; + displayDistance = '${distanceMiles.toStringAsFixed(0)} mi'; + } else { + // feet + final distanceFeet = scaleMeters[scaleLevel - 1]; + distanceMeters = distanceFeet * metersInAFoot; + displayDistance = '${distanceFeet.toStringAsFixed(0)} ft'; + } + break; + } + final start = map.project(center); - final targetPoint = ScaleBarUtils.calculateEndingGlobalCoordinates(center, 90, distance); + final targetPoint = ScaleBarUtils.calculateEndingGlobalCoordinates(center, 90, distanceMeters); final end = map.project(targetPoint); - final displayDistance = distance > 999 ? '${(distance / 1000).toStringAsFixed(0)} km' : '${distance.toStringAsFixed(0)} m'; final width = end.x - (start.x as double); return scaleLayerOpts.builder(width, displayDistance); diff --git a/lib/widgets/common/map/leaflet/scalebar_utils.dart b/lib/widgets/common/map/leaflet/scalebar_utils.dart index 8cdc50ca4..0027bc616 100644 --- a/lib/widgets/common/map/leaflet/scalebar_utils.dart +++ b/lib/widgets/common/map/leaflet/scalebar_utils.dart @@ -3,7 +3,7 @@ import 'dart:math'; import 'package:latlong2/latlong.dart'; class ScaleBarUtils { - static LatLng calculateEndingGlobalCoordinates(LatLng start, double startBearing, double distance) { + static LatLng calculateEndingGlobalCoordinates(LatLng start, double startBearing, double distanceMeters) { var mSemiMajorAxis = 6378137.0; //WGS84 major axis var mSemiMinorAxis = (1.0 - 1.0 / 298.257223563) * 6378137.0; var mFlattening = 1.0 / 298.257223563; @@ -18,7 +18,7 @@ class ScaleBarUtils { var alpha1 = degToRadian(startBearing); var cosAlpha1 = cos(alpha1); var sinAlpha1 = sin(alpha1); - var s = distance; + var s = distanceMeters; var tanU1 = (1.0 - f) * tan(phi1); var cosU1 = 1.0 / sqrt(1.0 + tanU1 * tanU1); var sinU1 = tanU1 * cosU1; diff --git a/lib/widgets/settings/language/language.dart b/lib/widgets/settings/language/language.dart index ab6f79186..066ec33f1 100644 --- a/lib/widgets/settings/language/language.dart +++ b/lib/widgets/settings/language/language.dart @@ -1,6 +1,7 @@ import 'package:aves/model/settings/coordinate_format.dart'; import 'package:aves/model/settings/enums.dart'; import 'package:aves/model/settings/settings.dart'; +import 'package:aves/model/settings/unit_system.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/utils/color_utils.dart'; import 'package:aves/utils/constants.dart'; @@ -23,6 +24,7 @@ class LanguageSection extends StatelessWidget { @override Widget build(BuildContext context) { final currentCoordinateFormat = context.select((s) => s.coordinateFormat); + final currentUnitSystem = context.select((s) => s.unitSystem); return AvesExpansionTile( // use a fixed value instead of the title to identify this expansion tile @@ -55,6 +57,23 @@ class LanguageSection extends StatelessWidget { } }, ), + ListTile( + title: Text(context.l10n.settingsUnitSystemTile), + subtitle: Text(currentUnitSystem.getName(context)), + onTap: () async { + final value = await showDialog( + context: context, + builder: (context) => AvesSelectionDialog( + initialValue: currentUnitSystem, + options: Map.fromEntries(UnitSystem.values.map((v) => MapEntry(v, v.getName(context)))), + title: context.l10n.settingsUnitSystemTitle, + ), + ); + if (value != null) { + settings.unitSystem = value; + } + }, + ), ], ); }