deactivate geocoding and Google maps when Play Services are unavailable

This commit is contained in:
Thibault Deckers 2021-02-02 12:01:17 +09:00
parent 910dd1fe54
commit ab96741a18
11 changed files with 78 additions and 46 deletions

View file

@ -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<bool> 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<bool> 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<bool> get canGeolocate => Future.wait<bool>([isConnected, hasPlayServices]).then((results) => results.every((result) => result));
}

View file

@ -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<bool> get isConnected async {
if (_isConnected != null) return SynchronousFuture(_isConnected);
final result = await (Connectivity().checkConnectivity());
_updateFromResult(result);
return _isConnected;
}
Future<bool> get canGeolocate => isConnected;
void _updateFromResult(ConnectivityResult result) {
_isConnected = result != ConnectivityResult.none;
debugPrint('Device is connected=$_isConnected');
}
}

View file

@ -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<List<Address>> Function(Coordinates coordinates) _findAddresses = Geocoder.local.findAddressesFromCoordinates;
// TODO TLAD make it dynamic if it depends on OS/lib versions
static const List<String> undecodable = [MimeTypes.crw, MimeTypes.psd];
@ -441,7 +445,7 @@ class AvesEntry {
final coordinates = Coordinates(latitude, longitude);
try {
Future<List<Address>> call() => Geocoder.local.findAddressesFromCoordinates(coordinates);
Future<List<Address>> 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;

View file

@ -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<void> locateEntries() async {
if (!(await connectivity.canGeolocate)) return;
if (!(await availability.canGeolocate)) return;
// final stopwatch = Stopwatch()..start();
final byLocated = groupBy<AvesEntry, bool>(rawEntries.where((entry) => entry.hasGps), (entry) => entry.isLocated);

View file

@ -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<HomePage> {
// 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();
}

View file

@ -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<ViewerVerticalPageView> {
// 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();
}

View file

@ -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<EntryViewerStack> with SingleTickerPr
_pauseVideoControllers();
break;
case AppLifecycleState.resumed:
connectivity.onResume();
availability.onResume();
break;
default:
break;

View file

@ -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<LocationSection> with TickerProviderSt
children: [
if (widget.showTitle) SectionRow(AIcons.location),
FutureBuilder<bool>(
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();
}

View file

@ -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<EntryMapStyle>(
context: context,
builder: (context) => AvesSelectionDialog<EntryMapStyle>(
initialValue: settings.infoMapStyle,
options: Map.fromEntries(EntryMapStyle.values.map((v) => MapEntry(v, v.name))),
title: 'Map Style',
),
builder: (context) {
return AvesSelectionDialog<EntryMapStyle>(
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);

View file

@ -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:

View file

@ -55,6 +55,7 @@ dependencies:
flutter_staggered_animations:
flutter_svg:
geocoder:
google_api_availability:
google_maps_flutter:
intl:
latlong: # for flutter_map