Polish startup loader and map fly-in
Build and deploy Flutter Web / build (push) Successful in 4m35s
Build and deploy Flutter Web / build (push) Successful in 4m35s
This commit is contained in:
@@ -62,14 +62,11 @@ class _MapContent extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _MapContentState extends State<_MapContent> {
|
||||
static const _fallbackCenter = LatLng(16.0544, 108.2022);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final state = widget.state;
|
||||
final selected = state.selectedPlace;
|
||||
final userCoordinate = state.userCoordinate;
|
||||
final mapCenter = _focusCenter(state) ?? _fallbackCenter;
|
||||
final placeCubit = context.read<PlaceCubit>();
|
||||
final traitCounts = _countPlaceTraits(state.places);
|
||||
final availableTraits = [
|
||||
@@ -89,7 +86,6 @@ class _MapContentState extends State<_MapContent> {
|
||||
body: Stack(
|
||||
children: [
|
||||
_MapboxMapLayer(
|
||||
initialCenter: mapCenter,
|
||||
focusCenter: _focusCenter(state),
|
||||
places: state.recommendations,
|
||||
selectedPlaceId: selected?.id,
|
||||
@@ -309,7 +305,6 @@ class _ProjectedMarker {
|
||||
|
||||
class _MapboxMapLayer extends StatefulWidget {
|
||||
const _MapboxMapLayer({
|
||||
required this.initialCenter,
|
||||
required this.focusCenter,
|
||||
required this.places,
|
||||
required this.selectedPlaceId,
|
||||
@@ -317,7 +312,6 @@ class _MapboxMapLayer extends StatefulWidget {
|
||||
required this.onPlaceTap,
|
||||
});
|
||||
|
||||
final LatLng initialCenter;
|
||||
final LatLng? focusCenter;
|
||||
final List<PlaceRecommendation> places;
|
||||
final String? selectedPlaceId;
|
||||
@@ -334,7 +328,9 @@ class _MapboxMapLayerState extends State<_MapboxMapLayer> {
|
||||
mbx.MapboxMap? _mapboxMap;
|
||||
var _mapLoaded = false;
|
||||
var _didInitialFlyIn = false;
|
||||
var _cameraAnimationSequence = 0;
|
||||
var _projectionVersion = 0;
|
||||
Timer? _initialFlyInTimer;
|
||||
LatLng? _lastFocusCenter;
|
||||
List<_ProjectedMarker> _projectedPlaces = const [];
|
||||
Offset? _projectedUserCoordinate;
|
||||
@@ -354,12 +350,10 @@ class _MapboxMapLayerState extends State<_MapboxMapLayer> {
|
||||
mbx.MapWidget(
|
||||
styleUri: _mapboxStyleUri,
|
||||
viewport: mbx.CameraViewportState(
|
||||
center: _mapboxPoint(
|
||||
_didInitialFlyIn ? widget.initialCenter : _worldCenter,
|
||||
),
|
||||
zoom: _didInitialFlyIn ? 14.8 : 0.58,
|
||||
bearing: _didInitialFlyIn ? -16 : -24,
|
||||
pitch: _didInitialFlyIn ? 62 : 0,
|
||||
center: _mapboxPoint(_worldCenter),
|
||||
zoom: 0.58,
|
||||
bearing: -42,
|
||||
pitch: 0,
|
||||
),
|
||||
onMapCreated: _onMapCreated,
|
||||
onStyleLoadedListener: (_) {
|
||||
@@ -402,6 +396,12 @@ class _MapboxMapLayerState extends State<_MapboxMapLayer> {
|
||||
_mapboxMap = mapboxMap;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_initialFlyInTimer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _enableGlobeProjection() async {
|
||||
final mapboxMap = _mapboxMap;
|
||||
if (mapboxMap == null) {
|
||||
@@ -430,6 +430,7 @@ class _MapboxMapLayerState extends State<_MapboxMapLayer> {
|
||||
pitch: 62,
|
||||
);
|
||||
if (_didInitialFlyIn) {
|
||||
_cameraAnimationSequence++;
|
||||
unawaited(
|
||||
mapboxMap.easeTo(camera, mbx.MapAnimationOptions(duration: 900)),
|
||||
);
|
||||
@@ -437,12 +438,29 @@ class _MapboxMapLayerState extends State<_MapboxMapLayer> {
|
||||
}
|
||||
|
||||
_didInitialFlyIn = true;
|
||||
final animationSequence = ++_cameraAnimationSequence;
|
||||
unawaited(
|
||||
mapboxMap.flyTo(
|
||||
camera,
|
||||
mbx.MapAnimationOptions(duration: 4200, startDelay: 350),
|
||||
mapboxMap.easeTo(
|
||||
mbx.CameraOptions(
|
||||
center: _mapboxPoint(_worldCenter),
|
||||
zoom: 0.62,
|
||||
bearing: 26,
|
||||
pitch: 0,
|
||||
),
|
||||
mbx.MapAnimationOptions(duration: 1100),
|
||||
),
|
||||
);
|
||||
_initialFlyInTimer?.cancel();
|
||||
_initialFlyInTimer = Timer(const Duration(milliseconds: 760), () {
|
||||
if (!mounted ||
|
||||
animationSequence != _cameraAnimationSequence ||
|
||||
_mapboxMap != mapboxMap) {
|
||||
return;
|
||||
}
|
||||
unawaited(
|
||||
mapboxMap.flyTo(camera, mbx.MapAnimationOptions(duration: 4600)),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _projectMarkers() async {
|
||||
@@ -1043,9 +1061,7 @@ class _TelegramLoginScreenState extends State<_TelegramLoginScreen> {
|
||||
}
|
||||
|
||||
class _AnimatedEarthBackdrop extends StatefulWidget {
|
||||
const _AnimatedEarthBackdrop({this.compact = false});
|
||||
|
||||
final bool compact;
|
||||
const _AnimatedEarthBackdrop();
|
||||
|
||||
@override
|
||||
State<_AnimatedEarthBackdrop> createState() => _AnimatedEarthBackdropState();
|
||||
@@ -1080,7 +1096,6 @@ class _AnimatedEarthBackdropState extends State<_AnimatedEarthBackdrop>
|
||||
painter: _EarthBackdropPainter(
|
||||
progress: _controller.value,
|
||||
tokens: context.mapflowTokens,
|
||||
compact: widget.compact,
|
||||
),
|
||||
child: const SizedBox.expand(),
|
||||
);
|
||||
@@ -1091,15 +1106,10 @@ class _AnimatedEarthBackdropState extends State<_AnimatedEarthBackdrop>
|
||||
}
|
||||
|
||||
class _EarthBackdropPainter extends CustomPainter {
|
||||
const _EarthBackdropPainter({
|
||||
required this.progress,
|
||||
required this.tokens,
|
||||
required this.compact,
|
||||
});
|
||||
const _EarthBackdropPainter({required this.progress, required this.tokens});
|
||||
|
||||
final double progress;
|
||||
final MapflowThemeTokens tokens;
|
||||
final bool compact;
|
||||
|
||||
static const _landMasses = <({double lon, double lat, double sx, double sy})>[
|
||||
(lon: -106, lat: 42, sx: 0.30, sy: 0.18),
|
||||
@@ -1432,78 +1442,58 @@ class _EarthBackdropPainter extends CustomPainter {
|
||||
}
|
||||
|
||||
Offset _earthCenter(Size size) {
|
||||
if (compact) {
|
||||
return Offset(size.width * 0.50, size.height * 0.39);
|
||||
}
|
||||
return Offset(size.width * 0.50, size.height * 0.40);
|
||||
}
|
||||
|
||||
double _earthRadius(Size size) {
|
||||
final base = math.min(size.width, size.height);
|
||||
final radius = base * (compact ? 0.18 : 0.31);
|
||||
return radius.clamp(116.0, compact ? 180.0 : 310.0);
|
||||
final radius = base * 0.31;
|
||||
return radius.clamp(116.0, 310.0);
|
||||
}
|
||||
|
||||
double _radians(double degrees) => degrees * math.pi / 180;
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _EarthBackdropPainter oldDelegate) {
|
||||
return oldDelegate.progress != progress ||
|
||||
oldDelegate.tokens != tokens ||
|
||||
oldDelegate.compact != compact;
|
||||
return oldDelegate.progress != progress || oldDelegate.tokens != tokens;
|
||||
}
|
||||
}
|
||||
|
||||
class _MapLoading extends StatelessWidget {
|
||||
const _MapLoading();
|
||||
|
||||
static const _iconPath = 'icons/mapflow-signal-spoon.svg';
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final tokens = context.mapflowTokens;
|
||||
return Scaffold(
|
||||
backgroundColor: tokens.darkSurface,
|
||||
body: Stack(
|
||||
children: [
|
||||
const Positioned.fill(child: _AnimatedEarthBackdrop(compact: true)),
|
||||
Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(tokens.panelRadius),
|
||||
child: Image.network(
|
||||
'icons/Icon-192.png',
|
||||
width: 64,
|
||||
height: 64,
|
||||
errorBuilder: (_, _, _) => Icon(
|
||||
Icons.map_outlined,
|
||||
color: tokens.voiceAccent,
|
||||
size: 56,
|
||||
),
|
||||
),
|
||||
body: Center(
|
||||
child: SizedBox.square(
|
||||
dimension: 112,
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
SizedBox.square(
|
||||
dimension: 96,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 3,
|
||||
color: tokens.voiceAccent,
|
||||
backgroundColor: tokens.onDark.withValues(alpha: 0.1),
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
Text(
|
||||
'MapFlow',
|
||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||
color: tokens.onDark,
|
||||
fontWeight: FontWeight.w900,
|
||||
letterSpacing: 0,
|
||||
),
|
||||
),
|
||||
ClipOval(
|
||||
child: SvgPicture.network(
|
||||
_iconPath,
|
||||
width: 72,
|
||||
height: 72,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
SizedBox(
|
||||
width: 96,
|
||||
child: LinearProgressIndicator(
|
||||
minHeight: 3,
|
||||
color: tokens.voiceAccent,
|
||||
backgroundColor: tokens.onDark.withValues(alpha: 0.12),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -1522,7 +1512,6 @@ class _MapError extends StatelessWidget {
|
||||
body: Stack(
|
||||
children: [
|
||||
const _MapboxMapLayer(
|
||||
initialCenter: LatLng(16.0544, 108.2022),
|
||||
focusCenter: LatLng(16.0544, 108.2022),
|
||||
places: [],
|
||||
selectedPlaceId: null,
|
||||
|
||||
Reference in New Issue
Block a user