diff --git a/CHANGELOG.md b/CHANGELOG.md index fcbf50668..3712807e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ All notable changes to this project will be documented in this file. - upgraded Flutter to stable v3.0.1 - stretching overscroll effect +- disabled Google Maps layer on Android Lollipop ### Fixed diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/DeviceHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/DeviceHandler.kt index be8ee35ee..7bb635ad6 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/DeviceHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/DeviceHandler.kt @@ -32,10 +32,6 @@ class DeviceHandler(private val context: Context) : MethodCallHandler { "canPinShortcut" to ShortcutManagerCompat.isRequestPinShortcutSupported(context), "canPrint" to (sdkInt >= Build.VERSION_CODES.KITKAT), "canRenderFlagEmojis" to (sdkInt >= Build.VERSION_CODES.LOLLIPOP), - // as of google_maps_flutter v2.1.1, minSDK is 20 because of default PlatformView usage, - // but using hybrid composition would make it usable on API 19 too, - // cf https://github.com/flutter/flutter/issues/23728 - "canRenderGoogleMaps" to (sdkInt >= Build.VERSION_CODES.KITKAT_WATCH), "showPinShortcutFeedback" to (sdkInt >= Build.VERSION_CODES.O), "supportEdgeToEdgeUIMode" to (sdkInt >= Build.VERSION_CODES.Q), ) diff --git a/lib/model/availability.dart b/lib/model/availability.dart index 86f9c4640..fb5446d6e 100644 --- a/lib/model/availability.dart +++ b/lib/model/availability.dart @@ -1,5 +1,6 @@ -import 'package:aves/model/device.dart'; -import 'package:aves_services_platform/aves_services_platform.dart'; +import 'package:aves/model/settings/enums/map_style.dart'; +import 'package:aves/services/common/services.dart'; +import 'package:aves_map/aves_map.dart'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:flutter/foundation.dart'; @@ -10,7 +11,7 @@ abstract class AvesAvailability { Future get canLocatePlaces; - Future get canUseDeviceMaps; + List get mapStyles; } class LiveAvesAvailability implements AvesAvailability { @@ -42,11 +43,11 @@ class LiveAvesAvailability implements AvesAvailability { // local geocoding with `geocoder` seems to require Google Play Services // what about devices with Huawei Mobile Services? @override - Future get canLocatePlaces => Future.wait([ - isConnected, - PlatformMobileServices().isServiceAvailable(), - ]).then((results) => results.every((result) => result)); + Future get canLocatePlaces async => mobileServices.isServiceAvailable && await isConnected; @override - Future get canUseDeviceMaps async => device.canRenderGoogleMaps && await PlatformMobileServices().isServiceAvailable(); + List get mapStyles => [ + ...mobileServices.mapStyles, + ...EntryMapStyle.values.where((v) => !v.needMobileService), + ]; } diff --git a/lib/model/device.dart b/lib/model/device.dart index e26583f61..ab288f972 100644 --- a/lib/model/device.dart +++ b/lib/model/device.dart @@ -5,7 +5,7 @@ final Device device = Device._private(); class Device { late final String _userAgent; - late final bool _canGrantDirectoryAccess, _canPinShortcut, _canPrint, _canRenderFlagEmojis, _canRenderGoogleMaps; + late final bool _canGrantDirectoryAccess, _canPinShortcut, _canPrint, _canRenderFlagEmojis; late final bool _showPinShortcutFeedback, _supportEdgeToEdgeUIMode; String get userAgent => _userAgent; @@ -18,8 +18,6 @@ class Device { bool get canRenderFlagEmojis => _canRenderFlagEmojis; - bool get canRenderGoogleMaps => _canRenderGoogleMaps; - bool get showPinShortcutFeedback => _showPinShortcutFeedback; bool get supportEdgeToEdgeUIMode => _supportEdgeToEdgeUIMode; @@ -35,7 +33,6 @@ class Device { _canPinShortcut = capabilities['canPinShortcut'] ?? false; _canPrint = capabilities['canPrint'] ?? false; _canRenderFlagEmojis = capabilities['canRenderFlagEmojis'] ?? false; - _canRenderGoogleMaps = capabilities['canRenderGoogleMaps'] ?? false; _showPinShortcutFeedback = capabilities['showPinShortcutFeedback'] ?? false; _supportEdgeToEdgeUIMode = capabilities['supportEdgeToEdgeUIMode'] ?? false; } diff --git a/lib/model/settings/enums/map_style.dart b/lib/model/settings/enums/map_style.dart index 359436b2b..3bed08e31 100644 --- a/lib/model/settings/enums/map_style.dart +++ b/lib/model/settings/enums/map_style.dart @@ -37,7 +37,7 @@ extension ExtraEntryMapStyle on EntryMapStyle { } } - bool get needDeviceService { + bool get needMobileService { switch (this) { case EntryMapStyle.osmHot: case EntryMapStyle.stamenToner: diff --git a/lib/model/settings/settings.dart b/lib/model/settings/settings.dart index eb97a8afb..6c0617863 100644 --- a/lib/model/settings/settings.dart +++ b/lib/model/settings/settings.dart @@ -12,7 +12,6 @@ import 'package:aves/model/source/enums.dart'; import 'package:aves/services/accessibility_service.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves_map/aves_map.dart'; -import 'package:aves_services_platform/aves_services_platform.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -165,11 +164,11 @@ class Settings extends ChangeNotifier { enableOverlayBlurEffect = performanceClass >= 29; // availability - final isDeviceMapAvailable = await availability.canUseDeviceMaps; - if (isDeviceMapAvailable) { - infoMapStyle = PlatformMobileServices().defaultMapStyle; + final defaultMapStyle = mobileServices.defaultMapStyle; + if (mobileServices.mapStyles.contains(defaultMapStyle)) { + infoMapStyle = defaultMapStyle; } else { - final styles = EntryMapStyle.values.whereNot((v) => v.needDeviceService).toList(); + final styles = EntryMapStyle.values.whereNot((v) => v.needMobileService).toList(); infoMapStyle = styles[Random().nextInt(styles.length)]; } @@ -516,7 +515,11 @@ class Settings extends ChangeNotifier { // info - EntryMapStyle get infoMapStyle => getEnumOrDefault(infoMapStyleKey, SettingsDefaults.infoMapStyle, EntryMapStyle.values); + EntryMapStyle get infoMapStyle { + final preferred = getEnumOrDefault(infoMapStyleKey, SettingsDefaults.infoMapStyle, EntryMapStyle.values); + final available = availability.mapStyles; + return available.contains(preferred) ? preferred : available.first; + } set infoMapStyle(EntryMapStyle newValue) => setAndNotify(infoMapStyleKey, newValue.toString()); diff --git a/lib/services/common/services.dart b/lib/services/common/services.dart index 12f817a61..9a2a1e062 100644 --- a/lib/services/common/services.dart +++ b/lib/services/common/services.dart @@ -14,6 +14,8 @@ import 'package:aves/services/storage_service.dart'; import 'package:aves/services/window_service.dart'; import 'package:aves_report/aves_report.dart'; import 'package:aves_report_platform/aves_report_platform.dart'; +import 'package:aves_services/aves_services.dart'; +import 'package:aves_services_platform/aves_services_platform.dart'; import 'package:get_it/get_it.dart'; import 'package:path/path.dart' as p; @@ -33,6 +35,7 @@ final MediaFileService mediaFileService = getIt(); final MediaStoreService mediaStoreService = getIt(); final MetadataEditService metadataEditService = getIt(); final MetadataFetchService metadataFetchService = getIt(); +final MobileServices mobileServices = getIt(); final ReportService reportService = getIt(); final StorageService storageService = getIt(); final WindowService windowService = getIt(); @@ -49,6 +52,7 @@ void initPlatformServices() { getIt.registerLazySingleton(PlatformMediaStoreService.new); getIt.registerLazySingleton(PlatformMetadataEditService.new); getIt.registerLazySingleton(PlatformMetadataFetchService.new); + getIt.registerLazySingleton(PlatformMobileServices.new); getIt.registerLazySingleton(PlatformReportService.new); getIt.registerLazySingleton(PlatformStorageService.new); getIt.registerLazySingleton(PlatformWindowService.new); diff --git a/lib/widgets/about/bug_report.dart b/lib/widgets/about/bug_report.dart index 1dff3e106..2cdb16d6e 100644 --- a/lib/widgets/about/bug_report.dart +++ b/lib/widgets/about/bug_report.dart @@ -15,7 +15,6 @@ import 'package:aves/widgets/common/action_mixins/feedback.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/identity/aves_filter_chip.dart'; import 'package:aves/widgets/common/identity/buttons.dart'; -import 'package:aves_services_platform/aves_services_platform.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -145,7 +144,6 @@ class _BugReportState extends State with FeedbackMixin { final packageInfo = await PackageInfo.fromPlatform(); final androidInfo = await DeviceInfoPlugin().androidInfo; final installer = await androidAppService.getAppInstaller(); - final hasMobileServices = await PlatformMobileServices().isServiceAvailable(); final flavor = context.read().toString().split('.')[1]; return [ 'Aves version: ${packageInfo.version}-$flavor (Build ${packageInfo.buildNumber})', @@ -153,7 +151,7 @@ class _BugReportState extends State with FeedbackMixin { 'Android version: ${androidInfo.version.release} (SDK ${androidInfo.version.sdkInt})', 'Android build: ${androidInfo.display}', 'Device: ${androidInfo.manufacturer} ${androidInfo.model}', - 'Mobile services: ${hasMobileServices ? 'ready' : 'not available'}', + 'Mobile services: ${mobileServices.isServiceAvailable ? 'ready' : 'not available'}', 'System locales: ${WidgetsBinding.instance.window.locales.join(', ')}', 'Aves locale: ${settings.locale ?? 'system'} -> ${settings.appliedLocale}', 'Installer: $installer', diff --git a/lib/widgets/aves_app.dart b/lib/widgets/aves_app.dart index 655268c5c..7c25a05d1 100644 --- a/lib/widgets/aves_app.dart +++ b/lib/widgets/aves_app.dart @@ -30,7 +30,6 @@ import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/providers/highlight_info_provider.dart'; import 'package:aves/widgets/home_page.dart'; import 'package:aves/widgets/welcome_page.dart'; -import 'package:aves_services_platform/aves_services_platform.dart'; import 'package:equatable/equatable.dart'; import 'package:fijkplayer/fijkplayer.dart'; import 'package:flutter/foundation.dart'; @@ -225,6 +224,7 @@ class _AvesAppState extends State with WidgetsBindingObserver { final stopwatch = Stopwatch()..start(); await device.init(); + await mobileServices.init(); await settings.init(monitorPlatformSettings: true); settings.isRotationLocked = await windowService.isRotationLocked(); settings.areAnimationsRemoved = await AccessibilityService.areAnimationsRemoved(); @@ -273,14 +273,13 @@ class _AvesAppState extends State with WidgetsBindingObserver { FlutterError.onError = reportService.recordFlutterError; final now = DateTime.now(); - final hasMobileServices = await PlatformMobileServices().isServiceAvailable(); await reportService.setCustomKeys({ 'build_mode': kReleaseMode ? 'release' : kProfileMode ? 'profile' : 'debug', - 'has_mobile_services': hasMobileServices, + 'has_mobile_services': mobileServices.isServiceAvailable, 'locales': WidgetsBinding.instance.window.locales.join(', '), 'time_zone': '${now.timeZoneName} (${now.timeZoneOffset})', }); diff --git a/lib/widgets/common/map/buttons/panel.dart b/lib/widgets/common/map/buttons/panel.dart index 31d0505b9..62377021b 100644 --- a/lib/widgets/common/map/buttons/panel.dart +++ b/lib/widgets/common/map/buttons/panel.dart @@ -9,7 +9,6 @@ import 'package:aves/widgets/common/map/buttons/coordinate_filter.dart'; import 'package:aves/widgets/common/map/compass.dart'; import 'package:aves/widgets/dialogs/aves_selection_dialog.dart'; import 'package:aves_map/aves_map.dart'; -import 'package:aves_services_platform/aves_services_platform.dart'; import 'package:flutter/material.dart'; import 'package:latlong2/latlong.dart'; import 'package:provider/provider.dart'; @@ -125,24 +124,15 @@ class MapButtonPanel extends StatelessWidget { padding: EdgeInsets.only(top: padding), child: MapOverlayButton( icon: const Icon(AIcons.layers), - onPressed: () async { - final canUseDeviceMaps = await availability.canUseDeviceMaps; - final availableStyles = [ - if (canUseDeviceMaps) ...PlatformMobileServices().mapStyles, - ...EntryMapStyle.values.where((v) => !v.needDeviceService), - ]; - final preferredStyle = settings.infoMapStyle; - final initialStyle = availableStyles.contains(preferredStyle) ? preferredStyle : availableStyles.first; - await showSelectionDialog( - context: context, - builder: (context) => AvesSelectionDialog( - initialValue: initialStyle, - options: Map.fromEntries(availableStyles.map((v) => MapEntry(v, v.getName(context)))), - title: context.l10n.mapStyleTitle, - ), - onSelection: (v) => settings.infoMapStyle = v, - ); - }, + onPressed: () => showSelectionDialog( + context: context, + builder: (context) => AvesSelectionDialog( + initialValue: settings.infoMapStyle, + options: Map.fromEntries(availability.mapStyles.map((v) => MapEntry(v, v.getName(context)))), + title: context.l10n.mapStyleTitle, + ), + onSelection: (v) => settings.infoMapStyle = v, + ), tooltip: context.l10n.mapStyleTooltip, ), ), diff --git a/lib/widgets/common/map/geo_map.dart b/lib/widgets/common/map/geo_map.dart index a6c598cb3..44de80547 100644 --- a/lib/widgets/common/map/geo_map.dart +++ b/lib/widgets/common/map/geo_map.dart @@ -5,6 +5,7 @@ import 'package:aves/model/entry.dart'; import 'package:aves/model/entry_images.dart'; import 'package:aves/model/settings/enums/map_style.dart'; import 'package:aves/model/settings/settings.dart'; +import 'package:aves/services/common/services.dart'; import 'package:aves/theme/durations.dart'; import 'package:aves/utils/change_notifier.dart'; import 'package:aves/utils/constants.dart'; @@ -15,7 +16,6 @@ import 'package:aves/widgets/common/map/decorator.dart'; import 'package:aves/widgets/common/map/leaflet/map.dart'; import 'package:aves/widgets/common/thumbnail/image.dart'; import 'package:aves_map/aves_map.dart'; -import 'package:aves_services_platform/aves_services_platform.dart'; import 'package:collection/collection.dart'; import 'package:fluster/fluster.dart'; import 'package:flutter/material.dart'; @@ -71,8 +71,6 @@ class _GeoMapState extends State { List get entries => widget.entries; - static final _platformMobileServices = PlatformMobileServices(); - // cap initial zoom to avoid a zoom change // when toggling overlay on Google map initial state static const double minInitialZoom = 3; @@ -172,7 +170,7 @@ class _GeoMapState extends State { case EntryMapStyle.googleTerrain: case EntryMapStyle.hmsNormal: case EntryMapStyle.hmsTerrain: - child = _platformMobileServices.buildMap( + child = mobileServices.buildMap( controller: widget.controller, clusterListenable: _clusterChangeNotifier, boundsNotifier: _boundsNotifier, diff --git a/plugins/aves_services/lib/aves_services.dart b/plugins/aves_services/lib/aves_services.dart index 197267d2e..200c6ead2 100644 --- a/plugins/aves_services/lib/aves_services.dart +++ b/plugins/aves_services/lib/aves_services.dart @@ -5,7 +5,9 @@ import 'package:flutter/widgets.dart'; import 'package:latlong2/latlong.dart'; abstract class MobileServices { - Future isServiceAvailable(); + Future init(); + + bool get isServiceAvailable; EntryMapStyle get defaultMapStyle; diff --git a/plugins/aves_services_google/lib/aves_services_platform.dart b/plugins/aves_services_google/lib/aves_services_platform.dart index bfe6d1bf8..4bc46e554 100644 --- a/plugins/aves_services_google/lib/aves_services_platform.dart +++ b/plugins/aves_services_google/lib/aves_services_platform.dart @@ -3,28 +3,43 @@ library aves_services_platform; import 'package:aves_map/aves_map.dart'; import 'package:aves_services/aves_services.dart'; import 'package:aves_services_platform/src/map.dart'; -import 'package:flutter/foundation.dart'; +import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter/widgets.dart'; import 'package:google_api_availability/google_api_availability.dart'; import 'package:latlong2/latlong.dart'; class PlatformMobileServices extends MobileServices { - bool? _isAvailable; + bool _isAvailable = false; + bool _canRenderMaps = false; @override - Future isServiceAvailable() async { - if (_isAvailable != null) return SynchronousFuture(_isAvailable!); + Future init() async { final result = await GoogleApiAvailability.instance.checkGooglePlayServicesAvailability(); _isAvailable = result == GooglePlayServicesAvailability.success; debugPrint('Device has Google Play Services=$_isAvailable'); - return _isAvailable!; + + // as of google_maps_flutter v2.1.1, minSDK is 20 because of default PlatformView usage, + // but using hybrid composition would make it usable on API 19 too, + // cf https://github.com/flutter/flutter/issues/23728 + // as of google_maps_flutter v2.1.5, Flutter v3.0.1 makes the map hide overlay widgets on API <=22 + final androidInfo = await DeviceInfoPlugin().androidInfo; + _canRenderMaps = (androidInfo.version.sdkInt ?? 0) >= 23; } + @override + bool get isServiceAvailable => _isAvailable; + @override EntryMapStyle get defaultMapStyle => EntryMapStyle.googleNormal; @override - List get mapStyles => [EntryMapStyle.googleNormal, EntryMapStyle.googleHybrid, EntryMapStyle.googleTerrain]; + List get mapStyles => (isServiceAvailable && _canRenderMaps) + ? [ + EntryMapStyle.googleNormal, + EntryMapStyle.googleHybrid, + EntryMapStyle.googleTerrain, + ] + : []; @override Widget buildMap({ diff --git a/plugins/aves_services_google/pubspec.yaml b/plugins/aves_services_google/pubspec.yaml index c402b3751..b2b3f16f1 100644 --- a/plugins/aves_services_google/pubspec.yaml +++ b/plugins/aves_services_google/pubspec.yaml @@ -12,6 +12,7 @@ dependencies: path: ../aves_map aves_services: path: ../aves_services + device_info_plus: google_api_availability: google_maps_flutter: latlong2: diff --git a/plugins/aves_services_huawei/lib/aves_services_platform.dart b/plugins/aves_services_huawei/lib/aves_services_platform.dart index be5ab6a14..6132349fd 100644 --- a/plugins/aves_services_huawei/lib/aves_services_platform.dart +++ b/plugins/aves_services_huawei/lib/aves_services_platform.dart @@ -3,7 +3,6 @@ library aves_services_platform; import 'package:aves_map/aves_map.dart'; import 'package:aves_services/aves_services.dart'; import 'package:aves_services_platform/src/map.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'package:huawei_hmsavailability/huawei_hmsavailability.dart'; import 'package:latlong2/latlong.dart'; @@ -12,22 +11,28 @@ class PlatformMobileServices extends MobileServices { // cf https://developer.huawei.com/consumer/en/doc/development/hmscore-common-References/huaweiapiavailability-0000001050121134#section9492524178 static const int _hmsCoreAvailable = 0; - bool? _isAvailable; + bool _isAvailable = false; @override - Future isServiceAvailable() async { - if (_isAvailable != null) return SynchronousFuture(_isAvailable!); + Future init() async { final result = await HmsApiAvailability().isHMSAvailable(); _isAvailable = result == _hmsCoreAvailable; debugPrint('Device has Huawei Mobile Services=$_isAvailable'); - return _isAvailable!; } + @override + bool get isServiceAvailable => _isAvailable; + @override EntryMapStyle get defaultMapStyle => EntryMapStyle.hmsNormal; @override - List get mapStyles => [EntryMapStyle.hmsNormal, EntryMapStyle.hmsTerrain]; + List get mapStyles => isServiceAvailable + ? [ + EntryMapStyle.hmsNormal, + EntryMapStyle.hmsTerrain, + ] + : []; @override Widget buildMap({