diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MediaSessionHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MediaSessionHandler.kt index 090422f3c..e3373bcce 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MediaSessionHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MediaSessionHandler.kt @@ -25,6 +25,7 @@ class MediaSessionHandler(private val context: Context, private val mediaCommand private var session: MediaSessionCompat? = null private var wasPlaying = false + private var isNoisyAudioReceiverRegistered = false private val noisyAudioReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { if (intent.action == AudioManager.ACTION_AUDIO_BECOMING_NOISY) { @@ -34,7 +35,10 @@ class MediaSessionHandler(private val context: Context, private val mediaCommand } fun dispose() { - context.unregisterReceiver(noisyAudioReceiver) + if (isNoisyAudioReceiverRegistered) { + context.unregisterReceiver(noisyAudioReceiver) + isNoisyAudioReceiverRegistered = false + } } override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { @@ -110,8 +114,10 @@ class MediaSessionHandler(private val context: Context, private val mediaCommand val isPlaying = state == PlaybackStateCompat.STATE_PLAYING if (!wasPlaying && isPlaying) { context.registerReceiver(noisyAudioReceiver, IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY)) + isNoisyAudioReceiverRegistered = true } else if (wasPlaying && !isPlaying) { context.unregisterReceiver(noisyAudioReceiver) + isNoisyAudioReceiverRegistered = false } wasPlaying = isPlaying } diff --git a/lib/model/actions/map_actions.dart b/lib/model/actions/map_actions.dart new file mode 100644 index 000000000..07cc9189a --- /dev/null +++ b/lib/model/actions/map_actions.dart @@ -0,0 +1,35 @@ +import 'package:aves/theme/icons.dart'; +import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:flutter/widgets.dart'; + +enum MapAction { + selectStyle, + zoomIn, + zoomOut, +} + +extension ExtraMapAction on MapAction { + String getText(BuildContext context) { + switch (this) { + case MapAction.selectStyle: + return context.l10n.mapStyleTooltip; + case MapAction.zoomIn: + return context.l10n.mapZoomInTooltip; + case MapAction.zoomOut: + return context.l10n.mapZoomOutTooltip; + } + } + + Widget getIcon() => Icon(_getIconData()); + + IconData _getIconData() { + switch (this) { + case MapAction.selectStyle: + return AIcons.layers; + case MapAction.zoomIn: + return AIcons.zoomIn; + case MapAction.zoomOut: + return AIcons.zoomOut; + } + } +} diff --git a/lib/model/device.dart b/lib/model/device.dart index deefa5e6d..b69195128 100644 --- a/lib/model/device.dart +++ b/lib/model/device.dart @@ -27,8 +27,6 @@ class Device { bool get isDynamicColorAvailable => _isDynamicColorAvailable; - bool get isReadOnly => _isTelevision; - bool get isTelevision => _isTelevision; bool get showPinShortcutFeedback => _showPinShortcutFeedback; diff --git a/lib/model/entry.dart b/lib/model/entry.dart index 86e1fcdce..6a9dc3625 100644 --- a/lib/model/entry.dart +++ b/lib/model/entry.dart @@ -3,7 +3,6 @@ import 'dart:io'; import 'dart:ui'; import 'package:aves/geo/countries.dart'; -import 'package:aves/model/device.dart'; import 'package:aves/model/entry_cache.dart'; import 'package:aves/model/entry_dirs.dart'; import 'package:aves/model/favourites.dart'; @@ -12,6 +11,7 @@ import 'package:aves/model/metadata/address.dart'; import 'package:aves/model/metadata/catalog.dart'; import 'package:aves/model/metadata/trash.dart'; import 'package:aves/model/multipage.dart'; +import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/trash.dart'; import 'package:aves/model/video/metadata.dart'; import 'package:aves/ref/mime_types.dart'; @@ -281,7 +281,7 @@ class AvesEntry { bool get isMediaStoreMediaContent => isMediaStoreContent && {'/external/images/', '/external/video/'}.any(uri.contains); - bool get canEdit => !device.isReadOnly && path != null && !trashed && isMediaStoreContent; + bool get canEdit => !settings.isReadOnly && path != null && !trashed && isMediaStoreContent; bool get canEditDate => canEdit && (canEditExif || canEditXmp); diff --git a/lib/model/settings/settings.dart b/lib/model/settings/settings.dart index bb28aeaf6..cd3fcd57c 100644 --- a/lib/model/settings/settings.dart +++ b/lib/model/settings/settings.dart @@ -403,6 +403,8 @@ class Settings extends ChangeNotifier { bool get useTvLayout => device.isTelevision || forceTvLayout; + bool get isReadOnly => useTvLayout; + // navigation bool get mustBackTwiceToExit => getBool(mustBackTwiceToExitKey) ?? SettingsDefaults.mustBackTwiceToExit; @@ -491,7 +493,7 @@ class Settings extends ChangeNotifier { ThumbnailOverlayTagIcon get thumbnailTagIcon => getEnumOrDefault(thumbnailTagIconKey, SettingsDefaults.thumbnailTagIcon, ThumbnailOverlayTagIcon.values); set thumbnailTagIcon(ThumbnailOverlayTagIcon newValue) => setAndNotify(thumbnailTagIconKey, newValue.toString()); - + bool get showThumbnailMotionPhoto => getBool(showThumbnailMotionPhotoKey) ?? SettingsDefaults.showThumbnailMotionPhoto; set showThumbnailMotionPhoto(bool newValue) => setAndNotify(showThumbnailMotionPhotoKey, newValue); diff --git a/lib/widgets/collection/app_bar.dart b/lib/widgets/collection/app_bar.dart index 63ce8f10c..0c5abac12 100644 --- a/lib/widgets/collection/app_bar.dart +++ b/lib/widgets/collection/app_bar.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'package:aves/app_mode.dart'; import 'package:aves/model/actions/entry_set_actions.dart'; -import 'package:aves/model/device.dart'; import 'package:aves/model/entry.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/query.dart'; @@ -392,7 +391,7 @@ class _CollectionAppBarState extends State with SingleTickerPr ...(isSelecting ? selectionMenuActions : browsingMenuActions).where(isVisible).map( (action) => _toMenuItem(action, enabled: canApply(action), selection: selection), ), - if (isSelecting && !device.isReadOnly && appMode == AppMode.main && !isTrash) + if (isSelecting && !settings.isReadOnly && appMode == AppMode.main && !isTrash) PopupMenuItem( enabled: hasSelection, padding: EdgeInsets.zero, diff --git a/lib/widgets/collection/entry_set_action_delegate.dart b/lib/widgets/collection/entry_set_action_delegate.dart index 865e87f50..72e8a5d06 100644 --- a/lib/widgets/collection/entry_set_action_delegate.dart +++ b/lib/widgets/collection/entry_set_action_delegate.dart @@ -55,7 +55,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware required int selectedItemCount, required bool isTrash, }) { - final canWrite = !device.isReadOnly; + final canWrite = !settings.isReadOnly; final isMain = appMode == AppMode.main; switch (action) { // general diff --git a/lib/widgets/common/grid/header.dart b/lib/widgets/common/grid/header.dart index 887cdc6e1..bc941d63b 100644 --- a/lib/widgets/common/grid/header.dart +++ b/lib/widgets/common/grid/header.dart @@ -34,17 +34,16 @@ class SectionHeader extends StatelessWidget { Widget build(BuildContext context) { Widget child = _buildContent(context); if (settings.useTvLayout) { - final colors = Theme.of(context).colorScheme; + final primaryColor = Theme.of(context).colorScheme.primary; child = Material( type: MaterialType.transparency, child: InkResponse( onTap: _onTap(context), - onHover: (_) {}, + containedInkWell: true, highlightShape: BoxShape.rectangle, borderRadius: const BorderRadius.all(Radius.circular(123)), - containedInkWell: true, - splashColor: colors.primary.withOpacity(0.12), - hoverColor: colors.primary.withOpacity(0.04), + hoverColor: primaryColor.withOpacity(0.04), + splashColor: primaryColor.withOpacity(0.12), child: child, ), ); diff --git a/lib/widgets/common/identity/buttons/overlay_button.dart b/lib/widgets/common/identity/buttons/overlay_button.dart index 2c894f994..d4b085433 100644 --- a/lib/widgets/common/identity/buttons/overlay_button.dart +++ b/lib/widgets/common/identity/buttons/overlay_button.dart @@ -73,7 +73,7 @@ class _OverlayButtonState extends State { builder: (context, focused, child) { final border = AvesBorder.border( context, - width: AvesBorder.curvedBorderWidth * (focused ? 2 : 1), + width: AvesBorder.curvedBorderWidth * (focused ? 3 : 1), ); return borderRadius != null ? BlurredRRect( diff --git a/lib/widgets/common/map/buttons/panel.dart b/lib/widgets/common/map/buttons/panel.dart index 3ed5f1773..e7205b1b5 100644 --- a/lib/widgets/common/map/buttons/panel.dart +++ b/lib/widgets/common/map/buttons/panel.dart @@ -1,28 +1,27 @@ -import 'package:aves/model/settings/enums/map_style.dart'; +import 'package:aves/model/actions/map_actions.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/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/map/buttons/button.dart'; 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/widgets/common/map/map_action_delegate.dart'; import 'package:aves_map/aves_map.dart'; import 'package:flutter/material.dart'; import 'package:latlong2/latlong.dart'; import 'package:provider/provider.dart'; class MapButtonPanel extends StatelessWidget { + final AvesMapController? controller; final ValueNotifier boundsNotifier; - final Future Function(double amount)? zoomBy; final void Function(BuildContext context)? openMapPage; final VoidCallback? resetRotation; const MapButtonPanel({ super.key, + required this.controller, required this.boundsNotifier, - this.zoomBy, this.openMapPage, this.resetRotation, }); @@ -123,21 +122,8 @@ class MapButtonPanel extends StatelessWidget { : const Spacer(), Padding( padding: EdgeInsets.only(top: padding), - child: MapOverlayButton( - // key is expected by test driver - buttonKey: const Key('map-menu-layers'), - icon: const Icon(AIcons.layers), - 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, - ), - tooltip: context.l10n.mapStyleTooltip, - ), + // key is expected by test driver + child: _buildButton(context, MapAction.selectStyle, buttonKey: const Key('map-menu-layers')), ), ], ), @@ -148,17 +134,9 @@ class MapButtonPanel extends StatelessWidget { child: Column( mainAxisSize: MainAxisSize.min, children: [ - MapOverlayButton( - icon: const Icon(AIcons.zoomIn), - onPressed: zoomBy != null ? () => zoomBy?.call(1) : null, - tooltip: context.l10n.mapZoomInTooltip, - ), + _buildButton(context, MapAction.zoomIn), SizedBox(height: padding), - MapOverlayButton( - icon: const Icon(AIcons.zoomOut), - onPressed: zoomBy != null ? () => zoomBy?.call(-1) : null, - tooltip: context.l10n.mapZoomOutTooltip, - ), + _buildButton(context, MapAction.zoomOut), ], ), ), @@ -168,4 +146,11 @@ class MapButtonPanel extends StatelessWidget { ), ); } + + Widget _buildButton(BuildContext context, MapAction action, {Key? buttonKey}) => MapOverlayButton( + buttonKey: buttonKey, + icon: action.getIcon(), + onPressed: () => MapActionDelegate(controller).onActionSelected(context, action), + tooltip: action.getText(context), + ); } diff --git a/lib/widgets/common/map/geo_map.dart b/lib/widgets/common/map/geo_map.dart index 796b30179..0643b2ce4 100644 --- a/lib/widgets/common/map/geo_map.dart +++ b/lib/widgets/common/map/geo_map.dart @@ -144,6 +144,7 @@ class _GeoMapState extends State { ); bool _isMarkerImageReady(MarkerKey key) => key.entry.isThumbnailReady(extent: MapThemeData.markerImageExtent); + final controller = widget.controller; Widget child = const SizedBox(); if (mapStyle != null) { switch (mapStyle) { @@ -153,7 +154,7 @@ class _GeoMapState extends State { case EntryMapStyle.hmsNormal: case EntryMapStyle.hmsTerrain: child = mobileServices.buildMap( - controller: widget.controller, + controller: controller, clusterListenable: _clusterChangeNotifier, boundsNotifier: _boundsNotifier, style: mapStyle, @@ -175,7 +176,7 @@ class _GeoMapState extends State { case EntryMapStyle.stamenToner: case EntryMapStyle.stamenWatercolor: child = EntryLeafletMap( - controller: widget.controller, + controller: controller, clusterListenable: _clusterChangeNotifier, boundsNotifier: _boundsNotifier, minZoom: 2, @@ -260,6 +261,7 @@ class _GeoMapState extends State { children: [ const MapDecorator(), MapButtonPanel( + controller: controller, boundsNotifier: _boundsNotifier, openMapPage: widget.openMapPage, ), @@ -485,14 +487,13 @@ class _GeoMapState extends State { Widget _decorateMap(BuildContext context, Widget? child) => MapDecorator(child: child); - Widget _buildButtonPanel( - Future Function(double amount) zoomBy, - VoidCallback resetRotation, - ) => - MapButtonPanel( - boundsNotifier: _boundsNotifier, - zoomBy: zoomBy, - openMapPage: widget.openMapPage, - resetRotation: resetRotation, - ); + Widget _buildButtonPanel(VoidCallback resetRotation) { + if (settings.useTvLayout) return const SizedBox(); + return MapButtonPanel( + controller: widget.controller, + boundsNotifier: _boundsNotifier, + openMapPage: widget.openMapPage, + resetRotation: resetRotation, + ); + } } diff --git a/lib/widgets/common/map/leaflet/map.dart b/lib/widgets/common/map/leaflet/map.dart index 358972f3f..d0c60933f 100644 --- a/lib/widgets/common/map/leaflet/map.dart +++ b/lib/widgets/common/map/leaflet/map.dart @@ -95,6 +95,7 @@ class _EntryLeafletMapState extends State> with TickerProv final avesMapController = widget.controller; if (avesMapController != null) { _subscriptions.add(avesMapController.moveCommands.listen((event) => _moveTo(event.latLng))); + _subscriptions.add(avesMapController.zoomCommands.listen((event) => _zoomBy(event.delta))); } _subscriptions.add(_leafletMapController.mapEventStream.listen((event) => _updateVisibleRegion())); widget.clusterListenable.addListener(_updateMarkers); @@ -114,7 +115,7 @@ class _EntryLeafletMapState extends State> with TickerProv return Stack( children: [ widget.decoratorBuilder(context, _buildMap()), - widget.buttonPanelBuilder(_zoomBy, _resetRotation), + widget.buttonPanelBuilder(_resetRotation), ], ); } diff --git a/lib/widgets/common/map/map_action_delegate.dart b/lib/widgets/common/map/map_action_delegate.dart new file mode 100644 index 000000000..06a984005 --- /dev/null +++ b/lib/widgets/common/map/map_action_delegate.dart @@ -0,0 +1,37 @@ +import 'package:aves/model/actions/map_actions.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/widgets/common/extensions/build_context.dart'; +import 'package:aves/widgets/dialogs/aves_selection_dialog.dart'; +import 'package:aves_map/aves_map.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; + +class MapActionDelegate { + final AvesMapController? controller; + + const MapActionDelegate(this.controller); + + void onActionSelected(BuildContext context, MapAction action) { + switch (action) { + case MapAction.selectStyle: + 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, + ); + break; + case MapAction.zoomIn: + controller?.zoomBy(1); + break; + case MapAction.zoomOut: + controller?.zoomBy(-1); + break; + } + } +} diff --git a/lib/widgets/filter_grids/common/action_delegates/album_set.dart b/lib/widgets/filter_grids/common/action_delegates/album_set.dart index 25cc2a85d..a14b93833 100644 --- a/lib/widgets/filter_grids/common/action_delegates/album_set.dart +++ b/lib/widgets/filter_grids/common/action_delegates/album_set.dart @@ -3,7 +3,6 @@ import 'dart:io'; import 'package:aves/app_mode.dart'; import 'package:aves/model/actions/chip_set_actions.dart'; import 'package:aves/model/actions/move_type.dart'; -import 'package:aves/model/device.dart'; import 'package:aves/model/filters/album.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/highlight.dart'; @@ -76,10 +75,10 @@ class AlbumChipSetActionDelegate extends ChipSetActionDelegate with }) { switch (action) { case ChipSetAction.createAlbum: - return !device.isReadOnly && appMode == AppMode.main && !isSelecting; + return !settings.isReadOnly && appMode == AppMode.main && !isSelecting; case ChipSetAction.delete: case ChipSetAction.rename: - return !device.isReadOnly && appMode == AppMode.main && isSelecting; + return !settings.isReadOnly && appMode == AppMode.main && isSelecting; default: return super.isVisible( action, diff --git a/lib/widgets/map/map_page.dart b/lib/widgets/map/map_page.dart index 5d59205a6..25a94f89e 100644 --- a/lib/widgets/map/map_page.dart +++ b/lib/widgets/map/map_page.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:aves/app_mode.dart'; +import 'package:aves/model/actions/map_actions.dart'; import 'package:aves/model/actions/map_cluster_actions.dart'; import 'package:aves/model/entry.dart'; import 'package:aves/model/filters/coordinate.dart'; @@ -16,11 +17,14 @@ import 'package:aves/theme/icons.dart'; import 'package:aves/utils/debouncer.dart'; import 'package:aves/widgets/collection/collection_page.dart'; import 'package:aves/widgets/collection/entry_set_action_delegate.dart'; +import 'package:aves/widgets/common/basic/insets.dart'; import 'package:aves/widgets/common/basic/menu.dart'; import 'package:aves/widgets/common/behaviour/routes.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves/widgets/common/identity/buttons/captioned_button.dart'; import 'package:aves/widgets/common/identity/empty.dart'; import 'package:aves/widgets/common/map/geo_map.dart'; +import 'package:aves/widgets/common/map/map_action_delegate.dart'; import 'package:aves/widgets/common/providers/highlight_info_provider.dart'; import 'package:aves/widgets/common/providers/map_theme_provider.dart'; import 'package:aves/widgets/common/thumbnail/scroller.dart'; @@ -231,7 +235,7 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin } Widget _buildMap() { - return MapTheme( + Widget child = MapTheme( interactive: true, showCoordinateFilter: true, navigationButton: MapNavigationButton.back, @@ -259,6 +263,37 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin onMarkerLongPress: _onMarkerLongPress, ), ); + if (settings.useTvLayout) { + child = DirectionalSafeArea( + top: false, + end: false, + bottom: false, + child: Row( + children: [ + Column( + mainAxisSize: MainAxisSize.min, + children: [ + MapAction.selectStyle, + MapAction.zoomIn, + MapAction.zoomOut, + ] + .map((action) => Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: CaptionedButton( + icon: action.getIcon(), + caption: action.getText(context), + onPressed: () => MapActionDelegate(_mapController).onActionSelected(context, action), + ), + )) + .toList(), + ), + const SizedBox(width: 16), + Expanded(child: child), + ], + ), + ); + } + return child; } Widget _buildOverlayController() { diff --git a/lib/widgets/stats/mime_donut.dart b/lib/widgets/stats/mime_donut.dart index 76c633256..f7739612e 100644 --- a/lib/widgets/stats/mime_donut.dart +++ b/lib/widgets/stats/mime_donut.dart @@ -111,35 +111,45 @@ class _MimeDonutState extends State with AutomaticKeepAliveClientMixi ], ), ); + final primaryColor = Theme.of(context).colorScheme.primary; final legend = SizedBox( width: dim, child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: seriesData - .map((d) => GestureDetector( - onTap: () => widget.onFilterSelection(MimeFilter(d.mimeType)), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(AIcons.disc, color: d.color), - const SizedBox(width: 8), - Flexible( - child: Text( - d.displayText, - overflow: TextOverflow.fade, - softWrap: false, - maxLines: 1, + .map((d) => Material( + type: MaterialType.transparency, + child: InkResponse( + onTap: () => widget.onFilterSelection(MimeFilter(d.mimeType)), + containedInkWell: true, + highlightShape: BoxShape.rectangle, + borderRadius: const BorderRadius.all(Radius.circular(123)), + hoverColor: primaryColor.withOpacity(0.04), + splashColor: primaryColor.withOpacity(0.12), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(AIcons.disc, color: d.color), + const SizedBox(width: 8), + Flexible( + child: Text( + d.displayText, + overflow: TextOverflow.fade, + softWrap: false, + maxLines: 1, + ), ), - ), - const SizedBox(width: 8), - Text( - numberFormat.format(d.entryCount), - style: TextStyle( - color: Theme.of(context).textTheme.bodySmall!.color, + const SizedBox(width: 8), + Text( + numberFormat.format(d.entryCount), + style: TextStyle( + color: Theme.of(context).textTheme.bodySmall!.color, + ), ), - ), - ], + const SizedBox(width: 4), + ], + ), ), )) .toList(), diff --git a/lib/widgets/stats/stats_page.dart b/lib/widgets/stats/stats_page.dart index c2b27db4a..23a543e3e 100644 --- a/lib/widgets/stats/stats_page.dart +++ b/lib/widgets/stats/stats_page.dart @@ -281,7 +281,7 @@ class _StatsPageState extends State { style: Constants.knownTitleTextStyle, ); if (settings.useTvLayout) { - final colors = Theme.of(context).colorScheme; + final primaryColor = Theme.of(context).colorScheme.primary; header = Container( padding: const EdgeInsets.symmetric(vertical: 12), alignment: AlignmentDirectional.centerStart, @@ -289,12 +289,11 @@ class _StatsPageState extends State { type: MaterialType.transparency, child: InkResponse( onTap: onHeaderPressed, - onHover: (_) {}, + containedInkWell: true, highlightShape: BoxShape.rectangle, borderRadius: const BorderRadius.all(Radius.circular(123)), - containedInkWell: true, - splashColor: colors.primary.withOpacity(0.12), - hoverColor: colors.primary.withOpacity(0.04), + hoverColor: primaryColor.withOpacity(0.04), + splashColor: primaryColor.withOpacity(0.12), child: Padding( padding: const EdgeInsets.all(16), child: header, diff --git a/lib/widgets/viewer/action/entry_action_delegate.dart b/lib/widgets/viewer/action/entry_action_delegate.dart index 36b0328a5..61577aa3b 100644 --- a/lib/widgets/viewer/action/entry_action_delegate.dart +++ b/lib/widgets/viewer/action/entry_action_delegate.dart @@ -67,7 +67,7 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix } } else { final targetEntry = EntryActions.pageActions.contains(action) ? pageEntry : mainEntry; - final canWrite = !device.isReadOnly; + final canWrite = !settings.isReadOnly; switch (action) { case EntryAction.toggleFavourite: return collection != null; diff --git a/lib/widgets/viewer/action/entry_info_action_delegate.dart b/lib/widgets/viewer/action/entry_info_action_delegate.dart index 7abc986a9..42d865f9f 100644 --- a/lib/widgets/viewer/action/entry_info_action_delegate.dart +++ b/lib/widgets/viewer/action/entry_info_action_delegate.dart @@ -3,12 +3,12 @@ import 'dart:convert'; import 'package:aves/model/actions/entry_actions.dart'; import 'package:aves/model/actions/events.dart'; -import 'package:aves/model/device.dart'; import 'package:aves/model/entry.dart'; import 'package:aves/model/entry_info.dart'; import 'package:aves/model/entry_metadata_edition.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/geotiff.dart'; +import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/ref/mime_types.dart'; import 'package:aves/services/common/services.dart'; @@ -30,7 +30,7 @@ class EntryInfoActionDelegate with FeedbackMixin, PermissionAwareMixin, EntryEdi Stream> get eventStream => _eventStreamController.stream; bool isVisible(AvesEntry targetEntry, EntryAction action) { - final canWrite = !device.isReadOnly; + final canWrite = !settings.isReadOnly; switch (action) { // general case EntryAction.editDate: diff --git a/lib/widgets/viewer/overlay/bottom.dart b/lib/widgets/viewer/overlay/bottom.dart index 94460ccca..a48dbb36e 100644 --- a/lib/widgets/viewer/overlay/bottom.dart +++ b/lib/widgets/viewer/overlay/bottom.dart @@ -135,6 +135,7 @@ class _BottomOverlayContentState extends State<_BottomOverlayContent> { void initState() { super.initState(); _registerWidget(widget); + WidgetsBinding.instance.addPostFrameCallback((_) => _requestFocus()); } @override @@ -162,11 +163,10 @@ class _BottomOverlayContentState extends State<_BottomOverlayContent> { parent: animationController, curve: Curves.easeOutQuad, ); - animationController.addStatusListener(_onAnimationStatusChanged); } void _unregisterWidget(_BottomOverlayContent widget) { - widget.animationController.removeStatusListener(_onAnimationStatusChanged); + // nothing } @override @@ -266,11 +266,7 @@ class _BottomOverlayContentState extends State<_BottomOverlayContent> { ); } - void _onAnimationStatusChanged(AnimationStatus status) { - if (status == AnimationStatus.completed) { - _buttonRowFocusScopeNode.children.firstOrNull?.requestFocus(); - } - } + void _requestFocus() => _buttonRowFocusScopeNode.children.firstOrNull?.requestFocus(); } class ExtraBottomOverlay extends StatelessWidget { diff --git a/lib/widgets/viewer/overlay/slideshow_buttons.dart b/lib/widgets/viewer/overlay/slideshow_buttons.dart index 1266594e8..d7d282d4a 100644 --- a/lib/widgets/viewer/overlay/slideshow_buttons.dart +++ b/lib/widgets/viewer/overlay/slideshow_buttons.dart @@ -36,6 +36,7 @@ class _SlideshowButtonsState extends State { void initState() { super.initState(); _registerWidget(widget); + WidgetsBinding.instance.addPostFrameCallback((_) => _requestFocus()); } @override @@ -53,17 +54,15 @@ class _SlideshowButtonsState extends State { } void _registerWidget(SlideshowButtons widget) { - final animationController = widget.animationController; _buttonScale = CurvedAnimation( - parent: animationController, + parent: widget.animationController, // a little bounce at the top curve: Curves.easeOutBack, ); - animationController.addStatusListener(_onAnimationStatusChanged); } void _unregisterWidget(SlideshowButtons widget) { - widget.animationController.removeStatusListener(_onAnimationStatusChanged); + // nothing } @override @@ -111,9 +110,5 @@ class _SlideshowButtonsState extends State { void _onAction(BuildContext context, SlideshowAction action) => SlideshowActionNotification(action).dispatch(context); - void _onAnimationStatusChanged(AnimationStatus status) { - if (status == AnimationStatus.completed) { - _buttonRowFocusScopeNode.children.firstOrNull?.requestFocus(); - } - } + void _requestFocus() => _buttonRowFocusScopeNode.children.firstOrNull?.requestFocus(); } diff --git a/plugins/aves_map/lib/src/controller.dart b/plugins/aves_map/lib/src/controller.dart index e196bcf2e..7f853c2a8 100644 --- a/plugins/aves_map/lib/src/controller.dart +++ b/plugins/aves_map/lib/src/controller.dart @@ -10,6 +10,8 @@ class AvesMapController { Stream get moveCommands => _events.where((event) => event is MapControllerMoveEvent).cast(); + Stream get zoomCommands => _events.where((event) => event is MapControllerZoomEvent).cast(); + Stream get idleUpdates => _events.where((event) => event is MapIdleUpdate).cast(); Stream get markerLocationChanges => _events.where((event) => event is MapMarkerLocationChangeEvent).cast(); @@ -20,6 +22,8 @@ class AvesMapController { void moveTo(LatLng latLng) => _streamController.add(MapControllerMoveEvent(latLng)); + void zoomBy(double delta) => _streamController.add(MapControllerZoomEvent(delta)); + void notifyIdle(ZoomedBounds bounds) => _streamController.add(MapIdleUpdate(bounds)); void notifyMarkerLocationChange() => _streamController.add(MapMarkerLocationChangeEvent()); @@ -31,6 +35,12 @@ class MapControllerMoveEvent { MapControllerMoveEvent(this.latLng); } +class MapControllerZoomEvent { + final double delta; + + MapControllerZoomEvent(this.delta); +} + class MapIdleUpdate { final ZoomedBounds bounds; diff --git a/plugins/aves_map/lib/src/interface.dart b/plugins/aves_map/lib/src/interface.dart index 6bc04ef61..6aedd5397 100644 --- a/plugins/aves_map/lib/src/interface.dart +++ b/plugins/aves_map/lib/src/interface.dart @@ -3,7 +3,7 @@ import 'package:aves_map/src/marker/key.dart'; import 'package:flutter/material.dart'; import 'package:latlong2/latlong.dart'; -typedef ButtonPanelBuilder = Widget Function(Future Function(double amount) zoomBy, VoidCallback resetRotation); +typedef ButtonPanelBuilder = Widget Function(VoidCallback resetRotation); typedef MarkerClusterBuilder = Map, GeoEntry> Function(); typedef MarkerWidgetBuilder = Widget Function(MarkerKey key); typedef MarkerImageReadyChecker = bool Function(MarkerKey key); diff --git a/plugins/aves_services_google/lib/src/map.dart b/plugins/aves_services_google/lib/src/map.dart index 4007294be..b3f50548d 100644 --- a/plugins/aves_services_google/lib/src/map.dart +++ b/plugins/aves_services_google/lib/src/map.dart @@ -95,6 +95,7 @@ class _EntryGoogleMapState extends State> with WidgetsBindi final avesMapController = widget.controller; if (avesMapController != null) { _subscriptions.add(avesMapController.moveCommands.listen((event) => _moveTo(_toServiceLatLng(event.latLng)))); + _subscriptions.add(avesMapController.zoomCommands.listen((event) => _zoomBy(event.delta))); } widget.clusterListenable.addListener(_updateMarkers); } @@ -139,7 +140,7 @@ class _EntryGoogleMapState extends State> with WidgetsBindi }, ), widget.decoratorBuilder(context, _buildMap()), - widget.buttonPanelBuilder(_zoomBy, _resetRotation), + widget.buttonPanelBuilder(_resetRotation), ], ); } diff --git a/plugins/aves_services_huawei/lib/src/map.dart b/plugins/aves_services_huawei/lib/src/map.dart index 1616101fb..82d02d966 100644 --- a/plugins/aves_services_huawei/lib/src/map.dart +++ b/plugins/aves_services_huawei/lib/src/map.dart @@ -89,6 +89,7 @@ class _EntryHmsMapState extends State> { final avesMapController = widget.controller; if (avesMapController != null) { _subscriptions.add(avesMapController.moveCommands.listen((event) => _moveTo(_toServiceLatLng(event.latLng)))); + _subscriptions.add(avesMapController.zoomCommands.listen((event) => _zoomBy(event.delta))); } widget.clusterListenable.addListener(_updateMarkers); } @@ -118,7 +119,7 @@ class _EntryHmsMapState extends State> { }, ), widget.decoratorBuilder(context, _buildMap()), - widget.buttonPanelBuilder(_zoomBy, _resetRotation), + widget.buttonPanelBuilder(_resetRotation), ], ); }