map: create shortcut to custom region and filters
This commit is contained in:
parent
5ce8bef9cc
commit
b9327db44b
25 changed files with 350 additions and 137 deletions
|
@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
|
|||
|
||||
## <a id="unreleased"></a>[Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- Map: create shortcut to custom region and filters
|
||||
|
||||
### Fixed
|
||||
|
||||
- crash when loading large collection
|
||||
|
|
|
@ -314,6 +314,7 @@ open class MainActivity : FlutterFragmentActivity() {
|
|||
return hashMapOf(
|
||||
INTENT_DATA_KEY_ACTION to INTENT_ACTION_VIEW_GEO,
|
||||
INTENT_DATA_KEY_URI to uri.toString(),
|
||||
INTENT_DATA_KEY_FILTERS to extractFiltersFromIntent(intent),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -584,6 +585,8 @@ open class MainActivity : FlutterFragmentActivity() {
|
|||
|
||||
// dart page routes
|
||||
const val COLLECTION_PAGE_ROUTE_NAME = "/collection"
|
||||
const val ENTRY_VIEWER_PAGE_ROUTE_NAME = "/viewer"
|
||||
const val EXPLORER_PAGE_ROUTE_NAME = "/explorer"
|
||||
const val MAP_PAGE_ROUTE_NAME = "/map"
|
||||
const val SEARCH_PAGE_ROUTE_NAME = "/search"
|
||||
|
||||
|
|
|
@ -23,11 +23,15 @@ import com.bumptech.glide.Glide
|
|||
import com.bumptech.glide.load.DecodeFormat
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
import deckers.thibault.aves.MainActivity
|
||||
import deckers.thibault.aves.MainActivity.Companion.COLLECTION_PAGE_ROUTE_NAME
|
||||
import deckers.thibault.aves.MainActivity.Companion.ENTRY_VIEWER_PAGE_ROUTE_NAME
|
||||
import deckers.thibault.aves.MainActivity.Companion.EXPLORER_PAGE_ROUTE_NAME
|
||||
import deckers.thibault.aves.MainActivity.Companion.EXTRA_KEY_EXPLORER_PATH
|
||||
import deckers.thibault.aves.MainActivity.Companion.EXTRA_KEY_FILTERS_ARRAY
|
||||
import deckers.thibault.aves.MainActivity.Companion.EXTRA_KEY_FILTERS_STRING
|
||||
import deckers.thibault.aves.MainActivity.Companion.EXTRA_KEY_PAGE
|
||||
import deckers.thibault.aves.MainActivity.Companion.EXTRA_STRING_ARRAY_SEPARATOR
|
||||
import deckers.thibault.aves.MainActivity.Companion.MAP_PAGE_ROUTE_NAME
|
||||
import deckers.thibault.aves.R
|
||||
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
|
||||
import deckers.thibault.aves.channel.calls.Coresult.Companion.safeSuspend
|
||||
|
@ -354,12 +358,17 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler {
|
|||
// shortcuts
|
||||
|
||||
private fun pinShortcut(call: MethodCall, result: MethodChannel.Result) {
|
||||
// common arguments
|
||||
val label = call.argument<String>("label")
|
||||
val iconBytes = call.argument<ByteArray>("iconBytes")
|
||||
val route = call.argument<String>("route")
|
||||
// route dependent arguments
|
||||
val filters = call.argument<List<String>>("filters")
|
||||
val explorerPath = call.argument<String>("explorerPath")
|
||||
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
|
||||
if (label == null) {
|
||||
val explorerPath = call.argument<String>("path")
|
||||
val viewUri = call.argument<String>("viewUri")?.let { Uri.parse(it) }
|
||||
val geoUri = call.argument<String>("geoUri")?.let { Uri.parse(it) }
|
||||
|
||||
if (label == null || route == null) {
|
||||
result.error("pin-args", "missing arguments", null)
|
||||
return
|
||||
}
|
||||
|
@ -383,24 +392,60 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler {
|
|||
// so that foreground is rendered at the intended scale
|
||||
val supportAdaptiveIcon = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
|
||||
|
||||
icon = IconCompat.createWithResource(context, if (supportAdaptiveIcon) R.mipmap.ic_shortcut_collection else R.drawable.ic_shortcut_collection)
|
||||
val resId = when (route) {
|
||||
MAP_PAGE_ROUTE_NAME -> if (supportAdaptiveIcon) R.mipmap.ic_shortcut_map else R.drawable.ic_shortcut_map
|
||||
else -> if (supportAdaptiveIcon) R.mipmap.ic_shortcut_collection else R.drawable.ic_shortcut_collection
|
||||
}
|
||||
icon = IconCompat.createWithResource(context, resId)
|
||||
}
|
||||
|
||||
val intent = when {
|
||||
filters != null -> Intent(Intent.ACTION_MAIN, null, context, MainActivity::class.java)
|
||||
.putExtra(EXTRA_KEY_PAGE, "/collection")
|
||||
.putExtra(EXTRA_KEY_FILTERS_ARRAY, filters.toTypedArray())
|
||||
// on API 25, `String[]` or `ArrayList` extras are null when using the shortcut
|
||||
// so we use a joined `String` as fallback
|
||||
.putExtra(EXTRA_KEY_FILTERS_STRING, filters.joinToString(EXTRA_STRING_ARRAY_SEPARATOR))
|
||||
val intent: Intent = when (route) {
|
||||
COLLECTION_PAGE_ROUTE_NAME -> {
|
||||
if (filters == null) {
|
||||
result.error("pin-filters", "collection shortcut requires filters", null)
|
||||
return
|
||||
}
|
||||
Intent(Intent.ACTION_MAIN, null, context, MainActivity::class.java)
|
||||
.putExtra(EXTRA_KEY_PAGE, route)
|
||||
.putExtra(EXTRA_KEY_FILTERS_ARRAY, filters.toTypedArray())
|
||||
// on API 25, `String[]` or `ArrayList` extras are null when using the shortcut
|
||||
// so we use a joined `String` as fallback
|
||||
.putExtra(EXTRA_KEY_FILTERS_STRING, filters.joinToString(EXTRA_STRING_ARRAY_SEPARATOR))
|
||||
}
|
||||
|
||||
explorerPath != null -> Intent(Intent.ACTION_MAIN, null, context, MainActivity::class.java)
|
||||
.putExtra(EXTRA_KEY_PAGE, "/explorer")
|
||||
.putExtra(EXTRA_KEY_EXPLORER_PATH, explorerPath)
|
||||
ENTRY_VIEWER_PAGE_ROUTE_NAME -> {
|
||||
if (viewUri == null) {
|
||||
result.error("pin-viewUri", "viewer shortcut requires URI", null)
|
||||
return
|
||||
}
|
||||
Intent(Intent.ACTION_VIEW, viewUri, context, MainActivity::class.java)
|
||||
}
|
||||
|
||||
EXPLORER_PAGE_ROUTE_NAME -> {
|
||||
Intent(Intent.ACTION_MAIN, null, context, MainActivity::class.java)
|
||||
.putExtra(EXTRA_KEY_PAGE, route)
|
||||
.putExtra(EXTRA_KEY_EXPLORER_PATH, explorerPath)
|
||||
}
|
||||
|
||||
MAP_PAGE_ROUTE_NAME -> {
|
||||
if (geoUri == null) {
|
||||
result.error("pin-geoUri", "map shortcut requires URI", null)
|
||||
return
|
||||
}
|
||||
Intent(Intent.ACTION_VIEW, geoUri, context, MainActivity::class.java).apply {
|
||||
putExtra(EXTRA_KEY_PAGE, route)
|
||||
// filters are optional
|
||||
filters?.let {
|
||||
putExtra(EXTRA_KEY_FILTERS_ARRAY, it.toTypedArray())
|
||||
// on API 25, `String[]` or `ArrayList` extras are null when using the shortcut
|
||||
// so we use a joined `String` as fallback
|
||||
putExtra(EXTRA_KEY_FILTERS_STRING, it.joinToString(EXTRA_STRING_ARRAY_SEPARATOR))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uri != null -> Intent(Intent.ACTION_VIEW, uri, context, MainActivity::class.java)
|
||||
else -> {
|
||||
result.error("pin-intent", "failed to build intent", null)
|
||||
result.error("pin-route", "unsupported shortcut route=$route", null)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:aves/utils/math_utils.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
|
||||
// e.g. `geo:44.4361283,26.1027248?z=4.0(Bucharest)`
|
||||
|
@ -24,3 +25,13 @@ import 'package:latlong2/latlong.dart';
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String toGeoUri(LatLng latLng, {double? zoom}) {
|
||||
final latitude = roundToPrecision(latLng.latitude, decimals: 6);
|
||||
final longitude = roundToPrecision(latLng.longitude, decimals: 6);
|
||||
var uri = 'geo:$latitude,$longitude?q=$latitude,$longitude';
|
||||
if (zoom != null) {
|
||||
uri += '&z=$zoom';
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
|
|
|
@ -106,6 +106,7 @@ class Contributors {
|
|||
Contributor('splice11', 'trenchedgrandpa@protonmail.com'),
|
||||
Contributor('Ihor Hordiichuk', 'igor_ck@outlook.com'),
|
||||
Contributor('João Palmeiro', 'joaommpalmeiro@gmail.com'),
|
||||
Contributor('Whoever4976', 'wolffjonas47@gmail.com'),
|
||||
// Contributor('Alvi Khan', 'aveenalvi@gmail.com'), // Bengali
|
||||
// Contributor('Htet Oo Hlaing', 'htetoh2006@outlook.com'), // Burmese
|
||||
// Contributor('Khant', 'khant@users.noreply.hosted.weblate.org'), // Burmese
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:aves/geo/uri.dart';
|
||||
import 'package:aves/model/app_inventory.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/entry/extensions/props.dart';
|
||||
import 'package:aves/model/filters/filters.dart';
|
||||
import 'package:aves/services/common/services.dart';
|
||||
import 'package:aves/utils/math_utils.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
|
@ -30,7 +30,15 @@ abstract class AppService {
|
|||
|
||||
Future<bool> shareSingle(String uri, String mimeType);
|
||||
|
||||
Future<void> pinToHomeScreen(String label, AvesEntry? coverEntry, {Set<CollectionFilter>? filters, String? explorerPath, String? uri});
|
||||
Future<void> pinToHomeScreen(
|
||||
String label,
|
||||
AvesEntry? coverEntry, {
|
||||
required String route,
|
||||
Set<CollectionFilter>? filters,
|
||||
String? path,
|
||||
String? viewUri,
|
||||
String? geoUri,
|
||||
});
|
||||
}
|
||||
|
||||
class PlatformAppService implements AppService {
|
||||
|
@ -138,13 +146,9 @@ class PlatformAppService implements AppService {
|
|||
|
||||
@override
|
||||
Future<bool> openMap(LatLng latLng) async {
|
||||
final latitude = roundToPrecision(latLng.latitude, decimals: 6);
|
||||
final longitude = roundToPrecision(latLng.longitude, decimals: 6);
|
||||
final geoUri = 'geo:$latitude,$longitude?q=$latitude,$longitude';
|
||||
|
||||
try {
|
||||
final result = await _platform.invokeMethod('openMap', <String, dynamic>{
|
||||
'geoUri': geoUri,
|
||||
'geoUri': toGeoUri(latLng),
|
||||
});
|
||||
if (result != null) return result as bool;
|
||||
} on PlatformException catch (e, stack) {
|
||||
|
@ -203,7 +207,15 @@ class PlatformAppService implements AppService {
|
|||
// app shortcuts
|
||||
|
||||
@override
|
||||
Future<void> pinToHomeScreen(String label, AvesEntry? coverEntry, {Set<CollectionFilter>? filters, String? explorerPath, String? uri}) async {
|
||||
Future<void> pinToHomeScreen(
|
||||
String label,
|
||||
AvesEntry? coverEntry, {
|
||||
required String route,
|
||||
Set<CollectionFilter>? filters,
|
||||
String? path,
|
||||
String? viewUri,
|
||||
String? geoUri,
|
||||
}) async {
|
||||
Uint8List? iconBytes;
|
||||
if (coverEntry != null) {
|
||||
final size = coverEntry.isVideo ? 0.0 : 256.0;
|
||||
|
@ -221,9 +233,11 @@ class PlatformAppService implements AppService {
|
|||
await _platform.invokeMethod('pinShortcut', <String, dynamic>{
|
||||
'label': label,
|
||||
'iconBytes': iconBytes,
|
||||
'route': route,
|
||||
'filters': filters?.map((filter) => filter.toJson()).toList(),
|
||||
'explorerPath': explorerPath,
|
||||
'uri': uri,
|
||||
'path': path,
|
||||
'viewUri': viewUri,
|
||||
'geoUri': geoUri,
|
||||
});
|
||||
} on PlatformException catch (e, stack) {
|
||||
await reportService.recordError(e, stack);
|
||||
|
|
|
@ -11,6 +11,7 @@ extension ExtraMapActionView on MapAction {
|
|||
MapAction.openMapApp => l10n.entryActionOpenMap,
|
||||
MapAction.zoomIn => l10n.mapZoomInTooltip,
|
||||
MapAction.zoomOut => l10n.mapZoomOutTooltip,
|
||||
MapAction.addShortcut => l10n.collectionActionAddShortcut,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -22,6 +23,7 @@ extension ExtraMapActionView on MapAction {
|
|||
MapAction.openMapApp => AIcons.openOutside,
|
||||
MapAction.zoomIn => AIcons.zoomIn,
|
||||
MapAction.zoomOut => AIcons.zoomOut,
|
||||
MapAction.addShortcut => AIcons.addShortcut,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ import 'package:aves/theme/durations.dart';
|
|||
import 'package:aves/theme/themes.dart';
|
||||
import 'package:aves/utils/collection_utils.dart';
|
||||
import 'package:aves/utils/mime_utils.dart';
|
||||
import 'package:aves/widgets/collection/collection_page.dart';
|
||||
import 'package:aves/widgets/common/action_mixins/entry_editor.dart';
|
||||
import 'package:aves/widgets/common/action_mixins/entry_storage.dart';
|
||||
import 'package:aves/widgets/common/action_mixins/feedback.dart';
|
||||
|
@ -746,7 +747,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
|||
final (coverEntry, name) = result;
|
||||
if (name.isEmpty) return;
|
||||
|
||||
await appService.pinToHomeScreen(name, coverEntry, filters: filters);
|
||||
await appService.pinToHomeScreen(name, coverEntry, route: CollectionPage.routeName, filters: filters);
|
||||
if (!device.showPinShortcutFeedback) {
|
||||
showFeedback(context, FeedbackType.info, context.l10n.genericSuccessFeedback);
|
||||
}
|
||||
|
|
|
@ -423,17 +423,20 @@ class _AvesFilterChipState extends State<AvesFilterChip> {
|
|||
);
|
||||
|
||||
final animate = context.select<Settings, bool>((v) => v.animate);
|
||||
if (animate && (widget.heroType == HeroType.always || widget.heroType == HeroType.onTap && _tapped)) {
|
||||
chip = Hero(
|
||||
tag: filter,
|
||||
transitionOnUserGestures: true,
|
||||
child: MediaQueryDataProvider(
|
||||
child: DefaultTextStyle(
|
||||
style: const TextStyle(),
|
||||
child: chip,
|
||||
if (animate) {
|
||||
final heroType = widget.heroType;
|
||||
if (heroType == HeroType.always || (heroType == HeroType.onTap && _tapped)) {
|
||||
chip = Hero(
|
||||
tag: filter,
|
||||
transitionOnUserGestures: true,
|
||||
child: MediaQueryDataProvider(
|
||||
child: DefaultTextStyle(
|
||||
style: const TextStyle(),
|
||||
child: chip,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
);
|
||||
}
|
||||
}
|
||||
return chip;
|
||||
}
|
||||
|
|
|
@ -4,19 +4,31 @@ import 'package:flutter/material.dart';
|
|||
import 'package:provider/provider.dart';
|
||||
|
||||
class MapOverlayButton extends StatelessWidget {
|
||||
final Key? buttonKey;
|
||||
final Widget icon;
|
||||
final String tooltip;
|
||||
final VoidCallback? onPressed;
|
||||
final ValueWidgetBuilder<VisualDensity> builder;
|
||||
|
||||
const MapOverlayButton({
|
||||
super.key,
|
||||
this.buttonKey,
|
||||
required this.icon,
|
||||
required this.tooltip,
|
||||
required this.onPressed,
|
||||
required this.builder,
|
||||
});
|
||||
|
||||
factory MapOverlayButton.icon({
|
||||
Key? buttonKey,
|
||||
required Widget icon,
|
||||
required String tooltip,
|
||||
VoidCallback? onPressed,
|
||||
}) {
|
||||
return MapOverlayButton(
|
||||
builder: (context, visualDensity, child) => IconButton(
|
||||
key: buttonKey,
|
||||
iconSize: iconSize(visualDensity),
|
||||
visualDensity: visualDensity,
|
||||
icon: icon,
|
||||
onPressed: onPressed,
|
||||
tooltip: tooltip,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Selector<MapThemeData, Animation<double>>(
|
||||
|
@ -27,15 +39,10 @@ class MapOverlayButton extends StatelessWidget {
|
|||
),
|
||||
child: Selector<MapThemeData, VisualDensity>(
|
||||
selector: (context, v) => v.visualDensity,
|
||||
builder: (context, visualDensity, child) => IconButton(
|
||||
key: buttonKey,
|
||||
iconSize: 20 + 1.5 * visualDensity.horizontal,
|
||||
visualDensity: visualDensity,
|
||||
icon: icon,
|
||||
onPressed: onPressed,
|
||||
tooltip: tooltip,
|
||||
),
|
||||
builder: builder,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static double iconSize(VisualDensity visualDensity) => 20 + 1.5 * visualDensity.horizontal;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import 'package:aves/model/settings/enums/accessibility_animations.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/theme/durations.dart';
|
||||
import 'package:aves/theme/icons.dart';
|
||||
import 'package:aves/view/view.dart';
|
||||
import 'package:aves/widgets/common/basic/popup/menu_row.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';
|
||||
|
@ -10,24 +12,44 @@ import 'package:aves/widgets/common/map/map_action_delegate.dart';
|
|||
import 'package:aves_map/aves_map.dart';
|
||||
import 'package:aves_model/aves_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class MapButtonPanel extends StatelessWidget {
|
||||
final AvesMapController? controller;
|
||||
class MapButtonPanel extends StatefulWidget {
|
||||
final AvesMapController controller;
|
||||
final ValueNotifier<ZoomedBounds> boundsNotifier;
|
||||
final void Function(BuildContext context)? openMapPage;
|
||||
final VoidCallback? resetRotation;
|
||||
|
||||
const MapButtonPanel({
|
||||
super.key,
|
||||
required this.controller,
|
||||
required this.boundsNotifier,
|
||||
this.openMapPage,
|
||||
this.resetRotation,
|
||||
});
|
||||
|
||||
@override
|
||||
State<MapButtonPanel> createState() => _MapButtonPanelState();
|
||||
}
|
||||
|
||||
class _MapButtonPanelState extends State<MapButtonPanel> {
|
||||
late MapActionDelegate _actionDelegate;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_updateDelegate();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant MapButtonPanel oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
_updateDelegate();
|
||||
}
|
||||
|
||||
void _updateDelegate() => _actionDelegate = MapActionDelegate(widget.controller);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final iconTheme = IconTheme.of(context);
|
||||
|
@ -37,23 +59,24 @@ class MapButtonPanel extends StatelessWidget {
|
|||
switch (context.select<MapThemeData, MapNavigationButton>((v) => v.navigationButton)) {
|
||||
case MapNavigationButton.back:
|
||||
if (!settings.useTvLayout) {
|
||||
navigationButton = MapOverlayButton(
|
||||
navigationButton = MapOverlayButton.icon(
|
||||
icon: const BackButtonIcon(),
|
||||
onPressed: () => Navigator.maybeOf(context)?.pop(),
|
||||
tooltip: MaterialLocalizations.of(context).backButtonTooltip,
|
||||
);
|
||||
}
|
||||
case MapNavigationButton.close:
|
||||
navigationButton = MapOverlayButton(
|
||||
navigationButton = MapOverlayButton.icon(
|
||||
icon: const CloseButtonIcon(),
|
||||
onPressed: SystemNavigator.pop,
|
||||
tooltip: MaterialLocalizations.of(context).closeButtonTooltip,
|
||||
);
|
||||
case MapNavigationButton.map:
|
||||
if (openMapPage != null) {
|
||||
navigationButton = MapOverlayButton(
|
||||
final _openMapPage = widget.openMapPage;
|
||||
if (_openMapPage != null) {
|
||||
navigationButton = MapOverlayButton.icon(
|
||||
icon: const Icon(AIcons.showFullscreenCorners),
|
||||
onPressed: () => openMapPage?.call(context),
|
||||
onPressed: () => _openMapPage.call(context),
|
||||
tooltip: context.l10n.openMapPageTooltip,
|
||||
);
|
||||
}
|
||||
|
@ -65,6 +88,11 @@ class MapButtonPanel extends StatelessWidget {
|
|||
final visualDensity = context.select<MapThemeData, VisualDensity>((v) => v.visualDensity);
|
||||
final double padding = 8 + visualDensity.horizontal * 2;
|
||||
|
||||
final actions = [
|
||||
MapAction.openMapApp,
|
||||
MapAction.addShortcut,
|
||||
].where((action) => _actionDelegate.isVisible(context, action)).toList();
|
||||
|
||||
return Positioned.fill(
|
||||
child: TooltipTheme(
|
||||
data: TooltipTheme.of(context).copyWith(
|
||||
|
@ -90,7 +118,7 @@ class MapButtonPanel extends StatelessWidget {
|
|||
SizedBox(height: padding),
|
||||
],
|
||||
ValueListenableBuilder<ZoomedBounds>(
|
||||
valueListenable: boundsNotifier,
|
||||
valueListenable: widget.boundsNotifier,
|
||||
builder: (context, bounds, child) {
|
||||
final degrees = bounds.rotation;
|
||||
final opacity = degrees == 0 ? .0 : 1.0;
|
||||
|
@ -99,7 +127,7 @@ class MapButtonPanel extends StatelessWidget {
|
|||
child: AnimatedOpacity(
|
||||
opacity: opacity,
|
||||
duration: context.select<DurationsData, Duration>((v) => v.viewerOverlayAnimation),
|
||||
child: MapOverlayButton(
|
||||
child: MapOverlayButton.icon(
|
||||
icon: Transform(
|
||||
origin: iconSize.center(Offset.zero),
|
||||
transform: Matrix4.rotationZ(degToRadian(degrees)),
|
||||
|
@ -110,7 +138,7 @@ class MapButtonPanel extends StatelessWidget {
|
|||
size: iconSize,
|
||||
),
|
||||
),
|
||||
onPressed: () => resetRotation?.call(),
|
||||
onPressed: widget.controller.resetRotation,
|
||||
tooltip: context.l10n.mapPointNorthUpTooltip,
|
||||
),
|
||||
),
|
||||
|
@ -123,7 +151,7 @@ class MapButtonPanel extends StatelessWidget {
|
|||
showCoordinateFilter
|
||||
? Expanded(
|
||||
child: OverlayCoordinateFilterChip(
|
||||
boundsNotifier: boundsNotifier,
|
||||
boundsNotifier: widget.boundsNotifier,
|
||||
padding: padding,
|
||||
),
|
||||
)
|
||||
|
@ -133,8 +161,31 @@ class MapButtonPanel extends StatelessWidget {
|
|||
// key is expected by test driver
|
||||
child: Column(
|
||||
children: [
|
||||
_buildActionButton(context, MapAction.openMapApp),
|
||||
if (actions.length == 1) _buildActionButton(context, actions.first),
|
||||
if (actions.length > 1)
|
||||
MapOverlayButton(builder: (context, visualDensity, child) {
|
||||
final animations = context.read<Settings>().accessibilityAnimations;
|
||||
return PopupMenuButton<MapAction>(
|
||||
itemBuilder: (context) => actions
|
||||
.map((action) => PopupMenuItem(
|
||||
value: action,
|
||||
child: MenuRow(
|
||||
text: action.getText(context),
|
||||
icon: action.getIcon(),
|
||||
),
|
||||
))
|
||||
.toList(),
|
||||
onSelected: (action) async {
|
||||
// wait for the popup menu to hide before proceeding with the action
|
||||
await Future.delayed(animations.popUpAnimationDelay * timeDilation);
|
||||
_actionDelegate.onActionSelected(context, action);
|
||||
},
|
||||
iconSize: MapOverlayButton.iconSize(visualDensity),
|
||||
popUpAnimationStyle: animations.popUpAnimationStyle,
|
||||
);
|
||||
}),
|
||||
SizedBox(height: padding),
|
||||
// key is expected by test driver
|
||||
_buildActionButton(context, MapAction.selectStyle, buttonKey: const Key('map-menu-layers')),
|
||||
],
|
||||
),
|
||||
|
@ -161,10 +212,10 @@ class MapButtonPanel extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
|
||||
Widget _buildActionButton(BuildContext context, MapAction action, {Key? buttonKey}) => MapOverlayButton(
|
||||
Widget _buildActionButton(BuildContext context, MapAction action, {Key? buttonKey}) => MapOverlayButton.icon(
|
||||
buttonKey: buttonKey,
|
||||
icon: action.getIcon(),
|
||||
onPressed: () => MapActionDelegate(controller).onActionSelected(context, action),
|
||||
onPressed: () => _actionDelegate.onActionSelected(context, action),
|
||||
tooltip: action.getText(context),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ import 'package:latlong2/latlong.dart';
|
|||
import 'package:provider/provider.dart';
|
||||
|
||||
class GeoMap extends StatefulWidget {
|
||||
final AvesMapController? controller;
|
||||
final AvesMapController controller;
|
||||
final CollectionLens? collection;
|
||||
final List<AvesEntry>? entries;
|
||||
final Size availableSize;
|
||||
|
@ -60,7 +60,7 @@ class GeoMap extends StatefulWidget {
|
|||
|
||||
const GeoMap({
|
||||
super.key,
|
||||
this.controller,
|
||||
required this.controller,
|
||||
this.collection,
|
||||
this.entries,
|
||||
required this.availableSize,
|
||||
|
@ -124,10 +124,7 @@ class _GeoMapState extends State<GeoMap> {
|
|||
|
||||
void _registerWidget(GeoMap widget) {
|
||||
widget.collection?.addListener(_onCollectionChanged);
|
||||
final controller = widget.controller;
|
||||
if (controller != null) {
|
||||
_subscriptions.add(controller.markerLocationChanges.listen((event) => _onCollectionChanged()));
|
||||
}
|
||||
_subscriptions.add(widget.controller.markerLocationChanges.listen((event) => _onCollectionChanged()));
|
||||
}
|
||||
|
||||
void _unregisterWidget(GeoMap widget) {
|
||||
|
@ -164,7 +161,6 @@ 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) {
|
||||
|
@ -172,7 +168,7 @@ class _GeoMapState extends State<GeoMap> {
|
|||
case EntryMapStyle.googleHybrid:
|
||||
case EntryMapStyle.googleTerrain:
|
||||
child = mobileServices.buildMap<AvesEntry>(
|
||||
controller: controller,
|
||||
controller: widget.controller,
|
||||
clusterListenable: _clusterChangeNotifier,
|
||||
boundsNotifier: _boundsNotifier,
|
||||
style: mapStyle,
|
||||
|
@ -194,7 +190,7 @@ class _GeoMapState extends State<GeoMap> {
|
|||
case EntryMapStyle.osmHot:
|
||||
case EntryMapStyle.stamenWatercolor:
|
||||
child = EntryLeafletMap<AvesEntry>(
|
||||
controller: controller,
|
||||
controller: widget.controller,
|
||||
clusterListenable: _clusterChangeNotifier,
|
||||
boundsNotifier: _boundsNotifier,
|
||||
minZoom: 2,
|
||||
|
@ -324,11 +320,7 @@ class _GeoMapState extends State<GeoMap> {
|
|||
Widget replacement = Stack(
|
||||
children: [
|
||||
const MapDecorator(),
|
||||
MapButtonPanel(
|
||||
controller: controller,
|
||||
boundsNotifier: _boundsNotifier,
|
||||
openMapPage: widget.openMapPage,
|
||||
),
|
||||
_buildButtonPanel(context),
|
||||
],
|
||||
);
|
||||
if (mapHeight != null) {
|
||||
|
@ -560,13 +552,12 @@ class _GeoMapState extends State<GeoMap> {
|
|||
|
||||
Widget _decorateMap(BuildContext context, Widget? child) => MapDecorator(child: child);
|
||||
|
||||
Widget _buildButtonPanel(VoidCallback resetRotation) {
|
||||
Widget _buildButtonPanel(BuildContext context) {
|
||||
if (settings.useTvLayout) return const SizedBox();
|
||||
return MapButtonPanel(
|
||||
controller: widget.controller,
|
||||
boundsNotifier: _boundsNotifier,
|
||||
openMapPage: widget.openMapPage,
|
||||
resetRotation: resetRotation,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,13 +14,13 @@ import 'package:latlong2/latlong.dart';
|
|||
import 'package:provider/provider.dart';
|
||||
|
||||
class EntryLeafletMap<T> extends StatefulWidget {
|
||||
final AvesMapController? controller;
|
||||
final AvesMapController controller;
|
||||
final Listenable clusterListenable;
|
||||
final ValueNotifier<ZoomedBounds> boundsNotifier;
|
||||
final double minZoom, maxZoom;
|
||||
final EntryMapStyle style;
|
||||
final TransitionBuilder decoratorBuilder;
|
||||
final ButtonPanelBuilder buttonPanelBuilder;
|
||||
final WidgetBuilder buttonPanelBuilder;
|
||||
final MarkerClusterBuilder<T> markerClusterBuilder;
|
||||
final MarkerWidgetBuilder<T> markerWidgetBuilder;
|
||||
final ValueNotifier<LatLng?>? dotLocationNotifier;
|
||||
|
@ -34,7 +34,7 @@ class EntryLeafletMap<T> extends StatefulWidget {
|
|||
|
||||
const EntryLeafletMap({
|
||||
super.key,
|
||||
this.controller,
|
||||
required this.controller,
|
||||
required this.clusterListenable,
|
||||
required this.boundsNotifier,
|
||||
this.minZoom = 0,
|
||||
|
@ -94,11 +94,10 @@ class _EntryLeafletMapState<T> extends State<EntryLeafletMap<T>> with TickerProv
|
|||
|
||||
void _registerWidget(EntryLeafletMap<T> widget) {
|
||||
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()));
|
||||
_subscriptions.add(avesMapController.moveCommands.listen((event) => _moveTo(event.latLng)));
|
||||
_subscriptions.add(avesMapController.zoomCommands.listen((event) => _zoomBy(event.delta)));
|
||||
_subscriptions.add(avesMapController.rotationResetCommands.listen((_) => _resetRotation()));
|
||||
_subscriptions.add(_leafletMapController.mapEventStream.listen((_) => _updateVisibleRegion()));
|
||||
widget.clusterListenable.addListener(_updateMarkers);
|
||||
widget.boundsNotifier.addListener(_onBoundsChanged);
|
||||
}
|
||||
|
@ -116,7 +115,7 @@ class _EntryLeafletMapState<T> extends State<EntryLeafletMap<T>> with TickerProv
|
|||
return Stack(
|
||||
children: [
|
||||
widget.decoratorBuilder(context, _buildMap()),
|
||||
widget.buttonPanelBuilder(_resetRotation),
|
||||
widget.buttonPanelBuilder(context),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
@ -240,7 +239,7 @@ class _EntryLeafletMapState<T> extends State<EntryLeafletMap<T>> with TickerProv
|
|||
|
||||
void _onIdle() {
|
||||
if (!mounted) return;
|
||||
widget.controller?.notifyIdle(bounds);
|
||||
widget.controller.notifyIdle(bounds);
|
||||
_updateMarkers();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,37 +1,94 @@
|
|||
import 'package:aves/geo/uri.dart';
|
||||
import 'package:aves/model/device.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/model/source/collection_lens.dart';
|
||||
import 'package:aves/services/common/services.dart';
|
||||
import 'package:aves/view/view.dart';
|
||||
import 'package:aves/widgets/common/action_mixins/feedback.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/dialogs/add_shortcut_dialog.dart';
|
||||
import 'package:aves/widgets/dialogs/selection_dialogs/common.dart';
|
||||
import 'package:aves/widgets/dialogs/selection_dialogs/single_selection.dart';
|
||||
import 'package:aves/widgets/map/map_page.dart';
|
||||
import 'package:aves_map/aves_map.dart';
|
||||
import 'package:aves_model/aves_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class MapActionDelegate {
|
||||
final AvesMapController? controller;
|
||||
class MapActionDelegate with FeedbackMixin {
|
||||
final AvesMapController controller;
|
||||
|
||||
const MapActionDelegate(this.controller);
|
||||
|
||||
bool isVisible(BuildContext context, MapAction action) {
|
||||
switch (action) {
|
||||
case MapAction.selectStyle:
|
||||
case MapAction.openMapApp:
|
||||
case MapAction.zoomIn:
|
||||
case MapAction.zoomOut:
|
||||
return true;
|
||||
case MapAction.addShortcut:
|
||||
return device.canPinShortcut && context.currentRouteName == MapPage.routeName;
|
||||
}
|
||||
}
|
||||
|
||||
void onActionSelected(BuildContext context, MapAction action) {
|
||||
switch (action) {
|
||||
case MapAction.selectStyle:
|
||||
showSelectionDialog<EntryMapStyle>(
|
||||
context: context,
|
||||
builder: (context) => AvesSingleSelectionDialog<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,
|
||||
);
|
||||
_selectStyle(context);
|
||||
case MapAction.openMapApp:
|
||||
OpenMapAppNotification().dispatch(context);
|
||||
case MapAction.zoomIn:
|
||||
controller?.zoomBy(1);
|
||||
controller.zoomBy(1);
|
||||
case MapAction.zoomOut:
|
||||
controller?.zoomBy(-1);
|
||||
controller.zoomBy(-1);
|
||||
case MapAction.addShortcut:
|
||||
_addShortcut(context);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _selectStyle(BuildContext context) => showSelectionDialog<EntryMapStyle>(
|
||||
context: context,
|
||||
builder: (context) => AvesSingleSelectionDialog<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,
|
||||
);
|
||||
|
||||
Future<void> _addShortcut(BuildContext context) async {
|
||||
final idleBounds = controller.idleBounds;
|
||||
if (idleBounds == null) {
|
||||
showFeedback(context, FeedbackType.warn, context.l10n.genericFailureFeedback);
|
||||
return;
|
||||
}
|
||||
|
||||
final collection = context.read<CollectionLens>();
|
||||
final result = await showDialog<(AvesEntry?, String)>(
|
||||
context: context,
|
||||
builder: (context) => AddShortcutDialog(
|
||||
defaultName: '',
|
||||
collection: collection,
|
||||
),
|
||||
routeSettings: const RouteSettings(name: AddShortcutDialog.routeName),
|
||||
);
|
||||
if (result == null) return;
|
||||
|
||||
final (coverEntry, name) = result;
|
||||
if (name.isEmpty) return;
|
||||
|
||||
final geoUri = toGeoUri(idleBounds.projectedCenter, zoom: idleBounds.zoom);
|
||||
await appService.pinToHomeScreen(
|
||||
name,
|
||||
coverEntry,
|
||||
route: MapPage.routeName,
|
||||
filters: collection.filters,
|
||||
geoUri: geoUri,
|
||||
);
|
||||
if (!device.showPinShortcutFeedback) {
|
||||
showFeedback(context, FeedbackType.info, context.l10n.genericSuccessFeedback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import 'package:aves/services/common/services.dart';
|
|||
import 'package:aves/widgets/common/action_mixins/feedback.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/dialogs/add_shortcut_dialog.dart';
|
||||
import 'package:aves/widgets/explorer/explorer_page.dart';
|
||||
import 'package:aves/widgets/filter_grids/common/action_delegates/chip.dart';
|
||||
import 'package:aves/widgets/stats/stats_page.dart';
|
||||
import 'package:aves_model/aves_model.dart';
|
||||
|
@ -84,7 +85,7 @@ class ExplorerActionDelegate with FeedbackMixin {
|
|||
final (coverEntry, name) = result;
|
||||
if (name.isEmpty) return;
|
||||
|
||||
await appService.pinToHomeScreen(name, coverEntry, explorerPath: filter.path);
|
||||
await appService.pinToHomeScreen(name, coverEntry, route: ExplorerPage.routeName, path: filter.path);
|
||||
if (!device.showPinShortcutFeedback) {
|
||||
showFeedback(context, FeedbackType.info, context.l10n.genericSuccessFeedback);
|
||||
}
|
||||
|
|
|
@ -377,7 +377,10 @@ class _HomePageState extends State<HomePage> {
|
|||
return buildRoute((context) {
|
||||
final mapCollection = CollectionLens(
|
||||
source: source,
|
||||
filters: {LocationFilter.located},
|
||||
filters: {
|
||||
LocationFilter.located,
|
||||
if (filters != null) ...filters,
|
||||
},
|
||||
);
|
||||
return MapPage(
|
||||
collection: mapCollection,
|
||||
|
|
|
@ -5,8 +5,9 @@ import 'package:aves/model/entry/entry.dart';
|
|||
import 'package:aves/model/entry/extensions/location.dart';
|
||||
import 'package:aves/model/filters/coordinate.dart';
|
||||
import 'package:aves/model/filters/filters.dart';
|
||||
import 'package:aves/model/media/geotiff.dart';
|
||||
import 'package:aves/model/filters/location.dart';
|
||||
import 'package:aves/model/highlight.dart';
|
||||
import 'package:aves/model/media/geotiff.dart';
|
||||
import 'package:aves/model/settings/enums/accessibility_animations.dart';
|
||||
import 'package:aves/model/settings/enums/map_style.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
|
@ -62,10 +63,15 @@ class MapPage extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// do not rely on the `HighlightInfoProvider` app level
|
||||
// as the map can be stacked on top of other pages
|
||||
// that catch highlight events and will not let it bubble up
|
||||
return HighlightInfoProvider(
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
// do not rely on the `HighlightInfoProvider` app level
|
||||
// as the map can be stacked on top of other pages
|
||||
// that catch highlight events and will not let it bubble up
|
||||
HighlightInfoProvider(),
|
||||
// opening collection can be used by map actions
|
||||
ChangeNotifierProvider<CollectionLens>.value(value: collection),
|
||||
],
|
||||
child: AvesScaffold(
|
||||
body: SafeArea(
|
||||
left: false,
|
||||
|
@ -442,10 +448,16 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
|
|||
Navigator.maybeOf(context)?.pushAndRemoveUntil(
|
||||
MaterialPageRoute(
|
||||
settings: const RouteSettings(name: CollectionPage.routeName),
|
||||
builder: (context) => CollectionPage(
|
||||
source: openingCollection.source,
|
||||
filters: {...openingCollection.filters, filter},
|
||||
),
|
||||
builder: (context) {
|
||||
final filters = {...openingCollection.filters, filter};
|
||||
if (filter is CoordinateFilter) {
|
||||
filters.removeWhere((v) => (v is CoordinateFilter && v != filter) || v == LocationFilter.located);
|
||||
}
|
||||
return CollectionPage(
|
||||
source: openingCollection.source,
|
||||
filters: filters,
|
||||
);
|
||||
},
|
||||
),
|
||||
(route) => false,
|
||||
);
|
||||
|
|
|
@ -34,6 +34,7 @@ import 'package:aves/widgets/viewer/action/printer.dart';
|
|||
import 'package:aves/widgets/viewer/action/single_entry_editor.dart';
|
||||
import 'package:aves/widgets/viewer/controls/notifications.dart';
|
||||
import 'package:aves/widgets/viewer/debug/debug_page.dart';
|
||||
import 'package:aves/widgets/viewer/entry_viewer_page.dart';
|
||||
import 'package:aves/widgets/viewer/multipage/conductor.dart';
|
||||
import 'package:aves/widgets/viewer/source_viewer_page.dart';
|
||||
import 'package:aves/widgets/viewer/video/conductor.dart';
|
||||
|
@ -395,7 +396,7 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
|
|||
final name = result.$2;
|
||||
if (name.isEmpty) return;
|
||||
|
||||
await appService.pinToHomeScreen(name, targetEntry, uri: targetEntry.uri);
|
||||
await appService.pinToHomeScreen(name, targetEntry, route: EntryViewerPage.routeName, viewUri: targetEntry.uri);
|
||||
if (!device.showPinShortcutFeedback) {
|
||||
showFeedback(context, FeedbackType.info, context.l10n.genericSuccessFeedback);
|
||||
}
|
||||
|
|
|
@ -16,6 +16,8 @@ class AvesMapController {
|
|||
|
||||
Stream<MapControllerZoomEvent> get zoomCommands => _events.where((event) => event is MapControllerZoomEvent).cast<MapControllerZoomEvent>();
|
||||
|
||||
Stream<MapControllerRotationResetEvent> get rotationResetCommands => _events.where((event) => event is MapControllerRotationResetEvent).cast<MapControllerRotationResetEvent>();
|
||||
|
||||
Stream<MapIdleUpdate> get idleUpdates => _events.where((event) => event is MapIdleUpdate).cast<MapIdleUpdate>();
|
||||
|
||||
Stream<MapMarkerLocationChangeEvent> get markerLocationChanges => _events.where((event) => event is MapMarkerLocationChangeEvent).cast<MapMarkerLocationChangeEvent>();
|
||||
|
@ -41,6 +43,8 @@ class AvesMapController {
|
|||
|
||||
void zoomBy(double delta) => _streamController.add(MapControllerZoomEvent(delta));
|
||||
|
||||
void resetRotation() => _streamController.add(MapControllerRotationResetEvent());
|
||||
|
||||
void notifyIdle(ZoomedBounds bounds) {
|
||||
_idleBounds = bounds;
|
||||
_streamController.add(MapIdleUpdate(bounds));
|
||||
|
@ -61,6 +65,8 @@ class MapControllerZoomEvent {
|
|||
MapControllerZoomEvent(this.delta);
|
||||
}
|
||||
|
||||
class MapControllerRotationResetEvent {}
|
||||
|
||||
class MapIdleUpdate {
|
||||
final ZoomedBounds bounds;
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@ import 'package:aves_map/src/marker/key.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
|
||||
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);
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
enum MapAction {
|
||||
// any map panel
|
||||
selectStyle,
|
||||
openMapApp,
|
||||
zoomIn,
|
||||
zoomOut,
|
||||
// full page only
|
||||
addShortcut,
|
||||
}
|
||||
|
|
|
@ -12,12 +12,12 @@ abstract class MobileServices {
|
|||
List<EntryMapStyle> get mapStyles;
|
||||
|
||||
Widget buildMap<T>({
|
||||
required AvesMapController? controller,
|
||||
required AvesMapController controller,
|
||||
required Listenable clusterListenable,
|
||||
required ValueNotifier<ZoomedBounds> boundsNotifier,
|
||||
required EntryMapStyle style,
|
||||
required TransitionBuilder decoratorBuilder,
|
||||
required ButtonPanelBuilder buttonPanelBuilder,
|
||||
required WidgetBuilder buttonPanelBuilder,
|
||||
required MarkerClusterBuilder<T> markerClusterBuilder,
|
||||
required MarkerWidgetBuilder<T> markerWidgetBuilder,
|
||||
required MarkerImageReadyChecker<T> markerImageReadyChecker,
|
||||
|
|
|
@ -46,12 +46,12 @@ class PlatformMobileServices extends MobileServices {
|
|||
|
||||
@override
|
||||
Widget buildMap<T>({
|
||||
required AvesMapController? controller,
|
||||
required AvesMapController controller,
|
||||
required Listenable clusterListenable,
|
||||
required ValueNotifier<ZoomedBounds> boundsNotifier,
|
||||
required EntryMapStyle style,
|
||||
required TransitionBuilder decoratorBuilder,
|
||||
required ButtonPanelBuilder buttonPanelBuilder,
|
||||
required WidgetBuilder buttonPanelBuilder,
|
||||
required MarkerClusterBuilder<T> markerClusterBuilder,
|
||||
required MarkerWidgetBuilder<T> markerWidgetBuilder,
|
||||
required MarkerImageReadyChecker<T> markerImageReadyChecker,
|
||||
|
|
|
@ -9,13 +9,13 @@ import 'package:latlong2/latlong.dart' as ll;
|
|||
import 'package:provider/provider.dart';
|
||||
|
||||
class EntryGoogleMap<T> extends StatefulWidget {
|
||||
final AvesMapController? controller;
|
||||
final AvesMapController controller;
|
||||
final Listenable clusterListenable;
|
||||
final ValueNotifier<ZoomedBounds> boundsNotifier;
|
||||
final double? minZoom, maxZoom;
|
||||
final EntryMapStyle style;
|
||||
final TransitionBuilder decoratorBuilder;
|
||||
final ButtonPanelBuilder buttonPanelBuilder;
|
||||
final WidgetBuilder buttonPanelBuilder;
|
||||
final MarkerClusterBuilder<T> markerClusterBuilder;
|
||||
final MarkerWidgetBuilder<T> markerWidgetBuilder;
|
||||
final MarkerImageReadyChecker<T> markerImageReadyChecker;
|
||||
|
@ -29,7 +29,7 @@ class EntryGoogleMap<T> extends StatefulWidget {
|
|||
|
||||
const EntryGoogleMap({
|
||||
super.key,
|
||||
this.controller,
|
||||
required this.controller,
|
||||
required this.clusterListenable,
|
||||
required this.boundsNotifier,
|
||||
this.minZoom,
|
||||
|
@ -93,10 +93,9 @@ class _EntryGoogleMapState<T> extends State<EntryGoogleMap<T>> {
|
|||
|
||||
void _registerWidget(EntryGoogleMap<T> widget) {
|
||||
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)));
|
||||
}
|
||||
_subscriptions.add(avesMapController.moveCommands.listen((event) => _moveTo(_toServiceLatLng(event.latLng))));
|
||||
_subscriptions.add(avesMapController.zoomCommands.listen((event) => _zoomBy(event.delta)));
|
||||
_subscriptions.add(avesMapController.rotationResetCommands.listen((_) => _resetRotation()));
|
||||
widget.clusterListenable.addListener(_updateMarkers);
|
||||
}
|
||||
|
||||
|
@ -125,7 +124,7 @@ class _EntryGoogleMapState<T> extends State<EntryGoogleMap<T>> {
|
|||
},
|
||||
),
|
||||
widget.decoratorBuilder(context, _buildMap()),
|
||||
widget.buttonPanelBuilder(_resetRotation),
|
||||
widget.buttonPanelBuilder(context),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
@ -241,7 +240,7 @@ class _EntryGoogleMapState<T> extends State<EntryGoogleMap<T>> {
|
|||
|
||||
void _onIdle() {
|
||||
if (!mounted) return;
|
||||
widget.controller?.notifyIdle(bounds);
|
||||
widget.controller.notifyIdle(bounds);
|
||||
_updateMarkers();
|
||||
}
|
||||
|
||||
|
|
|
@ -18,12 +18,12 @@ class PlatformMobileServices extends MobileServices {
|
|||
|
||||
@override
|
||||
Widget buildMap<T>({
|
||||
required AvesMapController? controller,
|
||||
required AvesMapController controller,
|
||||
required Listenable clusterListenable,
|
||||
required ValueNotifier<ZoomedBounds> boundsNotifier,
|
||||
required EntryMapStyle style,
|
||||
required TransitionBuilder decoratorBuilder,
|
||||
required ButtonPanelBuilder buttonPanelBuilder,
|
||||
required WidgetBuilder buttonPanelBuilder,
|
||||
required MarkerClusterBuilder<T> markerClusterBuilder,
|
||||
required MarkerWidgetBuilder<T> markerWidgetBuilder,
|
||||
required MarkerImageReadyChecker<T> markerImageReadyChecker,
|
||||
|
|
Loading…
Reference in a new issue