tv: stat donut legend focus, map captioned buttons, viewer button focus

This commit is contained in:
Thibault Deckers 2023-01-11 17:04:45 +01:00
parent 4d226e6e38
commit bcced35e66
25 changed files with 219 additions and 110 deletions

View file

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

View file

@ -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;
}
}
}

View file

@ -27,8 +27,6 @@ class Device {
bool get isDynamicColorAvailable => _isDynamicColorAvailable;
bool get isReadOnly => _isTelevision;
bool get isTelevision => _isTelevision;
bool get showPinShortcutFeedback => _showPinShortcutFeedback;

View file

@ -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);

View file

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

View file

@ -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<CollectionAppBar> 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<EntrySetAction>(
enabled: hasSelection,
padding: EdgeInsets.zero,

View file

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

View file

@ -34,17 +34,16 @@ class SectionHeader<T> 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,
),
);

View file

@ -73,7 +73,7 @@ class _OverlayButtonState extends State<OverlayButton> {
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(

View file

@ -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<ZoomedBounds> boundsNotifier;
final Future<void> 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<EntryMapStyle>(
context: context,
builder: (context) => AvesSelectionDialog<EntryMapStyle?>(
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),
);
}

View file

@ -144,6 +144,7 @@ class _GeoMapState extends State<GeoMap> {
);
bool _isMarkerImageReady(MarkerKey<AvesEntry> 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<GeoMap> {
case EntryMapStyle.hmsNormal:
case EntryMapStyle.hmsTerrain:
child = mobileServices.buildMap<AvesEntry>(
controller: widget.controller,
controller: controller,
clusterListenable: _clusterChangeNotifier,
boundsNotifier: _boundsNotifier,
style: mapStyle,
@ -175,7 +176,7 @@ class _GeoMapState extends State<GeoMap> {
case EntryMapStyle.stamenToner:
case EntryMapStyle.stamenWatercolor:
child = EntryLeafletMap<AvesEntry>(
controller: widget.controller,
controller: controller,
clusterListenable: _clusterChangeNotifier,
boundsNotifier: _boundsNotifier,
minZoom: 2,
@ -260,6 +261,7 @@ class _GeoMapState extends State<GeoMap> {
children: [
const MapDecorator(),
MapButtonPanel(
controller: controller,
boundsNotifier: _boundsNotifier,
openMapPage: widget.openMapPage,
),
@ -485,14 +487,13 @@ class _GeoMapState extends State<GeoMap> {
Widget _decorateMap(BuildContext context, Widget? child) => MapDecorator(child: child);
Widget _buildButtonPanel(
Future<void> 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,
);
}
}

View file

@ -95,6 +95,7 @@ class _EntryLeafletMapState<T> extends State<EntryLeafletMap<T>> 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<T> extends State<EntryLeafletMap<T>> with TickerProv
return Stack(
children: [
widget.decoratorBuilder(context, _buildMap()),
widget.buttonPanelBuilder(_zoomBy, _resetRotation),
widget.buttonPanelBuilder(_resetRotation),
],
);
}

View file

@ -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<EntryMapStyle>(
context: context,
builder: (context) => AvesSelectionDialog<EntryMapStyle?>(
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;
}
}
}

View file

@ -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<AlbumFilter> 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,

View file

@ -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() {

View file

@ -111,35 +111,45 @@ class _MimeDonutState extends State<MimeDonut> 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(),

View file

@ -281,7 +281,7 @@ class _StatsPageState extends State<StatsPage> {
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<StatsPage> {
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,

View file

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

View file

@ -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<ActionEvent<EntryAction>> get eventStream => _eventStreamController.stream;
bool isVisible(AvesEntry targetEntry, EntryAction action) {
final canWrite = !device.isReadOnly;
final canWrite = !settings.isReadOnly;
switch (action) {
// general
case EntryAction.editDate:

View file

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

View file

@ -36,6 +36,7 @@ class _SlideshowButtonsState extends State<SlideshowButtons> {
void initState() {
super.initState();
_registerWidget(widget);
WidgetsBinding.instance.addPostFrameCallback((_) => _requestFocus());
}
@override
@ -53,17 +54,15 @@ class _SlideshowButtonsState extends State<SlideshowButtons> {
}
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<SlideshowButtons> {
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();
}

View file

@ -10,6 +10,8 @@ class AvesMapController {
Stream<MapControllerMoveEvent> get moveCommands => _events.where((event) => event is MapControllerMoveEvent).cast<MapControllerMoveEvent>();
Stream<MapControllerZoomEvent> get zoomCommands => _events.where((event) => event is MapControllerZoomEvent).cast<MapControllerZoomEvent>();
Stream<MapIdleUpdate> get idleUpdates => _events.where((event) => event is MapIdleUpdate).cast<MapIdleUpdate>();
Stream<MapMarkerLocationChangeEvent> get markerLocationChanges => _events.where((event) => event is MapMarkerLocationChangeEvent).cast<MapMarkerLocationChangeEvent>();
@ -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;

View file

@ -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<void> Function(double amount) zoomBy, VoidCallback resetRotation);
typedef ButtonPanelBuilder = Widget Function(VoidCallback resetRotation);
typedef MarkerClusterBuilder<T> = Map<MarkerKey<T>, GeoEntry<T>> Function();
typedef MarkerWidgetBuilder<T> = Widget Function(MarkerKey<T> key);
typedef MarkerImageReadyChecker<T> = bool Function(MarkerKey<T> key);

View file

@ -95,6 +95,7 @@ class _EntryGoogleMapState<T> extends State<EntryGoogleMap<T>> 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<T> extends State<EntryGoogleMap<T>> with WidgetsBindi
},
),
widget.decoratorBuilder(context, _buildMap()),
widget.buttonPanelBuilder(_zoomBy, _resetRotation),
widget.buttonPanelBuilder(_resetRotation),
],
);
}

View file

@ -89,6 +89,7 @@ class _EntryHmsMapState<T> extends State<EntryHmsMap<T>> {
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<T> extends State<EntryHmsMap<T>> {
},
),
widget.decoratorBuilder(context, _buildMap()),
widget.buttonPanelBuilder(_zoomBy, _resetRotation),
widget.buttonPanelBuilder(_resetRotation),
],
);
}