diff --git a/lib/features/mapflow/presentation/mapflow_shell.dart b/lib/features/mapflow/presentation/mapflow_shell.dart index f9bf8ae..5b9ad09 100644 --- a/lib/features/mapflow/presentation/mapflow_shell.dart +++ b/lib/features/mapflow/presentation/mapflow_shell.dart @@ -24,8 +24,13 @@ import '../domain/place_models.dart'; import 'widgets/place_photo_card.dart'; String get _mapboxStyle { - final style = runtime_config.mapboxStyle(); - return style.isEmpty ? 'mapbox/standard' : style; + final style = runtime_config.mapboxStyle().trim(); + if (style.isEmpty || + style == 'mapbox/standard' || + style == mbx.MapboxStyles.STANDARD) { + return mbx.MapboxStyles.STANDARD_SATELLITE; + } + return style; } class MapflowShell extends StatelessWidget { @@ -67,6 +72,7 @@ class _MapContentState extends State<_MapContent> { final state = widget.state; final selected = state.selectedPlace; final userCoordinate = state.userCoordinate; + final targetCenter = _focusCenter(state); final placeCubit = context.read(); final traitCounts = _countPlaceTraits(state.places); final availableTraits = [ @@ -86,12 +92,13 @@ class _MapContentState extends State<_MapContent> { body: Stack( children: [ _MapboxMapLayer( - focusCenter: _focusCenter(state), + focusCenter: targetCenter, places: state.recommendations, selectedPlaceId: selected?.id, userCoordinate: userCoordinate, onPlaceTap: (place) => _selectAndShowPlace(context, place), ), + if (targetCenter == null) const _MapTargetLoadingOverlay(), SafeArea( child: Align( alignment: Alignment.topLeft, @@ -323,7 +330,7 @@ class _MapboxMapLayer extends StatefulWidget { } class _MapboxMapLayerState extends State<_MapboxMapLayer> { - static const _worldCenter = LatLng(8.0, 32.0); + static const _worldCenter = LatLng(0, 0); mbx.MapboxMap? _mapboxMap; var _mapLoaded = false; @@ -351,8 +358,8 @@ class _MapboxMapLayerState extends State<_MapboxMapLayer> { styleUri: _mapboxStyleUri, viewport: mbx.CameraViewportState( center: _mapboxPoint(_worldCenter), - zoom: 0.58, - bearing: -42, + zoom: 0.48, + bearing: 0, pitch: 0, ), onMapCreated: _onMapCreated, @@ -425,14 +432,14 @@ class _MapboxMapLayerState extends State<_MapboxMapLayer> { _lastFocusCenter = focusCenter; final camera = mbx.CameraOptions( center: _mapboxPoint(focusCenter), - zoom: 14.8, - bearing: -16, - pitch: 62, + zoom: 15.0, + bearing: -12, + pitch: 60, ); if (_didInitialFlyIn) { _cameraAnimationSequence++; unawaited( - mapboxMap.easeTo(camera, mbx.MapAnimationOptions(duration: 900)), + mapboxMap.easeTo(camera, mbx.MapAnimationOptions(duration: 650)), ); return; } @@ -442,27 +449,36 @@ class _MapboxMapLayerState extends State<_MapboxMapLayer> { unawaited( mapboxMap.easeTo( mbx.CameraOptions( - center: _mapboxPoint(_worldCenter), - zoom: 0.62, - bearing: 26, + center: _mapboxPoint(_targetOrbitCenter(focusCenter)), + zoom: 0.72, + bearing: _targetOrbitBearing(focusCenter), pitch: 0, ), - mbx.MapAnimationOptions(duration: 1100), + mbx.MapAnimationOptions(duration: 850), ), ); _initialFlyInTimer?.cancel(); - _initialFlyInTimer = Timer(const Duration(milliseconds: 760), () { + _initialFlyInTimer = Timer(const Duration(milliseconds: 520), () { if (!mounted || animationSequence != _cameraAnimationSequence || _mapboxMap != mapboxMap) { return; } unawaited( - mapboxMap.flyTo(camera, mbx.MapAnimationOptions(duration: 4600)), + mapboxMap.flyTo(camera, mbx.MapAnimationOptions(duration: 2800)), ); }); } + LatLng _targetOrbitCenter(LatLng target) { + final latitude = (target.latitude * 0.18).clamp(-18.0, 18.0); + return LatLng(latitude, target.longitude); + } + + double _targetOrbitBearing(LatLng target) { + return (-target.longitude * 0.34).clamp(-64.0, 64.0); + } + Future _projectMarkers() async { final mapboxMap = _mapboxMap; if (!_mapLoaded || mapboxMap == null) { @@ -1499,6 +1515,49 @@ class _MapLoading extends StatelessWidget { } } +class _MapTargetLoadingOverlay extends StatelessWidget { + const _MapTargetLoadingOverlay(); + + @override + Widget build(BuildContext context) { + final tokens = context.mapflowTokens; + return IgnorePointer( + child: SafeArea( + child: Align( + alignment: Alignment.topCenter, + child: Padding( + padding: const EdgeInsets.only(top: 18), + child: DecoratedBox( + decoration: BoxDecoration( + color: tokens.mapPanel.withValues(alpha: 0.84), + borderRadius: BorderRadius.circular(999), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.24), + blurRadius: 22, + offset: const Offset(0, 10), + ), + ], + ), + child: Padding( + padding: const EdgeInsets.all(10), + child: SizedBox.square( + dimension: 28, + child: CircularProgressIndicator( + strokeWidth: 2.5, + color: tokens.voiceAccent, + backgroundColor: tokens.onDark.withValues(alpha: 0.12), + ), + ), + ), + ), + ), + ), + ), + ); + } +} + class _MapError extends StatelessWidget { const _MapError({required this.message});