From ab96741a18827ab4961df25ba9d19ad85eaacbf9 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Tue, 2 Feb 2021 12:01:17 +0900 Subject: [PATCH] deactivate geocoding and Google maps when Play Services are unavailable --- lib/model/availability.dart | 41 +++++++++++++++++++ lib/model/connectivity.dart | 28 ------------- lib/model/entry.dart | 8 +++- lib/model/source/location.dart | 4 +- lib/widgets/home_page.dart | 4 +- lib/widgets/viewer/entry_vertical_pager.dart | 4 +- lib/widgets/viewer/entry_viewer_stack.dart | 4 +- lib/widgets/viewer/info/location_section.dart | 6 +-- lib/widgets/viewer/info/maps/common.dart | 17 +++++--- pubspec.lock | 7 ++++ pubspec.yaml | 1 + 11 files changed, 78 insertions(+), 46 deletions(-) create mode 100644 lib/model/availability.dart delete mode 100644 lib/model/connectivity.dart diff --git a/lib/model/availability.dart b/lib/model/availability.dart new file mode 100644 index 000000000..735c802e3 --- /dev/null +++ b/lib/model/availability.dart @@ -0,0 +1,41 @@ +import 'package:connectivity/connectivity.dart'; +import 'package:flutter/foundation.dart'; +import 'package:google_api_availability/google_api_availability.dart'; + +final AvesAvailability availability = AvesAvailability._private(); + +class AvesAvailability { + bool _isConnected, _hasPlayServices; + + AvesAvailability._private() { + Connectivity().onConnectivityChanged.listen(_updateConnectivityFromResult); + } + + void onResume() => _isConnected = null; + + Future get isConnected async { + if (_isConnected != null) return SynchronousFuture(_isConnected); + final result = await (Connectivity().checkConnectivity()); + _updateConnectivityFromResult(result); + return _isConnected; + } + + void _updateConnectivityFromResult(ConnectivityResult result) { + final newValue = result != ConnectivityResult.none; + if (_isConnected != newValue) { + _isConnected = newValue; + debugPrint('Device is connected=$_isConnected'); + } + } + + Future get hasPlayServices async { + if (_hasPlayServices != null) return SynchronousFuture(_hasPlayServices); + final result = await GoogleApiAvailability.instance.checkGooglePlayServicesAvailability(); + _hasPlayServices = result == GooglePlayServicesAvailability.success; + debugPrint('Device has Play Services=$_hasPlayServices'); + return _hasPlayServices; + } + + // local geolocation with `geocoder` requires Play Services + Future get canGeolocate => Future.wait([isConnected, hasPlayServices]).then((results) => results.every((result) => result)); +} diff --git a/lib/model/connectivity.dart b/lib/model/connectivity.dart deleted file mode 100644 index 009c0fa1c..000000000 --- a/lib/model/connectivity.dart +++ /dev/null @@ -1,28 +0,0 @@ -import 'package:connectivity/connectivity.dart'; -import 'package:flutter/foundation.dart'; - -final AvesConnectivity connectivity = AvesConnectivity._private(); - -class AvesConnectivity { - bool _isConnected; - - AvesConnectivity._private() { - Connectivity().onConnectivityChanged.listen(_updateFromResult); - } - - void onResume() => _isConnected = null; - - Future get isConnected async { - if (_isConnected != null) return SynchronousFuture(_isConnected); - final result = await (Connectivity().checkConnectivity()); - _updateFromResult(result); - return _isConnected; - } - - Future get canGeolocate => isConnected; - - void _updateFromResult(ConnectivityResult result) { - _isConnected = result != ConnectivityResult.none; - debugPrint('Device is connected=$_isConnected'); - } -} diff --git a/lib/model/entry.dart b/lib/model/entry.dart index 384b29a4b..8e2831d0d 100644 --- a/lib/model/entry.dart +++ b/lib/model/entry.dart @@ -42,6 +42,10 @@ 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.psd]; @@ -441,7 +445,7 @@ class AvesEntry { final coordinates = Coordinates(latitude, longitude); try { - Future> call() => Geocoder.local.findAddressesFromCoordinates(coordinates); + Future> call() => _findAddresses(coordinates); final addresses = await (background ? servicePolicy.call( call, @@ -475,7 +479,7 @@ class AvesEntry { final coordinates = Coordinates(latitude, longitude); try { - final addresses = await Geocoder.local.findAddressesFromCoordinates(coordinates); + final addresses = await _findAddresses(coordinates); if (addresses != null && addresses.isNotEmpty) { final address = addresses.first; return address.addressLine; diff --git a/lib/model/source/location.dart b/lib/model/source/location.dart index 37d6c0af8..ef5d36f51 100644 --- a/lib/model/source/location.dart +++ b/lib/model/source/location.dart @@ -1,6 +1,6 @@ import 'dart:math'; -import 'package:aves/model/connectivity.dart'; +import 'package:aves/model/availability.dart'; import 'package:aves/model/entry.dart'; import 'package:aves/model/filters/location.dart'; import 'package:aves/model/metadata.dart'; @@ -28,7 +28,7 @@ mixin LocationMixin on SourceBase { } Future locateEntries() async { - if (!(await connectivity.canGeolocate)) return; + if (!(await availability.canGeolocate)) return; // final stopwatch = Stopwatch()..start(); final byLocated = groupBy(rawEntries.where((entry) => entry.hasGps), (entry) => entry.isLocated); diff --git a/lib/widgets/home_page.dart b/lib/widgets/home_page.dart index 0cff14049..1a0374e18 100644 --- a/lib/widgets/home_page.dart +++ b/lib/widgets/home_page.dart @@ -1,5 +1,5 @@ import 'package:aves/main.dart'; -import 'package:aves/model/connectivity.dart'; +import 'package:aves/model/availability.dart'; import 'package:aves/model/entry.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/settings/home_page.dart'; @@ -115,7 +115,7 @@ class _HomePageState extends State { // cataloguing is essential for coordinates and video rotation await entry.catalog(); // locating is fine in the background - unawaited(connectivity.canGeolocate.then((connected) { + unawaited(availability.canGeolocate.then((connected) { if (connected) { entry.locate(); } diff --git a/lib/widgets/viewer/entry_vertical_pager.dart b/lib/widgets/viewer/entry_vertical_pager.dart index 2abf301a7..8a4c00325 100644 --- a/lib/widgets/viewer/entry_vertical_pager.dart +++ b/lib/widgets/viewer/entry_vertical_pager.dart @@ -1,6 +1,6 @@ import 'dart:math'; -import 'package:aves/model/connectivity.dart'; +import 'package:aves/model/availability.dart'; import 'package:aves/model/entry.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/widgets/common/magnifier/pan/scroll_physics.dart'; @@ -152,7 +152,7 @@ class _ViewerVerticalPageViewState extends State { // make sure to locate the entry, // so that we can display the address instead of coordinates // even when initial collection locating has not reached this entry yet - connectivity.canGeolocate.then((connected) { + availability.canGeolocate.then((connected) { if (connected) { entry.locate(); } diff --git a/lib/widgets/viewer/entry_viewer_stack.dart b/lib/widgets/viewer/entry_viewer_stack.dart index c6fc73aee..405817f62 100644 --- a/lib/widgets/viewer/entry_viewer_stack.dart +++ b/lib/widgets/viewer/entry_viewer_stack.dart @@ -1,6 +1,6 @@ import 'dart:math'; -import 'package:aves/model/connectivity.dart'; +import 'package:aves/model/availability.dart'; import 'package:aves/model/entry.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/settings/screen_on.dart'; @@ -151,7 +151,7 @@ class _EntryViewerStackState extends State with SingleTickerPr _pauseVideoControllers(); break; case AppLifecycleState.resumed: - connectivity.onResume(); + availability.onResume(); break; default: break; diff --git a/lib/widgets/viewer/info/location_section.dart b/lib/widgets/viewer/info/location_section.dart index 1dd7a3e0c..485c4639a 100644 --- a/lib/widgets/viewer/info/location_section.dart +++ b/lib/widgets/viewer/info/location_section.dart @@ -1,4 +1,4 @@ -import 'package:aves/model/connectivity.dart'; +import 'package:aves/model/availability.dart'; import 'package:aves/model/entry.dart'; import 'package:aves/model/filters/location.dart'; import 'package:aves/model/settings/coordinate_format.dart'; @@ -104,7 +104,7 @@ class _LocationSectionState extends State with TickerProviderSt children: [ if (widget.showTitle) SectionRow(AIcons.location), FutureBuilder( - future: connectivity.isConnected, + future: availability.isConnected, builder: (context, snapshot) { if (snapshot.data != true) return SizedBox(); return NotificationListener( @@ -181,7 +181,7 @@ class _AddressInfoGroupState extends State<_AddressInfoGroup> { @override void initState() { super.initState(); - _addressLineLoader = connectivity.canGeolocate.then((connected) { + _addressLineLoader = availability.canGeolocate.then((connected) { if (connected) { return entry.findAddressLine(); } diff --git a/lib/widgets/viewer/info/maps/common.dart b/lib/widgets/viewer/info/maps/common.dart index ee28ed748..6cf73e190 100644 --- a/lib/widgets/viewer/info/maps/common.dart +++ b/lib/widgets/viewer/info/maps/common.dart @@ -1,3 +1,4 @@ +import 'package:aves/model/availability.dart'; import 'package:aves/model/settings/map_style.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/services/android_app_service.dart'; @@ -73,13 +74,19 @@ class MapButtonPanel extends StatelessWidget { MapOverlayButton( icon: AIcons.layers, onPressed: () async { + final hasPlayServices = await availability.hasPlayServices; + final availableStyles = EntryMapStyle.values.where((style) => !style.isGoogleMaps || hasPlayServices); + final preferredStyle = settings.infoMapStyle; + final initialStyle = availableStyles.contains(preferredStyle) ? preferredStyle : availableStyles.first; final style = await showDialog( context: context, - builder: (context) => AvesSelectionDialog( - initialValue: settings.infoMapStyle, - options: Map.fromEntries(EntryMapStyle.values.map((v) => MapEntry(v, v.name))), - title: 'Map Style', - ), + builder: (context) { + return AvesSelectionDialog( + initialValue: initialStyle, + options: Map.fromEntries(availableStyles.map((v) => MapEntry(v, v.name))), + title: 'Map Style', + ); + }, ); // wait for the dialog to hide because switching to Google Maps layer may block the UI await Future.delayed(Durations.dialogTransitionAnimation * timeDilation); diff --git a/pubspec.lock b/pubspec.lock index 514b21954..432448f83 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -387,6 +387,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.2.0" + google_api_availability: + dependency: "direct main" + description: + name: google_api_availability + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.4" google_maps_flutter: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 19b833434..888311dc2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -55,6 +55,7 @@ dependencies: flutter_staggered_animations: flutter_svg: geocoder: + google_api_availability: google_maps_flutter: intl: latlong: # for flutter_map