Tune map target fly-in
Build and deploy Flutter Web / build (push) Successful in 5m19s

This commit is contained in:
Ruslan Bakiev
2026-06-24 13:07:48 +07:00
parent 381126e5d4
commit 98d6ec3f45
@@ -24,8 +24,13 @@ import '../domain/place_models.dart';
import 'widgets/place_photo_card.dart'; import 'widgets/place_photo_card.dart';
String get _mapboxStyle { String get _mapboxStyle {
final style = runtime_config.mapboxStyle(); final style = runtime_config.mapboxStyle().trim();
return style.isEmpty ? 'mapbox/standard' : style; if (style.isEmpty ||
style == 'mapbox/standard' ||
style == mbx.MapboxStyles.STANDARD) {
return mbx.MapboxStyles.STANDARD_SATELLITE;
}
return style;
} }
class MapflowShell extends StatelessWidget { class MapflowShell extends StatelessWidget {
@@ -67,6 +72,7 @@ class _MapContentState extends State<_MapContent> {
final state = widget.state; final state = widget.state;
final selected = state.selectedPlace; final selected = state.selectedPlace;
final userCoordinate = state.userCoordinate; final userCoordinate = state.userCoordinate;
final targetCenter = _focusCenter(state);
final placeCubit = context.read<PlaceCubit>(); final placeCubit = context.read<PlaceCubit>();
final traitCounts = _countPlaceTraits(state.places); final traitCounts = _countPlaceTraits(state.places);
final availableTraits = [ final availableTraits = [
@@ -86,12 +92,13 @@ class _MapContentState extends State<_MapContent> {
body: Stack( body: Stack(
children: [ children: [
_MapboxMapLayer( _MapboxMapLayer(
focusCenter: _focusCenter(state), focusCenter: targetCenter,
places: state.recommendations, places: state.recommendations,
selectedPlaceId: selected?.id, selectedPlaceId: selected?.id,
userCoordinate: userCoordinate, userCoordinate: userCoordinate,
onPlaceTap: (place) => _selectAndShowPlace(context, place), onPlaceTap: (place) => _selectAndShowPlace(context, place),
), ),
if (targetCenter == null) const _MapTargetLoadingOverlay(),
SafeArea( SafeArea(
child: Align( child: Align(
alignment: Alignment.topLeft, alignment: Alignment.topLeft,
@@ -323,7 +330,7 @@ class _MapboxMapLayer extends StatefulWidget {
} }
class _MapboxMapLayerState extends State<_MapboxMapLayer> { class _MapboxMapLayerState extends State<_MapboxMapLayer> {
static const _worldCenter = LatLng(8.0, 32.0); static const _worldCenter = LatLng(0, 0);
mbx.MapboxMap? _mapboxMap; mbx.MapboxMap? _mapboxMap;
var _mapLoaded = false; var _mapLoaded = false;
@@ -351,8 +358,8 @@ class _MapboxMapLayerState extends State<_MapboxMapLayer> {
styleUri: _mapboxStyleUri, styleUri: _mapboxStyleUri,
viewport: mbx.CameraViewportState( viewport: mbx.CameraViewportState(
center: _mapboxPoint(_worldCenter), center: _mapboxPoint(_worldCenter),
zoom: 0.58, zoom: 0.48,
bearing: -42, bearing: 0,
pitch: 0, pitch: 0,
), ),
onMapCreated: _onMapCreated, onMapCreated: _onMapCreated,
@@ -425,14 +432,14 @@ class _MapboxMapLayerState extends State<_MapboxMapLayer> {
_lastFocusCenter = focusCenter; _lastFocusCenter = focusCenter;
final camera = mbx.CameraOptions( final camera = mbx.CameraOptions(
center: _mapboxPoint(focusCenter), center: _mapboxPoint(focusCenter),
zoom: 14.8, zoom: 15.0,
bearing: -16, bearing: -12,
pitch: 62, pitch: 60,
); );
if (_didInitialFlyIn) { if (_didInitialFlyIn) {
_cameraAnimationSequence++; _cameraAnimationSequence++;
unawaited( unawaited(
mapboxMap.easeTo(camera, mbx.MapAnimationOptions(duration: 900)), mapboxMap.easeTo(camera, mbx.MapAnimationOptions(duration: 650)),
); );
return; return;
} }
@@ -442,27 +449,36 @@ class _MapboxMapLayerState extends State<_MapboxMapLayer> {
unawaited( unawaited(
mapboxMap.easeTo( mapboxMap.easeTo(
mbx.CameraOptions( mbx.CameraOptions(
center: _mapboxPoint(_worldCenter), center: _mapboxPoint(_targetOrbitCenter(focusCenter)),
zoom: 0.62, zoom: 0.72,
bearing: 26, bearing: _targetOrbitBearing(focusCenter),
pitch: 0, pitch: 0,
), ),
mbx.MapAnimationOptions(duration: 1100), mbx.MapAnimationOptions(duration: 850),
), ),
); );
_initialFlyInTimer?.cancel(); _initialFlyInTimer?.cancel();
_initialFlyInTimer = Timer(const Duration(milliseconds: 760), () { _initialFlyInTimer = Timer(const Duration(milliseconds: 520), () {
if (!mounted || if (!mounted ||
animationSequence != _cameraAnimationSequence || animationSequence != _cameraAnimationSequence ||
_mapboxMap != mapboxMap) { _mapboxMap != mapboxMap) {
return; return;
} }
unawaited( 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<void> _projectMarkers() async { Future<void> _projectMarkers() async {
final mapboxMap = _mapboxMap; final mapboxMap = _mapboxMap;
if (!_mapLoaded || mapboxMap == null) { 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 { class _MapError extends StatelessWidget {
const _MapError({required this.message}); const _MapError({required this.message});