mapbox / mapbox-maps-flutter

Interactive, thoroughly customizable maps for Flutter powered by Mapbox Maps SDK
https://www.mapbox.com/mobile-maps-sdk
Other
297 stars 122 forks source link

Clusters are not visible if map's style is any other than a default MapboxStyles.LIGHT #349

Open fufylev opened 10 months ago

fufylev commented 10 months ago

Version mapbox_maps_flutter: ^0.5.1 is being used. Flutter version 3.16.2

If I apply my own map style other than a default MapboxStyles.LIGHT, all clusters and unClusteredLayer are below the main map layout and not visible - means that I see them during some milliseconds (before map layout was loaded) and later map just covers them. Even if I use MapboxStyles.DARK the same problem exists.

Here is my code:

import 'dart:convert';

import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:ieye/features/map/domain/entities/lat_lng.dart';
import 'package:ieye/index.dart';
import 'package:ieye/router/router.gr.dart';
import 'package:ieye/theme.dart';
import 'package:mapbox_maps_flutter/mapbox_maps_flutter.dart';

class AnnotationClickListener extends OnPointAnnotationClickListener {
  final BuildContext context;

  AnnotationClickListener({required this.context});
  @override
  void onPointAnnotationClick(PointAnnotation annotation) {
    context.read<MapScreenBloc>().add(OnPlaceClickEvent(id: annotation.textField ?? ''));
  }
}

@RoutePage()
class MapScreen extends StatefulWidget {
  const MapScreen({super.key});

  @override
  State<MapScreen> createState() => _MapScreenState();
}

