Skip to content

Commit

Permalink
feat!: Rename ZoomButtons to ControlButtons and add optional track lo…
Browse files Browse the repository at this point in the history
…cation button (#140)

* multi: replace MapZoomButtons with MapControlButtons, add optional track location button. Improved MapControlButtons test.

* fix: dart format

* chore: removed MapLibreTrackLocationButton tooltip

* fix: switched MapControlButtons to StatefulWidget improving the PermissionManager handling and removing some bad code.

* fix: Removed test of MapLibreTrackLocationButton. Removed useless PermissionManager initialization.

* chore: added curly braces

* feat: enhance location tracking button with loading state and GPS status

* chore: dart format

* fix: prevent enabling location tracking if permissions are not granted

* chore: added TrackLocationState instead of handling state with booleans var.

* refactor: improve location tracking logic and state management in MapControlButtons

---------

Co-authored-by: Joscha <[email protected]>
  • Loading branch information
gabbopalma and josxha authored Dec 2, 2024
1 parent b50fe61 commit 675aecb
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 67 deletions.
4 changes: 3 additions & 1 deletion example/lib/user_interface_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ class _UserInterfacePageState extends State<UserInterfacePage> {
children: const [
MapScalebar(),
SourceAttribution(),
MapZoomButtons(),
MapControlButtons(
showTrackLocation: true,
),
MapCompass(),
],
),
Expand Down
2 changes: 1 addition & 1 deletion lib/maplibre.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ export 'src/permission_manager.dart';
export 'src/queried_layer.dart';
export 'src/style/style.dart';
export 'src/ui/map_compass.dart';
export 'src/ui/map_control_buttons.dart';
export 'src/ui/map_scalebar.dart';
export 'src/ui/map_zoom_buttons.dart';
export 'src/ui/source_attribution.dart';
export 'src/utils.dart';
export 'src/widget_layer.dart';
166 changes: 166 additions & 0 deletions lib/src/ui/map_control_buttons.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:maplibre/maplibre.dart';
import 'package:pointer_interceptor/pointer_interceptor.dart';

/// Display a zoom-in and zoom-out button to the [MapLibreMap] by using it in
/// [MapLibreMap.children].
///
/// This widget is purposefully kept simple. If you need to change the design
/// or behavior of the zoom buttons a lot, prefer to copy this class into your
/// app and adjust it according to your needs.
@immutable
class MapControlButtons extends StatefulWidget {
/// Display a zoom-in and zoom-out button to the [MapLibreMap] by using it in
/// [MapLibreMap.children].
const MapControlButtons({
super.key,
this.padding = const EdgeInsets.symmetric(vertical: 50, horizontal: 12),
this.alignment = Alignment.bottomRight,
this.showTrackLocation = false,
this.requestPermissionsExplanation =
'We need your location to show it on the map.',
});

/// The padding.
final EdgeInsets padding;

/// The alignment of the buttons.
final Alignment alignment;

/// Whether to show the track location button.
///
/// This button is currently not available on web.
final bool showTrackLocation;

/// The explanation to show when requesting location permissions.
final String requestPermissionsExplanation;

@override
State<MapControlButtons> createState() => _MapControlButtonsState();
}

class _MapControlButtonsState extends State<MapControlButtons> {
late final PermissionManager? _permissionManager;
_TrackLocationState _trackState = _TrackLocationState.gpsNotFixed;

late bool _trackLocationButtonInitialized = false;

@override
void initState() {
super.initState();
if (!kIsWeb && widget.showTrackLocation) {
_permissionManager = PermissionManager();
}
}

@override
Widget build(BuildContext context) {
final controller = MapController.maybeOf(context);
if (controller == null) return const SizedBox.shrink();

if (!kIsWeb && widget.showTrackLocation) {
if (!_trackLocationButtonInitialized) {
_trackLocationButtonInitialized = true;
if (_permissionManager?.locationPermissionsGranted ?? false) {
WidgetsBinding.instance.addPostFrameCallback((_) async {
await _initializeLocation(controller, trackLocation: false);
});
}
}
}

return Container(
alignment: widget.alignment,
padding: widget.padding,
child: PointerInterceptor(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
FloatingActionButton(
heroTag: 'MapLibreZoomInButton',
onPressed: () => controller.animateCamera(
zoom: controller.getCamera().zoom + 1,
nativeDuration: const Duration(milliseconds: 200),
),
child: const Icon(Icons.add),
),
const SizedBox(height: 8),
FloatingActionButton(
heroTag: 'MapLibreZoomOutButton',
onPressed: () => controller.animateCamera(
zoom: controller.getCamera().zoom - 1,
nativeDuration: const Duration(milliseconds: 200),
),
child: const Icon(Icons.remove),
),
if (!kIsWeb && widget.showTrackLocation) ...[
const SizedBox(height: 8),
FloatingActionButton(
heroTag: 'MapLibreTrackLocationButton',
onPressed: () async => _initializeLocation(controller),
child: _trackState == _TrackLocationState.loading
? const SizedBox.square(
dimension: kDefaultFontSize,
child: CircularProgressIndicator(),
)
: Icon(
_trackState == _TrackLocationState.gpsFixed
? Icons.gps_fixed
: Icons.gps_not_fixed,
),
),
],
],
),
),
);
}

Future<void> _initializeLocation(
MapController controller, {
bool trackLocation = true,
}) async {
try {
if (!_permissionManager!.locationPermissionsGranted) {
setState(() => _trackState = _TrackLocationState.loading);

await _permissionManager.requestLocationPermissions(
explanation: widget.requestPermissionsExplanation,
);
}
} finally {
await _enableLocationServices(controller, trackLocation: trackLocation);
}
}

Future<void> _enableLocationServices(
MapController controller, {
bool trackLocation = true,
}) async {
if (!_permissionManager!.locationPermissionsGranted) {
setState(() => _trackState = _TrackLocationState.gpsNotFixed);
}

try {
await controller.enableLocation();
setState(() => _trackState = _TrackLocationState.gpsFixed);

if (trackLocation) await controller.trackLocation();
} catch (error) {
setState(() => _trackState = _TrackLocationState.gpsNotFixed);
}
}
}

/// Location tracking state.
enum _TrackLocationState {
/// Whether the permission is currently being fetched.
loading,

/// The permission is granted.
gpsFixed,

/// The permission is denied.
gpsNotFixed,
}
61 changes: 0 additions & 61 deletions lib/src/ui/map_zoom_buttons.dart

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import 'package:flutter/widgets.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:maplibre/maplibre.dart';
import 'package:mocktail/mocktail.dart';

import '../shared/mocks.dart';
import '../shared/ui_app.dart';

class MockPermissionManager extends Mock implements PermissionManager {}

void main() {
group('MapZoomButtons', () {
group('MapControlButtons', () {
testWidgets('render', (tester) async {
final camera = MapCamera(
center: Position(0, 0),
Expand All @@ -23,14 +25,17 @@ void main() {
camera: camera,
controller: controller,
children: const [
MapZoomButtons(alignment: alignment, padding: padding),
MapControlButtons(
alignment: alignment,
padding: padding,
),
],
);
await tester.pumpWidget(app);
// give some time for getAttributions
await tester.pumpAndSettle();

expect(find.byType(MapZoomButtons), findsOneWidget);
expect(find.byType(MapControlButtons), findsOneWidget);
});
});
}

0 comments on commit 675aecb

Please sign in to comment.