From e860f9767d639be5cbc3a043daa843f3d9bb11d1 Mon Sep 17 00:00:00 2001 From: Ming Ming Date: Sun, 21 Aug 2022 03:08:41 +0800 Subject: [PATCH] Show estimated location of photos in detail pane --- app/lib/l10n/app_en.arb | 15 ++ app/lib/l10n/untranslated-messages.txt | 52 ++++- app/lib/location_util.dart | 254 +++++++++++++++++++++++++ app/lib/widget/viewer_detail_pane.dart | 53 ++++++ 4 files changed, 366 insertions(+), 8 deletions(-) create mode 100644 app/lib/location_util.dart diff --git a/app/lib/l10n/app_en.arb b/app/lib/l10n/app_en.arb index 967456e9..b1eda595 100644 --- a/app/lib/l10n/app_en.arb +++ b/app/lib/l10n/app_en.arb @@ -1374,6 +1374,21 @@ "@showAllButtonLabel": { "description": "A button to show all items of a certain item group (e.g., show all recognized faces)" }, + "gpsPlaceText": "Near {place}", + "@gpsPlaceText": { + "description": "The estimated place where a photo was taken at. The place could be a town, a city, an administrative region, or a country.", + "placeholders": { + "place": {} + } + }, + "gpsPlaceAboutDialogTitle": "About place", + "@gpsPlaceAboutDialogTitle": { + "description": "Warn about the inaccurate nature of our offline reverse geocoding feature (i.e., converting coordinates into addresses)" + }, + "gpsPlaceAboutDialogContent": "The place shown here is only a rough estimation and not guaranteed to be accurate. It does not represent our views on any disputed areas.", + "@gpsPlaceAboutDialogContent": { + "description": "Warn about the inaccurate nature of our offline reverse geocoding feature (i.e., converting coordinates into addresses)" + }, "errorUnauthenticated": "Unauthenticated access. Please sign-in again if the problem continues", "@errorUnauthenticated": { diff --git a/app/lib/l10n/untranslated-messages.txt b/app/lib/l10n/untranslated-messages.txt index 7adf4f49..3dbfa79e 100644 --- a/app/lib/l10n/untranslated-messages.txt +++ b/app/lib/l10n/untranslated-messages.txt @@ -143,6 +143,9 @@ "searchFilterBubbleFavoriteTrueText", "searchFilterBubbleFavoriteFalseText", "showAllButtonLabel", + "gpsPlaceText", + "gpsPlaceAboutDialogTitle", + "gpsPlaceAboutDialogContent", "errorAlbumDowngrade" ], @@ -304,6 +307,9 @@ "searchFilterBubbleFavoriteTrueText", "searchFilterBubbleFavoriteFalseText", "showAllButtonLabel", + "gpsPlaceText", + "gpsPlaceAboutDialogTitle", + "gpsPlaceAboutDialogContent", "errorAlbumDowngrade" ], @@ -345,13 +351,25 @@ "searchFilterFavoriteLabel", "searchFilterBubbleFavoriteTrueText", "searchFilterBubbleFavoriteFalseText", - "showAllButtonLabel" + "showAllButtonLabel", + "gpsPlaceText", + "gpsPlaceAboutDialogTitle", + "gpsPlaceAboutDialogContent" ], "es": [ "settingsLanguageOptionSystemDefaultLabel", "rootPickerSkipConfirmationDialogContent2", - "showAllButtonLabel" + "showAllButtonLabel", + "gpsPlaceText", + "gpsPlaceAboutDialogTitle", + "gpsPlaceAboutDialogContent" + ], + + "fi": [ + "gpsPlaceText", + "gpsPlaceAboutDialogTitle", + "gpsPlaceAboutDialogContent" ], "fr": [ @@ -417,7 +435,10 @@ "searchFilterFavoriteLabel", "searchFilterBubbleFavoriteTrueText", "searchFilterBubbleFavoriteFalseText", - "showAllButtonLabel" + "showAllButtonLabel", + "gpsPlaceText", + "gpsPlaceAboutDialogTitle", + "gpsPlaceAboutDialogContent" ], "pl": [ @@ -500,7 +521,10 @@ "searchFilterFavoriteLabel", "searchFilterBubbleFavoriteTrueText", "searchFilterBubbleFavoriteFalseText", - "showAllButtonLabel" + "showAllButtonLabel", + "gpsPlaceText", + "gpsPlaceAboutDialogTitle", + "gpsPlaceAboutDialogContent" ], "pt": [ @@ -562,7 +586,10 @@ "searchFilterFavoriteLabel", "searchFilterBubbleFavoriteTrueText", "searchFilterBubbleFavoriteFalseText", - "showAllButtonLabel" + "showAllButtonLabel", + "gpsPlaceText", + "gpsPlaceAboutDialogTitle", + "gpsPlaceAboutDialogContent" ], "ru": [ @@ -624,7 +651,10 @@ "searchFilterFavoriteLabel", "searchFilterBubbleFavoriteTrueText", "searchFilterBubbleFavoriteFalseText", - "showAllButtonLabel" + "showAllButtonLabel", + "gpsPlaceText", + "gpsPlaceAboutDialogTitle", + "gpsPlaceAboutDialogContent" ], "zh": [ @@ -686,7 +716,10 @@ "searchFilterFavoriteLabel", "searchFilterBubbleFavoriteTrueText", "searchFilterBubbleFavoriteFalseText", - "showAllButtonLabel" + "showAllButtonLabel", + "gpsPlaceText", + "gpsPlaceAboutDialogTitle", + "gpsPlaceAboutDialogContent" ], "zh_Hant": [ @@ -748,6 +781,9 @@ "searchFilterFavoriteLabel", "searchFilterBubbleFavoriteTrueText", "searchFilterBubbleFavoriteFalseText", - "showAllButtonLabel" + "showAllButtonLabel", + "gpsPlaceText", + "gpsPlaceAboutDialogTitle", + "gpsPlaceAboutDialogContent" ] } diff --git a/app/lib/location_util.dart b/app/lib/location_util.dart new file mode 100644 index 00000000..93688133 --- /dev/null +++ b/app/lib/location_util.dart @@ -0,0 +1,254 @@ +/// Convert a ISO 3166-1 alpha-2 code into country name +String? alpha2CodeToName(String cc) => _ccMap[cc]; + +const _ccMap = { + "AD": "Andorra", + "AE": "United Arab Emirates", + "AF": "Afghanistan", + "AG": "Antigua and Barbuda", + "AI": "Anguilla", + "AL": "Albania", + "AM": "Armenia", + "AO": "Angola", + "AQ": "Antarctica", + "AR": "Argentina", + "AS": "American Samoa", + "AT": "Austria", + "AU": "Australia", + "AW": "Aruba", + "AX": "Åland Islands", + "AZ": "Azerbaijan", + "BA": "Bosnia and Herzegovina", + "BB": "Barbados", + "BD": "Bangladesh", + "BE": "Belgium", + "BF": "Burkina Faso", + "BG": "Bulgaria", + "BH": "Bahrain", + "BI": "Burundi", + "BJ": "Benin", + "BL": "Saint Barthélemy", + "BM": "Bermuda", + "BN": "Brunei Darussalam", + "BO": "Bolivia", + "BQ": "Bonaire, Sint Eustatius and Saba", + "BR": "Brazil", + "BS": "Bahamas", + "BT": "Bhutan", + "BV": "Bouvet Island", + "BW": "Botswana", + "BY": "Belarus", + "BZ": "Belize", + "CA": "Canada", + "CC": "Cocos (Keeling) Islands", + "CD": "DR Congo", + "CF": "Central African Republic", + "CG": "Congo", + "CH": "Switzerland", + "CI": "Ivory Coast", + "CK": "Cook Islands", + "CL": "Chile", + "CM": "Cameroon", + "CN": "China", + "CO": "Colombia", + "CR": "Costa Rica", + "CU": "Cuba", + "CV": "Cabo Verde", + "CW": "Curaçao", + "CX": "Christmas Island", + "CY": "Cyprus", + "CZ": "Czechia", + "DE": "Germany", + "DJ": "Djibouti", + "DK": "Denmark", + "DM": "Dominica", + "DO": "Dominican Republic", + "DZ": "Algeria", + "EC": "Ecuador", + "EE": "Estonia", + "EG": "Egypt", + "EH": "Western Sahara", + "ER": "Eritrea", + "ES": "Spain", + "ET": "Ethiopia", + "FI": "Finland", + "FJ": "Fiji", + "FK": "Falkland Islands (Malvinas)", + "FM": "Micronesia", + "FO": "Faroe Islands", + "FR": "France", + "GA": "Gabon", + "GB": "United Kingdom", + "GD": "Grenada", + "GE": "Georgia", + "GF": "French Guiana", + "GG": "Guernsey", + "GH": "Ghana", + "GI": "Gibraltar", + "GL": "Greenland", + "GM": "Gambia", + "GN": "Guinea", + "GP": "Guadeloupe", + "GQ": "Equatorial Guinea", + "GR": "Greece", + "GS": "South Georgia and the South Sandwich Islands", + "GT": "Guatemala", + "GU": "Guam", + "GW": "Guinea-Bissau", + "GY": "Guyana", + "HK": "Hong Kong", + "HM": "Heard Island and McDonald Islands", + "HN": "Honduras", + "HR": "Croatia", + "HT": "Haiti", + "HU": "Hungary", + "ID": "Indonesia", + "IE": "Ireland", + "IL": "Israel", + "IM": "Isle of Man", + "IN": "India", + "IO": "British Indian Ocean Territory", + "IQ": "Iraq", + "IR": "Iran", + "IS": "Iceland", + "IT": "Italy", + "JE": "Jersey", + "JM": "Jamaica", + "JO": "Jordan", + "JP": "Japan", + "KE": "Kenya", + "KG": "Kyrgyzstan", + "KH": "Cambodia", + "KI": "Kiribati", + "KM": "Comoros", + "KN": "Saint Kitts and Nevis", + "KP": "North Korea", + "KR": "South Korea", + "KW": "Kuwait", + "KY": "Cayman Islands", + "KZ": "Kazakhstan", + "LA": "Laos", + "LB": "Lebanon", + "LC": "Saint Lucia", + "LI": "Liechtenstein", + "LK": "Sri Lanka", + "LR": "Liberia", + "LS": "Lesotho", + "LT": "Lithuania", + "LU": "Luxembourg", + "LV": "Latvia", + "LY": "Libya", + "MA": "Morocco", + "MC": "Monaco", + "MD": "Moldova", + "ME": "Montenegro", + "MF": "Saint Martin", + "MG": "Madagascar", + "MH": "Marshall Islands", + "MK": "North Macedonia", + "ML": "Mali", + "MM": "Myanmar", + "MN": "Mongolia", + "MO": "Macau", + "MP": "Northern Mariana Islands", + "MQ": "Martinique", + "MR": "Mauritania", + "MS": "Montserrat", + "MT": "Malta", + "MU": "Mauritius", + "MV": "Maldives", + "MW": "Malawi", + "MX": "Mexico", + "MY": "Malaysia", + "MZ": "Mozambique", + "NA": "Namibia", + "NC": "New Caledonia", + "NE": "Niger", + "NF": "Norfolk Island", + "NG": "Nigeria", + "NI": "Nicaragua", + "NL": "Netherlands", + "NO": "Norway", + "NP": "Nepal", + "NR": "Nauru", + "NU": "Niue", + "NZ": "New Zealand", + "OM": "Oman", + "PA": "Panama", + "PE": "Peru", + "PF": "French Polynesia", + "PG": "Papua New Guinea", + "PH": "Philippines", + "PK": "Pakistan", + "PL": "Poland", + "PM": "Saint Pierre and Miquelon", + "PN": "Pitcairn", + "PR": "Puerto Rico", + "PS": "Palestine", + "PT": "Portugal", + "PW": "Palau", + "PY": "Paraguay", + "QA": "Qatar", + "RE": "Réunion", + "RO": "Romania", + "RS": "Serbia", + "RU": "Russia", + "RW": "Rwanda", + "SA": "Saudi Arabia", + "SB": "Solomon Islands", + "SC": "Seychelles", + "SD": "Sudan", + "SE": "Sweden", + "SG": "Singapore", + "SH": "Saint Helena, Ascension and Tristan da Cunha", + "SI": "Slovenia", + "SJ": "Svalbard and Jan Mayen", + "SK": "Slovakia", + "SL": "Sierra Leone", + "SM": "San Marino", + "SN": "Senegal", + "SO": "Somalia", + "SR": "Suriname", + "SS": "South Sudan", + "ST": "Sao Tome and Principe", + "SV": "El Salvador", + "SX": "Sint Maarten", + "SY": "Syria", + "SZ": "Eswatini", + "TC": "Turks and Caicos Islands", + "TD": "Chad", + "TF": "French Southern Territories", + "TG": "Togo", + "TH": "Thailand", + "TJ": "Tajikistan", + "TK": "Tokelau", + "TL": "Timor-Leste", + "TM": "Turkmenistan", + "TN": "Tunisia", + "TO": "Tonga", + "TR": "Türkiye", + "TT": "Trinidad and Tobago", + "TV": "Tuvalu", + "TW": "Taiwan", + "TZ": "Tanzania", + "UA": "Ukraine", + "UG": "Uganda", + "UM": "United States Minor Outlying Islands", + "US": "United States", + "UY": "Uruguay", + "UZ": "Uzbekistan", + "VA": "Holy See", + "VC": "Saint Vincent and the Grenadines", + "VE": "Venezuela", + "VG": "Virgin Islands (British)", + "VI": "Virgin Islands (U.S.)", + "VN": "Viet Nam", + "VU": "Vanuatu", + "WF": "Wallis and Futuna", + "WS": "Samoa", + "YE": "Yemen", + "YT": "Mayotte", + "ZA": "South Africa", + "ZM": "Zambia", + "ZW": "Zimbabwe", +}; diff --git a/app/lib/widget/viewer_detail_pane.dart b/app/lib/widget/viewer_detail_pane.dart index 1d24a88f..7fefc697 100644 --- a/app/lib/widget/viewer_detail_pane.dart +++ b/app/lib/widget/viewer_detail_pane.dart @@ -17,7 +17,9 @@ import 'package:nc_photos/entity/album/provider.dart'; import 'package:nc_photos/entity/exif_extension.dart'; import 'package:nc_photos/entity/file.dart'; import 'package:nc_photos/k.dart' as k; +import 'package:nc_photos/location_util.dart' as location_util; import 'package:nc_photos/notified_action.dart'; +import 'package:nc_photos/object_extension.dart'; import 'package:nc_photos/platform/features.dart' as features; import 'package:nc_photos/platform/k.dart' as platform_k; import 'package:nc_photos/snack_bar_manager.dart'; @@ -291,6 +293,41 @@ class _ViewerDetailPaneState extends State { title: Text(_model!), subtitle: cameraSubStr.isNotEmpty ? Text(cameraSubStr) : null, ), + if (_location?.name != null) + ListTile( + leading: ListTileCenterLeading( + child: Icon( + Icons.location_on_outlined, + color: AppTheme.getSecondaryTextColor(context), + ), + ), + title: Text(L10n.global().gpsPlaceText(_location!.name!)), + subtitle: _location!.toSubtitle()?.run((obj) => Text(obj)), + trailing: Icon( + Icons.info_outline, + color: AppTheme.getSecondaryTextColor(context), + ), + onTap: () { + showDialog( + context: context, + builder: (_) => AlertDialog( + title: Text(L10n.global().gpsPlaceAboutDialogTitle), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(L10n.global().gpsPlaceAboutDialogContent), + const SizedBox(height: 16), + const Divider(height: 16), + const Text( + "Based on GeoNames Gazetteer data by GeoNames, licensed under CC BY 4.0", + ), + ], + ), + ), + ); + }, + ), if (features.isSupportMapView && _gps != null) AnimatedVisibility( opacity: _shouldBlockGpsMap ? 0 : 1, @@ -339,6 +376,7 @@ class _ViewerDetailPaneState extends State { if (lat != null && lng != null) { _log.fine("GPS: ($lat, $lng)"); _gps = Tuple2(lat, lng); + _location = widget.file.location; } } @@ -520,6 +558,7 @@ class _ViewerDetailPaneState extends State { double? _focalLength; int? _isoSpeedRatings; Tuple2? _gps; + ImageLocation? _location; final _tags = []; @@ -589,3 +628,17 @@ String _byteSizeToString(int byteSize) { } return "${remain.toStringAsFixed(2)}${units[i]}"; } + +extension on ImageLocation { + String? toSubtitle() { + if (countryCode == null) { + return null; + } else if (admin1 == null) { + return location_util.alpha2CodeToName(countryCode!); + } else if (admin2 == null) { + return "$admin1, ${location_util.alpha2CodeToName(countryCode!)}"; + } else { + return "$admin2, $admin1, ${location_util.alpha2CodeToName(countryCode!)}"; + } + } +}