diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt b/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt index 7e4504e63..7f43f5215 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt @@ -34,6 +34,7 @@ class MainActivity : FlutterActivity() { MethodChannel(messenger, AppShortcutHandler.CHANNEL).setMethodCallHandler(AppShortcutHandler(this)) MethodChannel(messenger, DebugHandler.CHANNEL).setMethodCallHandler(DebugHandler(this)) MethodChannel(messenger, ImageFileHandler.CHANNEL).setMethodCallHandler(ImageFileHandler(this)) + MethodChannel(messenger, GeocodingHandler.CHANNEL).setMethodCallHandler(GeocodingHandler(this)) MethodChannel(messenger, MediaStoreHandler.CHANNEL).setMethodCallHandler(MediaStoreHandler(this)) MethodChannel(messenger, MetadataHandler.CHANNEL).setMethodCallHandler(MetadataHandler(this)) MethodChannel(messenger, StorageHandler.CHANNEL).setMethodCallHandler(StorageHandler(this)) diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/GeocodingHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/GeocodingHandler.kt new file mode 100644 index 000000000..245b727bd --- /dev/null +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/GeocodingHandler.kt @@ -0,0 +1,73 @@ +package deckers.thibault.aves.channel.calls + +import android.content.Context +import android.location.Address +import android.location.Geocoder +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import io.flutter.plugin.common.MethodChannel.MethodCallHandler +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch + +// as of 2021/03/10, geocoding packages exist but: +// - `geocoder` is unmaintained +// - `geocoding` method does not return `addressLine` (v2.0.0) +class GeocodingHandler(private val context: Context) : MethodCallHandler { + private var geocoder: Geocoder? = null + + override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { + when (call.method) { + "getAddress" -> GlobalScope.launch(Dispatchers.IO) { Coresult.safe(call, result, ::getAddress) } + else -> result.notImplemented() + } + } + + private fun getAddress(call: MethodCall, result: MethodChannel.Result) { + val latitude = call.argument("latitude")?.toDouble() + val longitude = call.argument("longitude")?.toDouble() + val maxResults = call.argument("maxResults") ?: 1 + if (latitude == null || longitude == null) { + result.error("getAddress-args", "failed because of missing arguments", null) + return + } + + if (!Geocoder.isPresent()) { + result.error("getAddress-unavailable", "Geocoder is unavailable", null) + return + } + + geocoder = geocoder ?: Geocoder(context) + val addresses = try { + geocoder!!.getFromLocation(latitude, longitude, maxResults) ?: ArrayList
() + } catch (e: Exception) { + result.error("getAddress-exception", "failed to get address", e.message) + return + } + + if (addresses.isEmpty()) { + result.error("getAddress-empty", "failed to find any address for latitude=$latitude, longitude=$longitude", null) + } else { + val addressMapList: ArrayList> = ArrayList(addresses.map { address -> + hashMapOf( + "addressLine" to (0..address.maxAddressLineIndex).joinToString(", ") { i -> address.getAddressLine(i) }, + "adminArea" to address.adminArea, + "countryCode" to address.countryCode, + "countryName" to address.countryName, + "featureName" to address.featureName, + "locality" to address.locality, + "postalCode" to address.postalCode, + "subAdminArea" to address.subAdminArea, + "subLocality" to address.subLocality, + "subThoroughfare" to address.subThoroughfare, + "thoroughfare" to address.thoroughfare, + ) + }) + result.success(addressMapList) + } + } + + companion object { + const val CHANNEL = "deckers.thibault/aves/geocoding" + } +} diff --git a/lib/model/entry.dart b/lib/model/entry.dart index ec78febd5..cc9da0b26 100644 --- a/lib/model/entry.dart +++ b/lib/model/entry.dart @@ -7,6 +7,7 @@ import 'package:aves/model/favourite_repo.dart'; import 'package:aves/model/metadata.dart'; import 'package:aves/model/metadata_db.dart'; import 'package:aves/model/multipage.dart'; +import 'package:aves/services/geocoding_service.dart'; import 'package:aves/services/image_file_service.dart'; import 'package:aves/services/metadata_service.dart'; import 'package:aves/services/service_policy.dart'; @@ -18,7 +19,6 @@ import 'package:collection/collection.dart'; import 'package:country_code/country_code.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:geocoder/geocoder.dart'; import 'package:latlong/latlong.dart'; import 'package:path/path.dart' as ppath; @@ -45,10 +45,6 @@ class AvesEntry { final AChangeNotifier imageChangeNotifier = AChangeNotifier(), metadataChangeNotifier = AChangeNotifier(), addressChangeNotifier = AChangeNotifier(); - // Local geocoding requires Google Play Services - // Google remote geocoding requires an API key and is not free - final Future> Function(Coordinates coordinates) _findAddresses = Geocoder.local.findAddressesFromCoordinates; - // TODO TLAD make it dynamic if it depends on OS/lib versions static const List undecodable = [MimeTypes.crw, MimeTypes.djvu, MimeTypes.psd]; @@ -482,9 +478,8 @@ class AvesEntry { // full reverse geocoding, requiring Play Services and some connectivity Future locatePlace({@required bool background}) async { if (!hasGps || hasFineAddress) return; - final coordinates = latLng; try { - Future> call() => _findAddresses(Coordinates(coordinates.latitude, coordinates.longitude)); + Future> call() => GeocodingService.getAddress(latLng); final addresses = await (background ? servicePolicy.call( call, @@ -507,22 +502,21 @@ class AvesEntry { ); } } catch (error, stack) { - debugPrint('$runtimeType locate failed with path=$path coordinates=$coordinates error=$error\n$stack'); + debugPrint('$runtimeType locate failed with path=$path coordinates=$latLng error=$error\n$stack'); } } Future findAddressLine() async { if (!hasGps) return null; - final coordinates = latLng; try { - final addresses = await _findAddresses(Coordinates(coordinates.latitude, coordinates.longitude)); + final addresses = await GeocodingService.getAddress(latLng); if (addresses != null && addresses.isNotEmpty) { final address = addresses.first; return address.addressLine; } } catch (error, stack) { - debugPrint('$runtimeType findAddressLine failed with path=$path coordinates=$coordinates error=$error\n$stack'); + debugPrint('$runtimeType findAddressLine failed with path=$path coordinates=$latLng error=$error\n$stack'); } return null; } diff --git a/lib/model/metadata.dart b/lib/model/metadata.dart index ea7c7a4bd..47a9783da 100644 --- a/lib/model/metadata.dart +++ b/lib/model/metadata.dart @@ -1,6 +1,6 @@ +import 'package:aves/services/geocoding_service.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; -import 'package:geocoder/model.dart'; import 'package:intl/intl.dart'; class DateMetadata { diff --git a/lib/services/geocoding_service.dart b/lib/services/geocoding_service.dart new file mode 100644 index 000000000..f66759643 --- /dev/null +++ b/lib/services/geocoding_service.dart @@ -0,0 +1,56 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:latlong/latlong.dart'; + +class GeocodingService { + static const platform = MethodChannel('deckers.thibault/aves/geocoding'); + + // geocoding requires Google Play Services + static Future> getAddress(LatLng coordinates) async { + try { + final result = await platform.invokeMethod('getAddress', { + 'latitude': coordinates.latitude, + 'longitude': coordinates.longitude, + }); + return (result as List).cast().map((map) => Address.fromMap(map)).toList(); + } on PlatformException catch (e) { + debugPrint('getAddress failed with code=${e.code}, exception=${e.message}, details=${e.details}}'); + } + return []; + } +} + +@immutable +class Address { + final String addressLine, adminArea, countryCode, countryName, featureName, locality, postalCode, subAdminArea, subLocality, subThoroughfare, thoroughfare; + + const Address({ + this.addressLine, + this.adminArea, + this.countryCode, + this.countryName, + this.featureName, + this.locality, + this.postalCode, + this.subAdminArea, + this.subLocality, + this.subThoroughfare, + this.thoroughfare, + }); + + factory Address.fromMap(Map map) => Address( + addressLine: map['addressLine'], + adminArea: map['adminArea'], + countryCode: map['countryCode'], + countryName: map['countryName'], + featureName: map['featureName'], + locality: map['locality'], + postalCode: map['postalCode'], + subAdminArea: map['subAdminArea'], + subLocality: map['subLocality'], + subThoroughfare: map['subThoroughfare'], + thoroughfare: map['thoroughfare'], + ); +} diff --git a/pubspec.lock b/pubspec.lock index 33130381f..baf50608e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -7,14 +7,14 @@ packages: name: _fe_analyzer_shared url: "https://pub.dartlang.org" source: hosted - version: "14.0.0" + version: "17.0.0" analyzer: dependency: transitive description: name: analyzer url: "https://pub.dartlang.org" source: hosted - version: "0.41.2" + version: "1.1.0" ansicolor: dependency: transitive description: @@ -28,14 +28,14 @@ packages: name: archive url: "https://pub.dartlang.org" source: hosted - version: "2.0.13" + version: "3.1.2" args: dependency: transitive description: name: args url: "https://pub.dartlang.org" source: hosted - version: "1.6.0" + version: "2.0.0" async: dependency: transitive description: @@ -49,7 +49,7 @@ packages: name: barcode url: "https://pub.dartlang.org" source: hosted - version: "1.17.1" + version: "2.1.0" boolean_selector: dependency: transitive description: @@ -77,14 +77,14 @@ packages: name: charts_common url: "https://pub.dartlang.org" source: hosted - version: "0.9.0" + version: "0.10.0" charts_flutter: dependency: "direct main" description: name: charts_flutter url: "https://pub.dartlang.org" source: hosted - version: "0.9.0" + version: "0.10.0" cli_util: dependency: transitive description: @@ -126,7 +126,7 @@ packages: name: connectivity_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.0.1" console_log_handler: dependency: transitive description: @@ -140,7 +140,7 @@ packages: name: convert url: "https://pub.dartlang.org" source: hosted - version: "2.1.1" + version: "3.0.0" country_code: dependency: "direct main" description: @@ -161,7 +161,7 @@ packages: name: crypto url: "https://pub.dartlang.org" source: hosted - version: "2.1.5" + version: "3.0.0" decorated_icon: dependency: "direct main" description: @@ -205,21 +205,21 @@ packages: name: file url: "https://pub.dartlang.org" source: hosted - version: "6.0.0" + version: "6.1.0" firebase: dependency: transitive description: name: firebase url: "https://pub.dartlang.org" source: hosted - version: "7.3.3" + version: "9.0.0" firebase_analytics: dependency: "direct main" description: name: firebase_analytics url: "https://pub.dartlang.org" source: hosted - version: "7.0.1" + version: "7.1.1" firebase_analytics_platform_interface: dependency: transitive description: @@ -233,42 +233,42 @@ packages: name: firebase_analytics_web url: "https://pub.dartlang.org" source: hosted - version: "0.1.1" + version: "0.2.0+1" firebase_core: dependency: "direct main" description: name: firebase_core url: "https://pub.dartlang.org" source: hosted - version: "0.7.0" + version: "1.0.1" firebase_core_platform_interface: dependency: transitive description: name: firebase_core_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "3.0.1" + version: "4.0.0" firebase_core_web: dependency: transitive description: name: firebase_core_web url: "https://pub.dartlang.org" source: hosted - version: "0.2.1+3" + version: "1.0.1" firebase_crashlytics: dependency: "direct main" description: name: firebase_crashlytics url: "https://pub.dartlang.org" source: hosted - version: "0.4.0+1" + version: "1.0.0" firebase_crashlytics_platform_interface: dependency: transitive description: name: firebase_crashlytics_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.1.6" + version: "2.0.0" flushbar: dependency: "direct main" description: @@ -341,7 +341,7 @@ packages: name: flutter_markdown url: "https://pub.dartlang.org" source: hosted - version: "0.5.2" + version: "0.6.0" flutter_plugin_android_lifecycle: dependency: transitive description: @@ -362,7 +362,7 @@ packages: name: flutter_svg url: "https://pub.dartlang.org" source: hosted - version: "0.19.3" + version: "0.21.0-nullsafety.0" flutter_test: dependency: "direct dev" description: flutter @@ -378,20 +378,13 @@ packages: description: flutter source: sdk version: "0.0.0" - geocoder: - dependency: "direct main" - description: - name: geocoder - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.1" github: dependency: "direct main" description: name: github url: "https://pub.dartlang.org" source: hosted - version: "7.0.4" + version: "8.0.1" glob: dependency: transitive description: @@ -419,7 +412,7 @@ packages: name: google_maps_flutter_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.0.1" highlight: dependency: transitive description: @@ -433,7 +426,7 @@ packages: name: http url: "https://pub.dartlang.org" source: hosted - version: "0.12.2" + version: "0.13.0" http_multi_server: dependency: transitive description: @@ -447,14 +440,14 @@ packages: name: http_parser url: "https://pub.dartlang.org" source: hosted - version: "3.1.4" + version: "4.0.0" image: dependency: transitive description: name: image url: "https://pub.dartlang.org" source: hosted - version: "2.1.19" + version: "3.0.1" intl: dependency: "direct main" description: @@ -482,7 +475,7 @@ packages: name: json_annotation url: "https://pub.dartlang.org" source: hosted - version: "3.1.1" + version: "4.0.0" latlong: dependency: "direct main" description: @@ -510,7 +503,7 @@ packages: name: markdown url: "https://pub.dartlang.org" source: hosted - version: "3.0.0" + version: "4.0.0" matcher: dependency: transitive description: @@ -580,7 +573,7 @@ packages: name: package_config url: "https://pub.dartlang.org" source: hosted - version: "1.9.3" + version: "2.0.0" package_info: dependency: "direct main" description: @@ -615,14 +608,14 @@ packages: name: path_drawing url: "https://pub.dartlang.org" source: hosted - version: "0.4.1+1" + version: "0.5.0-nullsafety.0" path_parsing: dependency: transitive description: name: path_parsing url: "https://pub.dartlang.org" source: hosted - version: "0.1.4" + version: "0.2.0-nullsafety.0" path_provider_linux: dependency: transitive description: @@ -636,7 +629,7 @@ packages: name: path_provider_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.0.1" path_provider_windows: dependency: transitive description: @@ -650,7 +643,7 @@ packages: name: pdf url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "3.0.1" pedantic: dependency: "direct main" description: @@ -671,21 +664,21 @@ packages: name: permission_handler url: "https://pub.dartlang.org" source: hosted - version: "5.1.0+2" + version: "6.0.1" permission_handler_platform_interface: dependency: transitive description: name: permission_handler_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "2.0.2" + version: "3.0.0+1" petitparser: dependency: transitive description: name: petitparser url: "https://pub.dartlang.org" source: hosted - version: "3.1.0" + version: "4.0.2" platform: dependency: transitive description: @@ -699,7 +692,7 @@ packages: name: plugin_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.0.3" + version: "2.0.0" pool: dependency: transitive description: @@ -720,14 +713,14 @@ packages: name: printing url: "https://pub.dartlang.org" source: hosted - version: "4.1.0" + version: "5.0.2" process: dependency: transitive description: name: process url: "https://pub.dartlang.org" source: hosted - version: "4.0.0" + version: "4.1.0" proj4dart: dependency: transitive description: @@ -755,7 +748,7 @@ packages: name: qr url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "2.0.0" quiver: dependency: transitive description: @@ -811,28 +804,28 @@ packages: name: shelf url: "https://pub.dartlang.org" source: hosted - version: "0.7.9" + version: "1.0.0" shelf_packages_handler: dependency: transitive description: name: shelf_packages_handler url: "https://pub.dartlang.org" source: hosted - version: "2.0.1" + version: "3.0.0" shelf_static: dependency: transitive description: name: shelf_static url: "https://pub.dartlang.org" source: hosted - version: "0.2.9+2" + version: "1.0.0" shelf_web_socket: dependency: transitive description: name: shelf_web_socket url: "https://pub.dartlang.org" source: hosted - version: "0.2.4+1" + version: "1.0.0" sky_engine: dependency: transitive description: flutter @@ -858,7 +851,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.0" + version: "1.8.1" sqflite: dependency: "direct main" description: @@ -914,7 +907,7 @@ packages: name: sync_http url: "https://pub.dartlang.org" source: hosted - version: "0.2.0" + version: "0.3.0" synchronized: dependency: transitive description: @@ -1005,7 +998,7 @@ packages: name: url_launcher_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "2.0.1" + version: "2.0.2" url_launcher_web: dependency: transitive description: @@ -1047,7 +1040,7 @@ packages: name: vm_service url: "https://pub.dartlang.org" source: hosted - version: "5.5.0" + version: "6.0.1-nullsafety.1" watcher: dependency: transitive description: @@ -1061,14 +1054,14 @@ packages: name: web_socket_channel url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "2.0.0" webdriver: dependency: transitive description: name: webdriver url: "https://pub.dartlang.org" source: hosted - version: "2.1.2" + version: "3.0.0" webkit_inspection_protocol: dependency: transitive description: @@ -1103,7 +1096,7 @@ packages: name: xml url: "https://pub.dartlang.org" source: hosted - version: "4.5.1" + version: "5.0.2" yaml: dependency: transitive description: @@ -1113,4 +1106,4 @@ packages: version: "3.1.0" sdks: dart: ">=2.12.0 <3.0.0" - flutter: ">=1.24.0-10.1.pre" + flutter: ">=1.24.0-10" diff --git a/pubspec.yaml b/pubspec.yaml index a6c3273c3..249fd8347 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -9,15 +9,13 @@ environment: # TODO TLAD merge null safe `expansion_tile_card` to fork # TODO TLAD migrate from `flushbar` to basic SnackBar or `another_flushbar` -# TODO TLAD migrate from `geocoder` to `geocoding` (or reimplement) - https://github.com/Baseflow/flutter-geocoding/issues/37 # TODO TLAD remove explicit `overlay_support` version when 1.2.0 is stable (1.0.5 uses deprecated `ancestorWidgetOfExactType`) -# TODO TLAD upgrade printing to >=5 when `flutter_driver` dependency upgrade get on the right channel -# https://github.com/flutter/flutter/pull/75370 -# https://github.com/flutter/flutter/blob/master/packages/flutter_driver/pubspec.yaml -# otherwise, we get this conundrum: -# every version of flutter_driver from sdk depends on crypto 2.1.5 -# pdf >=3.0.1 depends on crypto ^3.0.0 -# printing >=5.0.1 depends on pdf ^3.0.1 + +# TODO TLAD switch to Flutter dev/beta when possible, currently on master because of the following mess: +# printing >=5.0.1 depends on pdf ^3.0.1, pdf >=3.0.1 depends on crypto ^3.0.0 and archive ^3.1.0 +# but `flutter_driver` (shipped with Flutter) dependencies are too old in stable v2.0.1 +# bump `crypto` and others - 2021/02/05 https://github.com/flutter/flutter/commit/bc1cf4945841ba5874f5262b8146d52750e7c11f +# bump `archive` from 3.0.0 to 3.1.2 - 2021/03/04 https://github.com/flutter/flutter/commit/ddcb8d7d6d3fcedc906b2f1bf26b73c018d3dc28 # not null safe, as of 2021/03/09 # `charts_flutter` - https://github.com/google/charts/issues/579 @@ -26,7 +24,6 @@ environment: # `flushbar` - discontinued # `flutter_ijkplayer` - unmaintained? # `flutter_map` - https://github.com/fleaflet/flutter_map/issues/829 -# `geocoder` - unmaintained? - https://github.com/aloisdeniel/flutter_geocoder/issues/61 # `latlong` - archived - migrate to maps_toolkit? cf https://github.com/fleaflet/flutter_map/pull/750 # `palette_generator` - https://github.com/flutter/packages/pull/287 # `panorama` - no issue/PR @@ -61,7 +58,6 @@ dependencies: flutter_markdown: flutter_staggered_animations: flutter_svg: - geocoder: github: google_api_availability: google_maps_flutter: @@ -76,7 +72,7 @@ dependencies: pedantic: percent_indicator: permission_handler: - printing: 4.1.0 + printing: provider: shared_preferences: sqflite: @@ -86,10 +82,6 @@ dependencies: version: xml: -dependency_overrides: - # because of `charts_flutter` - intl: '>=0.17.0' - dev_dependencies: flutter_test: sdk: flutter