Skip to content

Commit afbb5e8

Browse files
fix(multilayer-map): recenter map when branch changes (#1182)
* fix(multilayer-map): recenter map when branch changes * feat(multilayer-map): enhance map zoom functionality and add recenter hook for branch changes * fix(multilayer-map): handle empty buildings list in branch recenter logic * fix(multilayer-map): update pending branch recenter logic to use state management
1 parent 3f5e7cf commit afbb5e8

4 files changed

Lines changed: 121 additions & 5 deletions

File tree

lib/features/map_view/controllers/map_controller.dart

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import "dart:async";
22

33
import "package:flutter/material.dart";
4+
import "package:flutter_map/flutter_map.dart";
45
import "package:flutter_map_animations/flutter_map_animations.dart";
56
import "package:flutter_riverpod/flutter_riverpod.dart";
67

@@ -10,6 +11,8 @@ import "bottom_sheet_controller.dart";
1011
import "controllers_set.dart";
1112

1213
class MyMapController<T extends GoogleNavigable> {
14+
static const _defaultFitPadding = EdgeInsets.all(24);
15+
1316
final Ref ref;
1417
final MapControllers<T> mapControllers;
1518
MyMapController(this.ref, this.mapControllers);
@@ -23,17 +26,45 @@ class MyMapController<T extends GoogleNavigable> {
2326
}
2427
}
2528

26-
Future<void> zoomOnMarker(T item) async {
29+
Future<void> zoomOnMarker(T item, {double? zoom}) async {
2730
if (!item.location.isValidForMap) return;
2831
final controller = await _controller;
2932
await controller.animateTo(
3033
dest: item.location,
31-
zoom: MapWidgetConfig.defaultMarkerZoom,
34+
zoom: zoom ?? MapWidgetConfig.defaultMarkerZoom,
3235
offset: Offset(0, -ref.read(bottomSheetControllerProvider).pixelsSafe / 2),
3336
rotation: 0,
3437
);
3538
}
3639

40+
Future<void> zoomOnItems(
41+
Iterable<T> items, {
42+
EdgeInsets padding = _defaultFitPadding,
43+
double? maxZoom,
44+
double? singleItemZoom,
45+
}) async {
46+
final validItems = items.where((item) => item.location.isValidForMap).toList();
47+
if (validItems.isEmpty) return;
48+
if (validItems.length == 1) {
49+
return zoomOnMarker(validItems.first, zoom: singleItemZoom);
50+
}
51+
52+
final controller = await _controller;
53+
await controller.animatedFitCamera(
54+
cameraFit: CameraFit.coordinates(
55+
coordinates: validItems.map((item) => item.location).toList(),
56+
padding: EdgeInsets.fromLTRB(
57+
padding.left,
58+
padding.top,
59+
padding.right,
60+
padding.bottom + ref.read(bottomSheetControllerProvider).pixelsSafe,
61+
),
62+
maxZoom: maxZoom,
63+
),
64+
rotation: 0,
65+
);
66+
}
67+
3768
Future<void> onMarkerTap(T item) async {
3869
ref.read(mapControllers.activeMarker.notifier).toggleItem(item);
3970
ref.read(bottomSheetControllerProvider).resetSafe();
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import "dart:async";
2+
3+
import "package:fast_immutable_collections/fast_immutable_collections.dart";
4+
import "package:flutter/material.dart";
5+
import "package:flutter_hooks/flutter_hooks.dart";
6+
import "package:hooks_riverpod/hooks_riverpod.dart";
7+
8+
import "../../../config/map_view_config.dart";
9+
import "../../branches/data/model/branch.dart";
10+
import "../../multilayer_map/data/model/multilayer_item.dart";
11+
import "../controllers/bottom_sheet_controller.dart";
12+
import "../controllers/controllers_set.dart";
13+
14+
void useMultilayerBranchRecenter({
15+
required WidgetRef ref,
16+
required Branch? selectedBranch,
17+
required AsyncValue<IList<MultilayerItem>> sourceState,
18+
required ActiveMarkerProv<MultilayerItem> activeMarkerProvider,
19+
required MapControllerProv<MultilayerItem> mapControllerProvider,
20+
}) {
21+
final lastHandledBranch = useRef<Branch?>(selectedBranch);
22+
final pendingBranchRecenter = useState<Branch?>(null);
23+
24+
useEffect(() {
25+
if (selectedBranch == null || lastHandledBranch.value == selectedBranch) return null;
26+
27+
lastHandledBranch.value = selectedBranch;
28+
pendingBranchRecenter.value = selectedBranch;
29+
WidgetsBinding.instance.addPostFrameCallback((_) {
30+
ref.read(activeMarkerProvider.notifier).unselect();
31+
ref.read(bottomSheetControllerProvider).resetSafe();
32+
});
33+
return null;
34+
}, [selectedBranch]);
35+
36+
useEffect(() {
37+
final branchToRecenter = pendingBranchRecenter.value;
38+
if (branchToRecenter == null) return null;
39+
40+
switch (sourceState) {
41+
case AsyncValue(:final value) when value != null:
42+
final buildings = value
43+
.whereType<BuildingItem>()
44+
.where((item) => item.building.branch == branchToRecenter)
45+
.toList();
46+
final dataMatchesSelectedBranch = value.whereType<BuildingItem>().every(
47+
(item) => item.building.branch == branchToRecenter,
48+
);
49+
50+
if (!dataMatchesSelectedBranch || buildings.isEmpty) return null;
51+
52+
pendingBranchRecenter.value = null;
53+
WidgetsBinding.instance.addPostFrameCallback((_) {
54+
unawaited(
55+
ref
56+
.read(mapControllerProvider)
57+
.zoomOnItems(
58+
buildings,
59+
padding: const EdgeInsets.fromLTRB(56, 100, 56, 120),
60+
maxZoom: MapWidgetConfig.defaultMarkerZoom - 2,
61+
singleItemZoom: MapWidgetConfig.defaultMarkerZoom - 2,
62+
),
63+
);
64+
});
65+
default:
66+
return null;
67+
}
68+
return null;
69+
}, [sourceState, pendingBranchRecenter.value]);
70+
}

lib/features/map_view/widgets/map_widget.dart

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,13 @@ import "package:hooks_riverpod/hooks_riverpod.dart";
77

88
import "../../../config/map_view_config.dart";
99
import "../../../theme/app_theme.dart";
10+
import "../../branches/business/selected_branch_on_map.dart";
11+
import "../../multilayer_map/data/model/multilayer_item.dart";
1012
import "../../my_loc_button/presentation/is_following_controller.dart";
1113
import "../../my_loc_button/presentation/my_loc_layer.dart";
1214
import "../controllers/controllers_set.dart";
1315
import "../data/cache.dart";
16+
import "../hooks/use_multilayer_branch_recenter.dart";
1417
import "load_animated_controller.dart";
1518
import "map_atrribution.dart";
1619
import "map_config.dart";
@@ -22,8 +25,20 @@ class MapWidget<T extends GoogleNavigable> extends HookConsumerWidget {
2225
const MapWidget(this.semanticsLabel, {super.key});
2326
@override
2427
Widget build(BuildContext context, WidgetRef ref) {
28+
final mapControllerProvider = context.mapController<T>();
29+
30+
if (T == MultilayerItem) {
31+
useMultilayerBranchRecenter(
32+
ref: ref,
33+
selectedBranch: ref.watch(selectedBranchOnMapProvider),
34+
sourceState: ref.watch(context.mapSourceRepository<MultilayerItem>()),
35+
activeMarkerProvider: context.activeMarkerController<MultilayerItem>(),
36+
mapControllerProvider: context.mapController<MultilayerItem>(),
37+
);
38+
}
39+
2540
return LoadAnimationMapController<T>(
26-
myMapController: ref.watch(context.mapController<T>()),
41+
myMapController: ref.watch(mapControllerProvider),
2742
builder: (context, controller) {
2843
return FlutterMap(
2944
options: MapOptions(
@@ -38,7 +53,7 @@ class MapWidget<T extends GoogleNavigable> extends HookConsumerWidget {
3853
.mapMoved(); // stop following location on user interaction
3954
}
4055
},
41-
onTap: ref.watch(context.mapController<T>()).onMapBackgroundTap,
56+
onTap: ref.watch(mapControllerProvider).onMapBackgroundTap,
4257
),
4358
mapController: controller.mapController,
4459
children: [

pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
1616
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
1717
# In Windows, build-name is used as the major, minor, and patch parts
1818
# of the product and file versions while build-number is used as the build suffix.
19-
version: 1.2.44+132
19+
version: 1.2.45+135
2020

2121
environment:
2222
sdk: ">=3.11.1 <4.0.0"

0 commit comments

Comments
 (0)