class _MapScreenState extends StateWithBLoc<MapScreenBloc, MapScreen>
    with TickerProviderStateMixin, AutomaticKeepAliveClientMixin {
  @override
  bool get wantKeepAlive => true;

  @override
  void onNewsReceived(BlocNews news) {
    if (news is ShowPlaceDetailsScreen) {
      AutoRouter.of(context).pop();
      context.router.push(PlaceDetailsRoute(place: news.place));
    } else if (news is ShowPlaceDetailsBottomSheet) {
      final place = news.place;
      final size = MediaQuery.of(context).size;
      showModalBottomSheet(
        useRootNavigator: true,
        backgroundColor: Colors.white,
        context: context,
        isScrollControlled: true,
        isDismissible: true,
        shape: const RoundedRectangleBorder(
            borderRadius: BorderRadius.vertical(top: Radius.circular(24.0))),
        clipBehavior: Clip.hardEdge,
        builder: (_) {
          return DraggableScrollableSheet(
              initialChildSize: 0.7,
              maxChildSize: ((size.height - context.statusBarHeight) / size.height) * 0.95,
              minChildSize: 0.5,
              expand: false,
              snap: false,
              builder: (_, scrollController) {
                return VenueShortBottomSheet(
                  key: ValueKey<PlaceEntity>(place),
                  scrollController: scrollController,
                  place: place,
                  onDetailsTap: () {
                    bloc.add(ShowPlaceDetailsScreenEvent(place: place));
                  },
                ); //whatever you're returning, does not have to be a Container
              });
        },
      );
    }
  }

  final pageController = PageController();
  late TextEditingController controller;
  late MapboxMap mapboxMap;

  Position center = Position(-0.1882464, 51.5382123);
  Position currentPosition = Position(-0.1882464, 51.5382123);
  double zoom = 10.0;
  bool mapLoaded = false;

  _onMapCreated(MapboxMap mapbox) async {
    setState(() {
      mapboxMap = mapbox;
      mapLoaded = true;
      mapboxMap.style;
    });
    final styleJson = await rootBundle.loadString('assets/map_styles/style.json');
    mapboxMap.style.setStyleJSON(styleJson);
    mapboxMap.compass.updateSettings(CompassSettings(enabled: false, opacity: 0.0));
    mapboxMap.logo.updateSettings(LogoSettings(marginBottom: -100.0));
    mapboxMap.attribution.updateSettings(AttributionSettings(marginBottom: -100.0));
    mapboxMap.scaleBar.updateSettings(ScaleBarSettings(enabled: false));
    mapboxMap.gestures.updateSettings(GesturesSettings(rotateEnabled: false));
    mapboxMap.location.updateSettings(LocationComponentSettings(enabled: false));
  }

  _onMapIdleListener(_) async {
    final cam = await mapboxMap.getCameraState();

    final coordinates = cam.center['coordinates'] as List;
    setState(() {
      zoom = cam.zoom;
      currentPosition = Position(
        num.tryParse(coordinates.first.toString()) ?? 0.0,
        num.tryParse(coordinates.last.toString()) ?? 0.0,
      );
    });
  }

  @override
  void initState() {
    super.initState();
    controller = TextEditingController();
  }

  @override
  void dispose() {
    controller.dispose();
    mapboxMap.dispose();
    super.dispose();
  }

  void setZoom(double value) {
    if (value > 18 || value < 4) return;
    setState(() => zoom = value);
    mapboxMap.setCamera(CameraOptions(
      center: Point(coordinates: currentPosition).toJson(),
      zoom: value,
    ));
  }

  void setCenter() {
    mapboxMap.setCamera(CameraOptions(
      center: Point(coordinates: center).toJson(),
      zoom: zoom,
    ));
  }

  void onTap() {
    FocusScopeNode currentFocus = FocusScope.of(context);
    if (!currentFocus.hasPrimaryFocus && currentFocus.focusedChild != null) {
      FocusManager.instance.primaryFocus?.unfocus();
    }
  }

  void setMarkers(BuildContext context, List<PlaceEntity> places) async {
    mapboxMap.annotations.createPointAnnotationManager().then((pointAnnotationManager) async {
      var options = <PointAnnotationOptions>[];

      for (var i = 0; i < places.length; i++) {
        final point = points[i];
        final place = places[i];

        final widget = IEyeMarker(
          place: place,
          onDetailsTap: () {},
        );

        final Uint8List? image = await createImageFromWidget(context, widget);

        options.add(
          PointAnnotationOptions(
            geometry: Point(coordinates: Position(point.lng, point.lat)).toJson(),
            image: image,
            textField: place.id.toString(),
            textColor: 0x00000000,
            iconSize: 1,
          ),
        );
      }

      pointAnnotationManager.createMulti(options);
      pointAnnotationManager
          .addOnPointAnnotationClickListener(AnnotationClickListener(context: context));
    });

    List<Map<String, Object>> features = [];

    for (var i = 0; i < places.length; i++) {
      final point = points[i];
      final place = places[i];
      features.add({
        'type': 'Feature',
        'id': place.id,
        'properties': {
          'point_count_abbreviated': '1',
          'cluster_id': place.id,
          // 'cluster': true,
          // 'point_count': 1
        },
        'geometry': {
          'type': 'Point',
          'coordinates': [point.lng, point.lat, 0.0]
        }
      });
    }

    final data = {
      'type': 'FeatureCollection',
      'crs': {
        'type': 'name',
        'properties': {'name': 'urn:ogc:def:crs:OGC:1.3:CRS84'}
      },
      'features': features,
    };

    final layout = {
      'type': 'geojson',
      'data': data,
      'cluster': true,
      'clusterMaxZoom': 14,
      'clusterRadius': 50,
    };

    mapboxMap.style.styleSourceExists('places').then((value) async {
      if (!value) {
        var source = jsonEncode(layout);
        mapboxMap.style.addStyleSource('places', source.toString());
      }
    });

    mapboxMap.style.styleLayerExists('clusters').then((value) async {
      if (!value) {
        var layer = await rootBundle.loadString('assets/cluster/cluster_layer.json');
        mapboxMap.style.addStyleLayer(layer, null);

        var clusterCountLayer =
            await rootBundle.loadString('assets/cluster/cluster_count_layer.json');
        mapboxMap.style.addStyleLayer(clusterCountLayer, null);

        // var unClusteredLayer =
        //     await rootBundle.loadString('assets/cluster/unclustered_point_layer.json');
        // mapboxMap.style.addStyleLayer(unClusteredLayer, null);
      }
    });
  }

  void setPositionFromGps(LatLng? position) {
    setState(() => center = Position(position?.lng ?? 0.0, position?.lat ?? 0.0));

    mapboxMap.setCamera(
      CameraOptions(
        center: Point(coordinates: Position(position?.lng ?? 0.0, position?.lat ?? 0.0)).toJson(),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    super.build(context);
    final colors = context.theme.extension<IeyeColors>();
    final statusBarHeight = context.statusBarHeight;
    final navBarHeight = context.navBarHeight;
    final height = MediaQuery.of(context).size.height;

    return Scaffold(
      body: BlocConsumer<MapScreenBloc, MapScreenState>(
        listener: (BuildContext context, MapScreenState state) {
          if (state.places.isNotEmpty && mapLoaded) {
            setMarkers(context, state.places);
          }
          if (state.gpsPosition != null) {
            // setPositionFromGps(state.gpsPosition);
          }
        },
        bloc: bloc,
        builder: (context, state) {
          return Stack(
            children: [
              MapWidget(
                key: const ValueKey('mapWidget'),
                resourceOptions: ResourceOptions(
                  accessToken:
                      'pk.eyJ1IjoiaWV5ZSIsImEiOiJjbGN2dDlhaWkxMG44M3BxdHIyb3MyeHZpIn0.D9Uo5M87jYRCnHU7qDYVMQ',
                ),
                styleUri: MapboxStyles.LIGHT,
                cameraOptions: CameraOptions(
                  center: Point(coordinates: currentPosition).toJson(),
                  zoom: zoom,
                ),
                onMapCreated: (MapboxMap mapboxMap) {
                  _onMapCreated(mapboxMap);
                  context.read<MapScreenBloc>().add(OnMapInitEvent());
                },
                onMapIdleListener: _onMapIdleListener,
              ),
              Positioned(
                left: 8,
                right: 8,
                top: statusBarHeight,
                child: const MapSearchTextFieldView(),
              ),
              Positioned(
                right: 8,
                bottom: navBarHeight + 92,
                child: RoundButton(
                  onTap: setCenter,
                  iconAssetPath: UiAssets.navigationFalse,
                  borderRadius: 24,
                  backgroundColor: colors?.opacityWhite80,
                ),
              ),
              Positioned(
                right: 8,
                top: (height - 48 - 48 - 12) / 2,
                child: RoundButton(
                  onTap: () => setZoom(zoom + 1),
                  iconAssetPath: UiAssets.plus,
                  borderRadius: 24,
                  backgroundColor: colors?.opacityWhite80,
                ),
              ),
              Positioned(
                right: 8,
                bottom: (height - 48 - 48 - 12) / 2,
                child: RoundButton(
                  onTap: () => setZoom(zoom - 1),
                  iconAssetPath: UiAssets.minus,
                  borderRadius: 24,
                  backgroundColor: colors?.opacityWhite80,
                ),
              ),
            ],
          );
        },
      ),
    );
  }
}

final points = [
  LatLng(lat: 51.5382123, lng: -0.1882464),
  LatLng(lat: 51.4382123, lng: -0.1982464),
  LatLng(lat: 51.5090229, lng: -0.2886548),
  LatLng(lat: 51.1090229, lng: -0.2786548),
  LatLng(lat: 51.5054563, lng: -0.0798412),
  LatLng(lat: 51.5090215, lng: -0.1959988),
  LatLng(lat: 51.5190215, lng: -0.1859988),
  LatLng(lat: 51.5577676, lng: -0.2008447),
];

Just for info here is my flutter doctor result:

Doctor summary (to see all details, run flutter doctor -v):
[!] Flutter (Channel stable, 3.16.2, on macOS 14.2.1 23C71 darwin-arm64, locale ru-RU)
    ! Warning: `dart` on your path resolves to /opt/homebrew/Cellar/dart-sdk/3.1.5/libexec/bin/dart, which is not inside your current Flutter SDK checkout at /Users/andreifufylev/fvm/versions/3.16.2. Consider
      adding /Users/andreifufylev/fvm/versions/3.16.2/bin to the front of your path.
[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 15.1)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2023.1)
[✓] IntelliJ IDEA Community Edition (version 2023.2.5)
[✓] VS Code (version 1.85.1)
[✓] Connected device (4 available)
[✓] Network resources

! Doctor found issues in 1 category.
appinteractive commented 8 months ago

Any updates on that?

fufylev commented 8 months ago

no ((