diff --git a/CHANGELOG.md b/CHANGELOG.md index 48f554795..6496c8b6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,11 @@ All notable changes to this project will be documented in this file. - Info: export metadata to text file - Accessibility: apply bold font system setting +- `libre` app flavor (no mobile service maps, no Crashlytics) + +### Changed + +- No default map style for `izzy` and `libre` flavors ### Fixed diff --git a/lib/app_flavor.dart b/lib/app_flavor.dart index 69d2e74c7..40e0e376c 100644 --- a/lib/app_flavor.dart +++ b/lib/app_flavor.dart @@ -11,4 +11,15 @@ extension ExtraAppFlavor on AppFlavor { return false; } } + + bool get hasMapStyleDefault { + switch (this) { + case AppFlavor.play: + case AppFlavor.huawei: + return true; + case AppFlavor.izzy: + case AppFlavor.libre: + return false; + } + } } diff --git a/lib/model/settings/defaults.dart b/lib/model/settings/defaults.dart index 71a382fd5..588336d0e 100644 --- a/lib/model/settings/defaults.dart +++ b/lib/model/settings/defaults.dart @@ -7,7 +7,6 @@ import 'package:aves/model/source/enums/enums.dart'; import 'package:aves/widgets/filter_grids/albums_page.dart'; import 'package:aves/widgets/filter_grids/countries_page.dart'; import 'package:aves/widgets/filter_grids/tags_page.dart'; -import 'package:aves_map/aves_map.dart'; import 'package:flutter/material.dart'; class SettingsDefaults { @@ -104,7 +103,6 @@ class SettingsDefaults { static const subtitleBackgroundColor = Colors.transparent; // info - static const infoMapStyle = EntryMapStyle.stamenWatercolor; // `infoMapStyle` has a contextual default value static const infoMapZoom = 12.0; static const coordinateFormat = CoordinateFormat.dms; static const unitSystem = UnitSystem.metric; diff --git a/lib/model/settings/enums/map_style.dart b/lib/model/settings/enums/map_style.dart index 3bed08e31..cedc7c71f 100644 --- a/lib/model/settings/enums/map_style.dart +++ b/lib/model/settings/enums/map_style.dart @@ -3,6 +3,19 @@ import 'package:aves_map/aves_map.dart'; import 'package:flutter/widgets.dart'; extension ExtraEntryMapStyle on EntryMapStyle { + static bool isHeavy(EntryMapStyle? style) { + switch (style) { + case EntryMapStyle.googleNormal: + case EntryMapStyle.googleHybrid: + case EntryMapStyle.googleTerrain: + case EntryMapStyle.hmsNormal: + case EntryMapStyle.hmsTerrain: + return true; + default: + return false; + } + } + String getName(BuildContext context) { switch (this) { case EntryMapStyle.googleNormal: @@ -24,19 +37,6 @@ extension ExtraEntryMapStyle on EntryMapStyle { } } - bool get isHeavy { - switch (this) { - case EntryMapStyle.googleNormal: - case EntryMapStyle.googleHybrid: - case EntryMapStyle.googleTerrain: - case EntryMapStyle.hmsNormal: - case EntryMapStyle.hmsTerrain: - return true; - default: - return false; - } - } - bool get needMobileService { switch (this) { case EntryMapStyle.osmHot: diff --git a/lib/model/settings/settings.dart b/lib/model/settings/settings.dart index b90c4d9e6..40431a1a5 100644 --- a/lib/model/settings/settings.dart +++ b/lib/model/settings/settings.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:math'; +import 'package:aves/app_flavor.dart'; import 'package:aves/model/actions/entry_actions.dart'; import 'package:aves/model/actions/entry_set_actions.dart'; import 'package:aves/model/filters/filters.dart'; @@ -202,18 +203,20 @@ class Settings extends ChangeNotifier { bool isInternalKey(String key) => _internalKeys.contains(key) || key.startsWith(_widgetKeyPrefix); - Future setContextualDefaults() async { + Future setContextualDefaults(AppFlavor flavor) async { // performance final performanceClass = await deviceService.getPerformanceClass(); enableBlurEffect = performanceClass >= 29; // availability - final defaultMapStyle = mobileServices.defaultMapStyle; - if (mobileServices.mapStyles.contains(defaultMapStyle)) { - mapStyle = defaultMapStyle; - } else { - final styles = EntryMapStyle.values.whereNot((v) => v.needMobileService).toList(); - mapStyle = styles[Random().nextInt(styles.length)]; + if (flavor.hasMapStyleDefault) { + final defaultMapStyle = mobileServices.defaultMapStyle; + if (mobileServices.mapStyles.contains(defaultMapStyle)) { + mapStyle = defaultMapStyle; + } else { + final styles = EntryMapStyle.values.whereNot((v) => v.needMobileService).toList(); + mapStyle = styles[Random().nextInt(styles.length)]; + } } } @@ -598,13 +601,15 @@ class Settings extends ChangeNotifier { // map - EntryMapStyle get mapStyle { - final preferred = getEnumOrDefault(mapStyleKey, SettingsDefaults.infoMapStyle, EntryMapStyle.values); + EntryMapStyle? get mapStyle { + final preferred = getEnumOrDefault(mapStyleKey, null, EntryMapStyle.values); + if (preferred == null) return null; + final available = availability.mapStyles; return available.contains(preferred) ? preferred : available.first; } - set mapStyle(EntryMapStyle newValue) => setAndNotify(mapStyleKey, newValue.toString()); + set mapStyle(EntryMapStyle? newValue) => setAndNotify(mapStyleKey, newValue?.toString()); LatLng? get mapDefaultCenter { final json = getString(mapDefaultCenterKey); diff --git a/lib/widgets/common/map/attribution.dart b/lib/widgets/common/map/attribution.dart index 2b50b7199..1c6694d2f 100644 --- a/lib/widgets/common/map/attribution.dart +++ b/lib/widgets/common/map/attribution.dart @@ -6,7 +6,7 @@ import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:url_launcher/url_launcher.dart'; class Attribution extends StatelessWidget { - final EntryMapStyle style; + final EntryMapStyle? style; const Attribution({ super.key, diff --git a/lib/widgets/common/map/buttons/panel.dart b/lib/widgets/common/map/buttons/panel.dart index 63c974513..faf9f8418 100644 --- a/lib/widgets/common/map/buttons/panel.dart +++ b/lib/widgets/common/map/buttons/panel.dart @@ -128,7 +128,7 @@ class MapButtonPanel extends StatelessWidget { icon: const Icon(AIcons.layers), onPressed: () => showSelectionDialog( context: context, - builder: (context) => AvesSelectionDialog( + builder: (context) => AvesSelectionDialog( initialValue: settings.mapStyle, options: Map.fromEntries(availability.mapStyles.map((v) => MapEntry(v, v.getName(context)))), title: context.l10n.mapStyleDialogTitle, diff --git a/lib/widgets/common/map/geo_map.dart b/lib/widgets/common/map/geo_map.dart index 3518e7095..68c70c0d3 100644 --- a/lib/widgets/common/map/geo_map.dart +++ b/lib/widgets/common/map/geo_map.dart @@ -7,14 +7,18 @@ 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/theme/icons.dart'; import 'package:aves/utils/change_notifier.dart'; import 'package:aves/utils/constants.dart'; import 'package:aves/utils/math_utils.dart'; +import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/map/attribution.dart'; import 'package:aves/widgets/common/map/buttons/panel.dart'; 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/widgets/dialogs/aves_selection_dialog.dart'; +import 'package:aves/widgets/viewer/overlay/common.dart'; import 'package:aves_map/aves_map.dart'; import 'package:collection/collection.dart'; import 'package:fluster/fluster.dart'; @@ -148,10 +152,10 @@ class _GeoMapState extends State { onTap(clusterAverageLocation, markerEntry, getClusterEntries); } - return Selector( + return Selector( selector: (context, s) => s.mapStyle, builder: (context, mapStyle, child) { - final isHeavy = mapStyle.isHeavy; + final isHeavy = ExtraEntryMapStyle.isHeavy(mapStyle); Widget _buildMarkerWidget(MarkerKey key) => ImageMarker( key: key, count: key.count, @@ -164,60 +168,85 @@ class _GeoMapState extends State { bool _isMarkerImageReady(MarkerKey key) => key.entry.isThumbnailReady(extent: MapThemeData.markerImageExtent); Widget child = const SizedBox(); - switch (mapStyle) { - case EntryMapStyle.googleNormal: - case EntryMapStyle.googleHybrid: - case EntryMapStyle.googleTerrain: - case EntryMapStyle.hmsNormal: - case EntryMapStyle.hmsTerrain: - child = mobileServices.buildMap( - controller: widget.controller, - clusterListenable: _clusterChangeNotifier, - boundsNotifier: _boundsNotifier, - style: mapStyle, - decoratorBuilder: _decorateMap, - buttonPanelBuilder: _buildButtonPanel, - markerClusterBuilder: _buildMarkerClusters, - markerWidgetBuilder: _buildMarkerWidget, - markerImageReadyChecker: _isMarkerImageReady, - dotLocationNotifier: widget.dotLocationNotifier, - overlayOpacityNotifier: widget.overlayOpacityNotifier, - overlayEntry: widget.overlayEntry, - onUserZoomChange: widget.onUserZoomChange, - onMapTap: widget.onMapTap, - onMarkerTap: _onMarkerTap, - ); - break; - case EntryMapStyle.osmHot: - case EntryMapStyle.stamenToner: - case EntryMapStyle.stamenWatercolor: - child = EntryLeafletMap( - controller: widget.controller, - clusterListenable: _clusterChangeNotifier, - boundsNotifier: _boundsNotifier, - minZoom: 2, - maxZoom: 16, - style: mapStyle, - decoratorBuilder: _decorateMap, - buttonPanelBuilder: _buildButtonPanel, - markerClusterBuilder: _buildMarkerClusters, - markerWidgetBuilder: _buildMarkerWidget, - dotLocationNotifier: widget.dotLocationNotifier, - markerSize: Size( - MapThemeData.markerImageExtent + MapThemeData.markerOuterBorderWidth * 2, - MapThemeData.markerImageExtent + MapThemeData.markerOuterBorderWidth * 2 + MapThemeData.markerArrowSize.height, + if (mapStyle != null) { + switch (mapStyle) { + case EntryMapStyle.googleNormal: + case EntryMapStyle.googleHybrid: + case EntryMapStyle.googleTerrain: + case EntryMapStyle.hmsNormal: + case EntryMapStyle.hmsTerrain: + child = mobileServices.buildMap( + controller: widget.controller, + clusterListenable: _clusterChangeNotifier, + boundsNotifier: _boundsNotifier, + style: mapStyle, + decoratorBuilder: _decorateMap, + buttonPanelBuilder: _buildButtonPanel, + markerClusterBuilder: _buildMarkerClusters, + markerWidgetBuilder: _buildMarkerWidget, + markerImageReadyChecker: _isMarkerImageReady, + dotLocationNotifier: widget.dotLocationNotifier, + overlayOpacityNotifier: widget.overlayOpacityNotifier, + overlayEntry: widget.overlayEntry, + onUserZoomChange: widget.onUserZoomChange, + onMapTap: widget.onMapTap, + onMarkerTap: _onMarkerTap, + ); + break; + case EntryMapStyle.osmHot: + case EntryMapStyle.stamenToner: + case EntryMapStyle.stamenWatercolor: + child = EntryLeafletMap( + controller: widget.controller, + clusterListenable: _clusterChangeNotifier, + boundsNotifier: _boundsNotifier, + minZoom: 2, + maxZoom: 16, + style: mapStyle, + decoratorBuilder: _decorateMap, + buttonPanelBuilder: _buildButtonPanel, + markerClusterBuilder: _buildMarkerClusters, + markerWidgetBuilder: _buildMarkerWidget, + dotLocationNotifier: widget.dotLocationNotifier, + markerSize: Size( + MapThemeData.markerImageExtent + MapThemeData.markerOuterBorderWidth * 2, + MapThemeData.markerImageExtent + MapThemeData.markerOuterBorderWidth * 2 + MapThemeData.markerArrowSize.height, + ), + dotMarkerSize: const Size( + DotMarker.diameter + MapThemeData.markerOuterBorderWidth * 2, + DotMarker.diameter + MapThemeData.markerOuterBorderWidth * 2, + ), + overlayOpacityNotifier: widget.overlayOpacityNotifier, + overlayEntry: widget.overlayEntry, + onUserZoomChange: widget.onUserZoomChange, + onMapTap: widget.onMapTap, + onMarkerTap: _onMarkerTap, + ); + break; + } + } else { + final overlay = Center( + child: OverlayTextButton( + onPressed: () => showSelectionDialog( + context: context, + builder: (context) => AvesSelectionDialog( + initialValue: settings.mapStyle, + options: Map.fromEntries(availability.mapStyles.map((v) => MapEntry(v, v.getName(context)))), + title: context.l10n.mapStyleDialogTitle, + ), + onSelection: (v) => settings.mapStyle = v, ), - dotMarkerSize: const Size( - DotMarker.diameter + MapThemeData.markerOuterBorderWidth * 2, - DotMarker.diameter + MapThemeData.markerOuterBorderWidth * 2, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(AIcons.layers), + const SizedBox(width: 8), + Text(context.l10n.mapStyleTooltip), + ], ), - overlayOpacityNotifier: widget.overlayOpacityNotifier, - overlayEntry: widget.overlayEntry, - onUserZoomChange: widget.onUserZoomChange, - onMapTap: widget.onMapTap, - onMarkerTap: _onMarkerTap, - ); - break; + ), + ); + child = _decorateMap(context, overlay); } final mapHeight = context.select((v) => v.mapHeight); @@ -308,7 +337,11 @@ class _GeoMapState extends State { } if (bounds == null) { // fallback to default center - final center = settings.mapDefaultCenter ??= Constants.wonders[Random().nextInt(Constants.wonders.length)]; + var center = settings.mapDefaultCenter; + if (center == null) { + center = Constants.wonders[Random().nextInt(Constants.wonders.length)]; + WidgetsBinding.instance.addPostFrameCallback((_) => settings.mapDefaultCenter = center); + } bounds = ZoomedBounds.fromPoints( points: {center}, collocationZoom: settings.infoMapZoom, diff --git a/lib/widgets/dialogs/location_pick_dialog.dart b/lib/widgets/dialogs/location_pick_dialog.dart index 480f3d060..f81e38528 100644 --- a/lib/widgets/dialogs/location_pick_dialog.dart +++ b/lib/widgets/dialogs/location_pick_dialog.dart @@ -78,7 +78,7 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin void initState() { super.initState(); - if (settings.mapStyle.isHeavy) { + if (ExtraEntryMapStyle.isHeavy(settings.mapStyle)) { _isPageAnimatingNotifier = ValueNotifier(true); Future.delayed(Durations.pageTransitionAnimation * timeDilation).then((_) { if (!mounted) return; diff --git a/lib/widgets/map/map_page.dart b/lib/widgets/map/map_page.dart index c59574814..ea83119db 100644 --- a/lib/widgets/map/map_page.dart +++ b/lib/widgets/map/map_page.dart @@ -108,7 +108,7 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin void initState() { super.initState(); - if (settings.mapStyle.isHeavy) { + if (ExtraEntryMapStyle.isHeavy(settings.mapStyle)) { _isPageAnimatingNotifier.value = true; Future.delayed(Durations.pageTransitionAnimation * timeDilation).then((_) { if (!mounted) return; @@ -176,11 +176,11 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin } return true; }, - child: Selector( + child: Selector( selector: (context, s) => s.mapStyle, builder: (context, mapStyle, child) { late Widget scroller; - if (mapStyle.isHeavy) { + if (ExtraEntryMapStyle.isHeavy(mapStyle)) { // the map widget is too heavy for a smooth resizing animation // so we just toggle visibility when overlay animation is done scroller = ValueListenableBuilder( diff --git a/lib/widgets/viewer/overlay/common.dart b/lib/widgets/viewer/overlay/common.dart index 523cd5fb9..1feee178a 100644 --- a/lib/widgets/viewer/overlay/common.dart +++ b/lib/widgets/viewer/overlay/common.dart @@ -60,16 +60,38 @@ class OverlayButton extends StatelessWidget { static double getSize(BuildContext context) => 48.0 + AvesBorder.curvedBorderWidth * 2; } -class OverlayTextButton extends StatelessWidget { +class ScalingOverlayTextButton extends StatelessWidget { final Animation scale; - final String buttonLabel; final VoidCallback? onPressed; + final Widget child; + + const ScalingOverlayTextButton({ + super.key, + required this.scale, + this.onPressed, + required this.child, + }); + + @override + Widget build(BuildContext context) { + return SizeTransition( + sizeFactor: scale, + child: OverlayTextButton( + onPressed: onPressed, + child: child, + ), + ); + } +} + +class OverlayTextButton extends StatelessWidget { + final VoidCallback? onPressed; + final Widget child; const OverlayTextButton({ super.key, - required this.scale, - required this.buttonLabel, this.onPressed, + required this.child, }); static const _borderRadius = 123.0; @@ -79,25 +101,22 @@ class OverlayTextButton extends StatelessWidget { Widget build(BuildContext context) { final blurred = settings.enableBlurEffect; final theme = Theme.of(context); - return SizeTransition( - sizeFactor: scale, - child: BlurredRRect.all( - enabled: blurred, - borderRadius: _borderRadius, - child: OutlinedButton( - onPressed: onPressed, - style: ButtonStyle( - backgroundColor: MaterialStateProperty.all(Themes.overlayBackgroundColor(brightness: theme.brightness, blurred: blurred)), - foregroundColor: MaterialStateProperty.all(theme.colorScheme.onSurface), - overlayColor: theme.brightness == Brightness.dark ? MaterialStateProperty.all(Colors.white.withOpacity(0.12)) : null, - minimumSize: _minSize, - side: MaterialStateProperty.all(AvesBorder.curvedSide(context)), - shape: MaterialStateProperty.all(const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(_borderRadius)), - )), - ), - child: Text(buttonLabel), + return BlurredRRect.all( + enabled: blurred, + borderRadius: _borderRadius, + child: OutlinedButton( + onPressed: onPressed, + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all(Themes.overlayBackgroundColor(brightness: theme.brightness, blurred: blurred)), + foregroundColor: MaterialStateProperty.all(theme.colorScheme.onSurface), + overlayColor: theme.brightness == Brightness.dark ? MaterialStateProperty.all(Colors.white.withOpacity(0.12)) : null, + minimumSize: _minSize, + side: MaterialStateProperty.all(AvesBorder.curvedSide(context)), + shape: MaterialStateProperty.all(const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(_borderRadius)), + )), ), + child: child, ), ); } diff --git a/lib/widgets/viewer/overlay/panorama.dart b/lib/widgets/viewer/overlay/panorama.dart index 4facceecc..5f32c8315 100644 --- a/lib/widgets/viewer/overlay/panorama.dart +++ b/lib/widgets/viewer/overlay/panorama.dart @@ -22,9 +22,8 @@ class PanoramaOverlay extends StatelessWidget { return Row( children: [ const Spacer(), - OverlayTextButton( + ScalingOverlayTextButton( scale: scale, - buttonLabel: context.l10n.viewerOpenPanoramaButtonLabel, onPressed: () async { final info = await metadataFetchService.getPanoramaInfo(entry); if (info != null) { @@ -40,7 +39,8 @@ class PanoramaOverlay extends StatelessWidget { )); } }, - ) + child: Text(context.l10n.viewerOpenPanoramaButtonLabel), + ), ], ); } diff --git a/lib/widgets/viewer/overlay/wallpaper_buttons.dart b/lib/widgets/viewer/overlay/wallpaper_buttons.dart index 39dd5b9eb..92a59d55f 100644 --- a/lib/widgets/viewer/overlay/wallpaper_buttons.dart +++ b/lib/widgets/viewer/overlay/wallpaper_buttons.dart @@ -42,10 +42,10 @@ class WallpaperButtons extends StatelessWidget with FeedbackMixin { const Spacer(), Padding( padding: const EdgeInsets.symmetric(horizontal: padding / 2), - child: OverlayTextButton( + child: ScalingOverlayTextButton( scale: scale, - buttonLabel: context.l10n.viewerSetWallpaperButtonLabel, onPressed: () => _setWallpaper(context), + child: Text(context.l10n.viewerSetWallpaperButtonLabel), ), ), ], diff --git a/lib/widgets/welcome_page.dart b/lib/widgets/welcome_page.dart index 7286676d9..91712fbbd 100644 --- a/lib/widgets/welcome_page.dart +++ b/lib/widgets/welcome_page.dart @@ -31,7 +31,6 @@ class _WelcomePageState extends State { @override void initState() { super.initState(); - settings.setContextualDefaults(); _termsLoader = rootBundle.loadString(termsPath); WidgetsBinding.instance.addPostFrameCallback((_) => _initWelcomeSettings()); } @@ -40,6 +39,7 @@ class _WelcomePageState extends State { // so they are not subject to future default changes void _initWelcomeSettings() { // this should be done outside of `initState`/`build` + settings.setContextualDefaults(context.read()); settings.isInstalledAppAccessAllowed = SettingsDefaults.isInstalledAppAccessAllowed; settings.isErrorReportingAllowed = SettingsDefaults.isErrorReportingAllowed; }