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