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:
+1
-1
Submodule instructions updated: 9e16b68277...bf94f17aa2
@@ -62,14 +62,11 @@ class _MapContent extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _MapContentState extends State<_MapContent> {
|
class _MapContentState extends State<_MapContent> {
|
||||||
static const _fallbackCenter = LatLng(16.0544, 108.2022);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
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 mapCenter = _focusCenter(state) ?? _fallbackCenter;
|
|
||||||
final placeCubit = context.read<PlaceCubit>();
|
final placeCubit = context.read<PlaceCubit>();
|
||||||
final traitCounts = _countPlaceTraits(state.places);
|
final traitCounts = _countPlaceTraits(state.places);
|
||||||
final availableTraits = [
|
final availableTraits = [
|
||||||
@@ -89,7 +86,6 @@ class _MapContentState extends State<_MapContent> {
|
|||||||
body: Stack(
|
body: Stack(
|
||||||
children: [
|
children: [
|
||||||
_MapboxMapLayer(
|
_MapboxMapLayer(
|
||||||
initialCenter: mapCenter,
|
|
||||||
focusCenter: _focusCenter(state),
|
focusCenter: _focusCenter(state),
|
||||||
places: state.recommendations,
|
places: state.recommendations,
|
||||||
selectedPlaceId: selected?.id,
|
selectedPlaceId: selected?.id,
|
||||||
@@ -309,7 +305,6 @@ class _ProjectedMarker {
|
|||||||
|
|
||||||
class _MapboxMapLayer extends StatefulWidget {
|
class _MapboxMapLayer extends StatefulWidget {
|
||||||
const _MapboxMapLayer({
|
const _MapboxMapLayer({
|
||||||
required this.initialCenter,
|
|
||||||
required this.focusCenter,
|
required this.focusCenter,
|
||||||
required this.places,
|
required this.places,
|
||||||
required this.selectedPlaceId,
|
required this.selectedPlaceId,
|
||||||
@@ -317,7 +312,6 @@ class _MapboxMapLayer extends StatefulWidget {
|
|||||||
required this.onPlaceTap,
|
required this.onPlaceTap,
|
||||||
});
|
});
|
||||||
|
|
||||||
final LatLng initialCenter;
|
|
||||||
final LatLng? focusCenter;
|
final LatLng? focusCenter;
|
||||||
final List<PlaceRecommendation> places;
|
final List<PlaceRecommendation> places;
|
||||||
final String? selectedPlaceId;
|
final String? selectedPlaceId;
|
||||||
@@ -334,7 +328,9 @@ class _MapboxMapLayerState extends State<_MapboxMapLayer> {
|
|||||||
mbx.MapboxMap? _mapboxMap;
|
mbx.MapboxMap? _mapboxMap;
|
||||||
var _mapLoaded = false;
|
var _mapLoaded = false;
|
||||||
var _didInitialFlyIn = false;
|
var _didInitialFlyIn = false;
|
||||||
|
var _cameraAnimationSequence = 0;
|
||||||
var _projectionVersion = 0;
|
var _projectionVersion = 0;
|
||||||
|
Timer? _initialFlyInTimer;
|
||||||
LatLng? _lastFocusCenter;
|
LatLng? _lastFocusCenter;
|
||||||
List<_ProjectedMarker> _projectedPlaces = const [];
|
List<_ProjectedMarker> _projectedPlaces = const [];
|
||||||
Offset? _projectedUserCoordinate;
|
Offset? _projectedUserCoordinate;
|
||||||
@@ -354,12 +350,10 @@ class _MapboxMapLayerState extends State<_MapboxMapLayer> {
|
|||||||
mbx.MapWidget(
|
mbx.MapWidget(
|
||||||
styleUri: _mapboxStyleUri,
|
styleUri: _mapboxStyleUri,
|
||||||
viewport: mbx.CameraViewportState(
|
viewport: mbx.CameraViewportState(
|
||||||
center: _mapboxPoint(
|
center: _mapboxPoint(_worldCenter),
|
||||||
_didInitialFlyIn ? widget.initialCenter : _worldCenter,
|
zoom: 0.58,
|
||||||
),
|
bearing: -42,
|
||||||
zoom: _didInitialFlyIn ? 14.8 : 0.58,
|
pitch: 0,
|
||||||
bearing: _didInitialFlyIn ? -16 : -24,
|
|
||||||
pitch: _didInitialFlyIn ? 62 : 0,
|
|
||||||
),
|
),
|
||||||
onMapCreated: _onMapCreated,
|
onMapCreated: _onMapCreated,
|
||||||
onStyleLoadedListener: (_) {
|
onStyleLoadedListener: (_) {
|
||||||
@@ -402,6 +396,12 @@ class _MapboxMapLayerState extends State<_MapboxMapLayer> {
|
|||||||
_mapboxMap = mapboxMap;
|
_mapboxMap = mapboxMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_initialFlyInTimer?.cancel();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _enableGlobeProjection() async {
|
Future<void> _enableGlobeProjection() async {
|
||||||
final mapboxMap = _mapboxMap;
|
final mapboxMap = _mapboxMap;
|
||||||
if (mapboxMap == null) {
|
if (mapboxMap == null) {
|
||||||
@@ -430,6 +430,7 @@ class _MapboxMapLayerState extends State<_MapboxMapLayer> {
|
|||||||
pitch: 62,
|
pitch: 62,
|
||||||
);
|
);
|
||||||
if (_didInitialFlyIn) {
|
if (_didInitialFlyIn) {
|
||||||
|
_cameraAnimationSequence++;
|
||||||
unawaited(
|
unawaited(
|
||||||
mapboxMap.easeTo(camera, mbx.MapAnimationOptions(duration: 900)),
|
mapboxMap.easeTo(camera, mbx.MapAnimationOptions(duration: 900)),
|
||||||
);
|
);
|
||||||
@@ -437,12 +438,29 @@ class _MapboxMapLayerState extends State<_MapboxMapLayer> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_didInitialFlyIn = true;
|
_didInitialFlyIn = true;
|
||||||
|
final animationSequence = ++_cameraAnimationSequence;
|
||||||
unawaited(
|
unawaited(
|
||||||
mapboxMap.flyTo(
|
mapboxMap.easeTo(
|
||||||
camera,
|
mbx.CameraOptions(
|
||||||
mbx.MapAnimationOptions(duration: 4200, startDelay: 350),
|
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 {
|
Future<void> _projectMarkers() async {
|
||||||
@@ -1043,9 +1061,7 @@ class _TelegramLoginScreenState extends State<_TelegramLoginScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _AnimatedEarthBackdrop extends StatefulWidget {
|
class _AnimatedEarthBackdrop extends StatefulWidget {
|
||||||
const _AnimatedEarthBackdrop({this.compact = false});
|
const _AnimatedEarthBackdrop();
|
||||||
|
|
||||||
final bool compact;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<_AnimatedEarthBackdrop> createState() => _AnimatedEarthBackdropState();
|
State<_AnimatedEarthBackdrop> createState() => _AnimatedEarthBackdropState();
|
||||||
@@ -1080,7 +1096,6 @@ class _AnimatedEarthBackdropState extends State<_AnimatedEarthBackdrop>
|
|||||||
painter: _EarthBackdropPainter(
|
painter: _EarthBackdropPainter(
|
||||||
progress: _controller.value,
|
progress: _controller.value,
|
||||||
tokens: context.mapflowTokens,
|
tokens: context.mapflowTokens,
|
||||||
compact: widget.compact,
|
|
||||||
),
|
),
|
||||||
child: const SizedBox.expand(),
|
child: const SizedBox.expand(),
|
||||||
);
|
);
|
||||||
@@ -1091,15 +1106,10 @@ class _AnimatedEarthBackdropState extends State<_AnimatedEarthBackdrop>
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _EarthBackdropPainter extends CustomPainter {
|
class _EarthBackdropPainter extends CustomPainter {
|
||||||
const _EarthBackdropPainter({
|
const _EarthBackdropPainter({required this.progress, required this.tokens});
|
||||||
required this.progress,
|
|
||||||
required this.tokens,
|
|
||||||
required this.compact,
|
|
||||||
});
|
|
||||||
|
|
||||||
final double progress;
|
final double progress;
|
||||||
final MapflowThemeTokens tokens;
|
final MapflowThemeTokens tokens;
|
||||||
final bool compact;
|
|
||||||
|
|
||||||
static const _landMasses = <({double lon, double lat, double sx, double sy})>[
|
static const _landMasses = <({double lon, double lat, double sx, double sy})>[
|
||||||
(lon: -106, lat: 42, sx: 0.30, sy: 0.18),
|
(lon: -106, lat: 42, sx: 0.30, sy: 0.18),
|
||||||
@@ -1432,78 +1442,58 @@ class _EarthBackdropPainter extends CustomPainter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Offset _earthCenter(Size size) {
|
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);
|
return Offset(size.width * 0.50, size.height * 0.40);
|
||||||
}
|
}
|
||||||
|
|
||||||
double _earthRadius(Size size) {
|
double _earthRadius(Size size) {
|
||||||
final base = math.min(size.width, size.height);
|
final base = math.min(size.width, size.height);
|
||||||
final radius = base * (compact ? 0.18 : 0.31);
|
final radius = base * 0.31;
|
||||||
return radius.clamp(116.0, compact ? 180.0 : 310.0);
|
return radius.clamp(116.0, 310.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
double _radians(double degrees) => degrees * math.pi / 180;
|
double _radians(double degrees) => degrees * math.pi / 180;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool shouldRepaint(covariant _EarthBackdropPainter oldDelegate) {
|
bool shouldRepaint(covariant _EarthBackdropPainter oldDelegate) {
|
||||||
return oldDelegate.progress != progress ||
|
return oldDelegate.progress != progress || oldDelegate.tokens != tokens;
|
||||||
oldDelegate.tokens != tokens ||
|
|
||||||
oldDelegate.compact != compact;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MapLoading extends StatelessWidget {
|
class _MapLoading extends StatelessWidget {
|
||||||
const _MapLoading();
|
const _MapLoading();
|
||||||
|
|
||||||
|
static const _iconPath = 'icons/mapflow-signal-spoon.svg';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final tokens = context.mapflowTokens;
|
final tokens = context.mapflowTokens;
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: tokens.darkSurface,
|
backgroundColor: tokens.darkSurface,
|
||||||
body: Stack(
|
body: Center(
|
||||||
children: [
|
child: SizedBox.square(
|
||||||
const Positioned.fill(child: _AnimatedEarthBackdrop(compact: true)),
|
dimension: 112,
|
||||||
Center(
|
child: Stack(
|
||||||
child: Column(
|
alignment: Alignment.center,
|
||||||
mainAxisSize: MainAxisSize.min,
|
children: [
|
||||||
children: [
|
SizedBox.square(
|
||||||
ClipRRect(
|
dimension: 96,
|
||||||
borderRadius: BorderRadius.circular(tokens.panelRadius),
|
child: CircularProgressIndicator(
|
||||||
child: Image.network(
|
strokeWidth: 3,
|
||||||
'icons/Icon-192.png',
|
color: tokens.voiceAccent,
|
||||||
width: 64,
|
backgroundColor: tokens.onDark.withValues(alpha: 0.1),
|
||||||
height: 64,
|
|
||||||
errorBuilder: (_, _, _) => Icon(
|
|
||||||
Icons.map_outlined,
|
|
||||||
color: tokens.voiceAccent,
|
|
||||||
size: 56,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 18),
|
),
|
||||||
Text(
|
ClipOval(
|
||||||
'MapFlow',
|
child: SvgPicture.network(
|
||||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
_iconPath,
|
||||||
color: tokens.onDark,
|
width: 72,
|
||||||
fontWeight: FontWeight.w900,
|
height: 72,
|
||||||
letterSpacing: 0,
|
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(
|
body: Stack(
|
||||||
children: [
|
children: [
|
||||||
const _MapboxMapLayer(
|
const _MapboxMapLayer(
|
||||||
initialCenter: LatLng(16.0544, 108.2022),
|
|
||||||
focusCenter: LatLng(16.0544, 108.2022),
|
focusCenter: LatLng(16.0544, 108.2022),
|
||||||
places: [],
|
places: [],
|
||||||
selectedPlaceId: null,
|
selectedPlaceId: null,
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 192 192" role="img" aria-label="MapFlow">
|
||||||
|
<defs>
|
||||||
|
<radialGradient id="bg" cx="35%" cy="25%" r="85%">
|
||||||
|
<stop offset="0%" stop-color="#4DF2FF"/>
|
||||||
|
<stop offset="42%" stop-color="#8E4DFF"/>
|
||||||
|
<stop offset="100%" stop-color="#090411"/>
|
||||||
|
</radialGradient>
|
||||||
|
<linearGradient id="spoon" x1="62" y1="42" x2="126" y2="154">
|
||||||
|
<stop offset="0%" stop-color="#FFFFFF"/>
|
||||||
|
<stop offset="48%" stop-color="#D9FBFF"/>
|
||||||
|
<stop offset="100%" stop-color="#FF4FA3"/>
|
||||||
|
</linearGradient>
|
||||||
|
<filter id="glow" x="-50%" y="-50%" width="200%" height="200%">
|
||||||
|
<feGaussianBlur stdDeviation="5" result="blur"/>
|
||||||
|
<feColorMatrix in="blur" values="0 0 0 0 1 0 0 0 0 0.18 0 0 0 0 0.52 0 0 0 0.9 0"/>
|
||||||
|
<feMerge>
|
||||||
|
<feMergeNode/>
|
||||||
|
<feMergeNode in="SourceGraphic"/>
|
||||||
|
</feMerge>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
<rect width="192" height="192" rx="48" fill="url(#bg)"/>
|
||||||
|
<path d="M45 137c26 18 76 18 102 0" fill="none" stroke="#4DF2FF" stroke-width="7" stroke-linecap="round" opacity=".32"/>
|
||||||
|
<path d="M36 113c36 27 84 27 120 0" fill="none" stroke="#4DF2FF" stroke-width="6" stroke-linecap="round" opacity=".42"/>
|
||||||
|
<path d="M57 82c24-23 54-23 78 0" fill="none" stroke="#FFFFFF" stroke-width="5" stroke-linecap="round" opacity=".44"/>
|
||||||
|
<path d="M74 68c13-11 31-11 44 0" fill="none" stroke="#FFFFFF" stroke-width="4" stroke-linecap="round" opacity=".64"/>
|
||||||
|
<g filter="url(#glow)">
|
||||||
|
<ellipse cx="96" cy="50" rx="18" ry="24" fill="url(#spoon)" transform="rotate(-18 96 50)"/>
|
||||||
|
<path d="M95 70c13 19 19 39 18 64-.1 8-5.3 13.7-12.2 13.7S89 142 88.7 134c-.7-25 3.9-45.2 16.6-65.5" fill="url(#spoon)"/>
|
||||||
|
<circle cx="96" cy="92" r="8" fill="#090411"/>
|
||||||
|
<circle cx="96" cy="92" r="3.5" fill="#4DF2FF"/>
|
||||||
|
</g>
|
||||||
|
<g fill="none" stroke="#FFFFFF" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" opacity=".84">
|
||||||
|
<path d="M77 104l-18 12 19 15"/>
|
||||||
|
<path d="M115 104l18 12-19 15"/>
|
||||||
|
<path d="M78 104l18-12 19 12"/>
|
||||||
|
<circle cx="59" cy="116" r="5" fill="#FFFFFF"/>
|
||||||
|
<circle cx="133" cy="116" r="5" fill="#FFFFFF"/>
|
||||||
|
<circle cx="96" cy="92" r="5" fill="#FFFFFF"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.1 KiB |
+17
-3
@@ -27,6 +27,21 @@
|
|||||||
color: #F8EEFF !important;
|
color: #F8EEFF !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#sw-loading .sw-logo-container {
|
||||||
|
margin-bottom: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sw-loading .sw-logo-image {
|
||||||
|
border-radius: 50% !important;
|
||||||
|
box-shadow: 0 0 32px rgba(255, 45, 117, 0.34) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sw-loading .sw-title,
|
||||||
|
#sw-loading .sw-status,
|
||||||
|
#sw-loading .sw-percentage {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
background: #05030B;
|
background: #05030B;
|
||||||
@@ -95,11 +110,10 @@
|
|||||||
data-sw-bootstrap
|
data-sw-bootstrap
|
||||||
src="bootstrap.js"
|
src="bootstrap.js"
|
||||||
data-config='{
|
data-config='{
|
||||||
"logo": "icons/Icon-192.png?v={{sw_version}}",
|
"logo": "icons/mapflow-signal-spoon.svg?v={{sw_version}}",
|
||||||
"title": "MapFlow",
|
|
||||||
"theme": "dark",
|
"theme": "dark",
|
||||||
"color": "#FF2D75",
|
"color": "#FF2D75",
|
||||||
"showPercentage": true
|
"showPercentage": false
|
||||||
}'
|
}'
|
||||||
></script>
|
></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
Reference in New Issue
Block a user