This commit is contained in:
@@ -2,6 +2,7 @@ import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
@@ -328,6 +329,8 @@ class _MapboxMapLayer extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _MapboxMapLayerState extends State<_MapboxMapLayer> {
|
||||
static const _worldCenter = LatLng(8.0, 32.0);
|
||||
|
||||
mbx.MapboxMap? _mapboxMap;
|
||||
var _mapLoaded = false;
|
||||
var _didInitialFlyIn = false;
|
||||
@@ -351,12 +354,19 @@ class _MapboxMapLayerState extends State<_MapboxMapLayer> {
|
||||
mbx.MapWidget(
|
||||
styleUri: _mapboxStyleUri,
|
||||
viewport: mbx.CameraViewportState(
|
||||
center: _mapboxPoint(widget.initialCenter),
|
||||
zoom: _didInitialFlyIn ? 14.8 : 1.25,
|
||||
bearing: _didInitialFlyIn ? -16 : 0,
|
||||
center: _mapboxPoint(
|
||||
_didInitialFlyIn ? widget.initialCenter : _worldCenter,
|
||||
),
|
||||
zoom: _didInitialFlyIn ? 14.8 : 0.58,
|
||||
bearing: _didInitialFlyIn ? -16 : -24,
|
||||
pitch: _didInitialFlyIn ? 62 : 0,
|
||||
),
|
||||
onMapCreated: _onMapCreated,
|
||||
onStyleLoadedListener: (_) {
|
||||
if (!kIsWeb) {
|
||||
unawaited(_enableGlobeProjection());
|
||||
}
|
||||
},
|
||||
onMapLoadedListener: (_) {
|
||||
_mapLoaded = true;
|
||||
_updateCameraForFocus();
|
||||
@@ -392,6 +402,16 @@ class _MapboxMapLayerState extends State<_MapboxMapLayer> {
|
||||
_mapboxMap = mapboxMap;
|
||||
}
|
||||
|
||||
Future<void> _enableGlobeProjection() async {
|
||||
final mapboxMap = _mapboxMap;
|
||||
if (mapboxMap == null) {
|
||||
return;
|
||||
}
|
||||
await mapboxMap.style.setProjection(
|
||||
mbx.StyleProjection(name: mbx.StyleProjectionName.globe),
|
||||
);
|
||||
}
|
||||
|
||||
void _updateCameraForFocus() {
|
||||
final mapboxMap = _mapboxMap;
|
||||
final focusCenter = widget.focusCenter;
|
||||
@@ -417,7 +437,12 @@ class _MapboxMapLayerState extends State<_MapboxMapLayer> {
|
||||
}
|
||||
|
||||
_didInitialFlyIn = true;
|
||||
unawaited(mapboxMap.flyTo(camera, mbx.MapAnimationOptions(duration: 2600)));
|
||||
unawaited(
|
||||
mapboxMap.flyTo(
|
||||
camera,
|
||||
mbx.MapAnimationOptions(duration: 4200, startDelay: 350),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _projectMarkers() async {
|
||||
@@ -968,40 +993,467 @@ class _TelegramLoginScreenState extends State<_TelegramLoginScreen> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final tokens = context.mapflowTokens;
|
||||
|
||||
return Scaffold(
|
||||
body: SafeArea(
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: 320,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'MapFlow',
|
||||
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
|
||||
fontWeight: FontWeight.w900,
|
||||
letterSpacing: 0,
|
||||
),
|
||||
backgroundColor: tokens.darkSurface,
|
||||
body: Stack(
|
||||
children: [
|
||||
const Positioned.fill(child: _AnimatedEarthBackdrop()),
|
||||
SafeArea(
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: 320,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'MapFlow',
|
||||
style: Theme.of(context).textTheme.headlineMedium
|
||||
?.copyWith(
|
||||
color: tokens.onDark,
|
||||
fontWeight: FontWeight.w900,
|
||||
letterSpacing: 0,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
TelegramLoginButton(
|
||||
onPressed: _startLogin,
|
||||
loading: _loading,
|
||||
),
|
||||
if (_message.isNotEmpty) ...[
|
||||
const SizedBox(height: 14),
|
||||
Text(
|
||||
_message,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.bodyMedium?.copyWith(color: tokens.onDark),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
TelegramLoginButton(onPressed: _startLogin, loading: _loading),
|
||||
if (_message.isNotEmpty) ...[
|
||||
const SizedBox(height: 14),
|
||||
Text(
|
||||
_message,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _AnimatedEarthBackdrop extends StatefulWidget {
|
||||
const _AnimatedEarthBackdrop({this.compact = false});
|
||||
|
||||
final bool compact;
|
||||
|
||||
@override
|
||||
State<_AnimatedEarthBackdrop> createState() => _AnimatedEarthBackdropState();
|
||||
}
|
||||
|
||||
class _AnimatedEarthBackdropState extends State<_AnimatedEarthBackdrop>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late final AnimationController _controller;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(seconds: 18),
|
||||
)..repeat();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RepaintBoundary(
|
||||
child: AnimatedBuilder(
|
||||
animation: _controller,
|
||||
builder: (context, _) {
|
||||
return CustomPaint(
|
||||
painter: _EarthBackdropPainter(
|
||||
progress: _controller.value,
|
||||
tokens: context.mapflowTokens,
|
||||
compact: widget.compact,
|
||||
),
|
||||
child: const SizedBox.expand(),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _EarthBackdropPainter extends CustomPainter {
|
||||
const _EarthBackdropPainter({
|
||||
required this.progress,
|
||||
required this.tokens,
|
||||
required this.compact,
|
||||
});
|
||||
|
||||
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),
|
||||
(lon: -82, lat: 18, sx: 0.20, sy: 0.14),
|
||||
(lon: -62, lat: -14, sx: 0.22, sy: 0.25),
|
||||
(lon: 16, lat: 8, sx: 0.23, sy: 0.31),
|
||||
(lon: 46, lat: 48, sx: 0.34, sy: 0.18),
|
||||
(lon: 78, lat: 25, sx: 0.32, sy: 0.20),
|
||||
(lon: 116, lat: -23, sx: 0.19, sy: 0.13),
|
||||
(lon: 137, lat: 36, sx: 0.10, sy: 0.07),
|
||||
(lon: -42, lat: 73, sx: 0.16, sy: 0.08),
|
||||
];
|
||||
|
||||
static const _routes =
|
||||
<({double fromLon, double fromLat, double toLon, double toLat})>[
|
||||
(fromLon: -74, fromLat: 41, toLon: 2, toLat: 49),
|
||||
(fromLon: 2, fromLat: 49, toLon: 105, toLat: 16),
|
||||
(fromLon: 139, fromLat: 35, toLon: 105, toLat: 16),
|
||||
(fromLon: -122, fromLat: 37, toLon: 139, toLat: 35),
|
||||
];
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
_paintSpace(canvas, size);
|
||||
_paintOrbits(canvas, size);
|
||||
_paintEarth(canvas, size);
|
||||
}
|
||||
|
||||
void _paintSpace(Canvas canvas, Size size) {
|
||||
final rect = Offset.zero & size;
|
||||
canvas.drawRect(
|
||||
rect,
|
||||
Paint()
|
||||
..shader = LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [
|
||||
tokens.darkSurface,
|
||||
const Color(0xFF07151B),
|
||||
const Color(0xFF150918),
|
||||
tokens.darkSurface,
|
||||
],
|
||||
).createShader(rect),
|
||||
);
|
||||
|
||||
final glowCenter = Offset(size.width * 0.77, size.height * 0.18);
|
||||
canvas.drawCircle(
|
||||
glowCenter,
|
||||
math.max(size.width, size.height) * 0.48,
|
||||
Paint()
|
||||
..shader =
|
||||
RadialGradient(
|
||||
colors: [
|
||||
const Color(0xFFFFB86B).withValues(alpha: 0.20),
|
||||
tokens.voiceAccent.withValues(alpha: 0.08),
|
||||
Colors.transparent,
|
||||
],
|
||||
stops: const [0, 0.34, 1],
|
||||
).createShader(
|
||||
Rect.fromCircle(
|
||||
center: glowCenter,
|
||||
radius: math.max(size.width, size.height) * 0.48,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final stars = Paint()..color = Colors.white;
|
||||
for (var index = 0; index < 96; index++) {
|
||||
final xSeed = math.sin(index * 12.9898) * 43758.5453;
|
||||
final ySeed = math.sin(index * 78.233) * 24634.6345;
|
||||
final x = (xSeed - xSeed.floor()) * size.width;
|
||||
final y = (ySeed - ySeed.floor()) * size.height;
|
||||
final twinkle = 0.35 + 0.65 * math.sin(progress * math.pi * 2 + index);
|
||||
stars.color = Colors.white.withValues(alpha: 0.10 + twinkle * 0.28);
|
||||
canvas.drawCircle(Offset(x, y), 0.7 + (index % 3) * 0.35, stars);
|
||||
}
|
||||
}
|
||||
|
||||
void _paintOrbits(Canvas canvas, Size size) {
|
||||
final center = _earthCenter(size);
|
||||
final radius = _earthRadius(size);
|
||||
final orbitPaint = Paint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.1
|
||||
..color = const Color(0xFF5BE7C4).withValues(alpha: 0.15);
|
||||
|
||||
for (final scale in const [1.28, 1.48, 1.72]) {
|
||||
canvas.save();
|
||||
canvas.translate(center.dx, center.dy);
|
||||
canvas.rotate(-0.46 + progress * math.pi * 0.18);
|
||||
canvas.scale(1, 0.34);
|
||||
canvas.drawCircle(Offset.zero, radius * scale, orbitPaint);
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
final satelliteAngle = progress * math.pi * 2;
|
||||
final satellite = Offset(
|
||||
center.dx + math.cos(satelliteAngle) * radius * 1.72,
|
||||
center.dy + math.sin(satelliteAngle) * radius * 0.58,
|
||||
);
|
||||
canvas.drawCircle(
|
||||
satellite,
|
||||
3.6,
|
||||
Paint()..color = const Color(0xFFFFD38A).withValues(alpha: 0.92),
|
||||
);
|
||||
}
|
||||
|
||||
void _paintEarth(Canvas canvas, Size size) {
|
||||
final center = _earthCenter(size);
|
||||
final radius = _earthRadius(size);
|
||||
final sphere = Rect.fromCircle(center: center, radius: radius);
|
||||
|
||||
canvas.drawCircle(
|
||||
center.translate(radius * 0.10, radius * 0.12),
|
||||
radius * 1.06,
|
||||
Paint()
|
||||
..shader = RadialGradient(
|
||||
colors: [Colors.black.withValues(alpha: 0.46), Colors.transparent],
|
||||
).createShader(sphere.inflate(radius * 0.18)),
|
||||
);
|
||||
|
||||
canvas.drawCircle(
|
||||
center,
|
||||
radius * 1.02,
|
||||
Paint()
|
||||
..shader = RadialGradient(
|
||||
center: const Alignment(-0.48, -0.62),
|
||||
colors: [
|
||||
const Color(0xFF8AF8FF).withValues(alpha: 0.32),
|
||||
const Color(0xFF124968).withValues(alpha: 0.72),
|
||||
Colors.transparent,
|
||||
],
|
||||
stops: const [0, 0.64, 1],
|
||||
).createShader(sphere.inflate(radius * 0.18)),
|
||||
);
|
||||
|
||||
canvas.save();
|
||||
canvas.clipPath(Path()..addOval(sphere));
|
||||
canvas.drawOval(
|
||||
sphere,
|
||||
Paint()
|
||||
..shader = RadialGradient(
|
||||
center: const Alignment(-0.42, -0.50),
|
||||
radius: 1.08,
|
||||
colors: const [
|
||||
Color(0xFF7AF6FF),
|
||||
Color(0xFF1589B0),
|
||||
Color(0xFF073456),
|
||||
Color(0xFF030A1A),
|
||||
],
|
||||
stops: [0, 0.34, 0.72, 1],
|
||||
).createShader(sphere),
|
||||
);
|
||||
|
||||
_paintGrid(canvas, center, radius);
|
||||
_paintLand(canvas, center, radius);
|
||||
_paintRoutes(canvas, center, radius);
|
||||
_paintTerminator(canvas, center, radius, sphere);
|
||||
canvas.restore();
|
||||
|
||||
canvas.drawOval(
|
||||
sphere,
|
||||
Paint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.5
|
||||
..color = const Color(0xFFB7FFF5).withValues(alpha: 0.36),
|
||||
);
|
||||
}
|
||||
|
||||
void _paintGrid(Canvas canvas, Offset center, double radius) {
|
||||
final paint = Paint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 0.8
|
||||
..color = Colors.white.withValues(alpha: 0.11);
|
||||
|
||||
for (final lat in const [-60, -30, 0, 30, 60]) {
|
||||
final y = center.dy - math.sin(_radians(lat.toDouble())) * radius * 0.72;
|
||||
final width = math.cos(_radians(lat.toDouble())) * radius;
|
||||
canvas.drawOval(
|
||||
Rect.fromCenter(
|
||||
center: Offset(center.dx, y),
|
||||
width: width * 2,
|
||||
height: width * 0.38,
|
||||
),
|
||||
paint,
|
||||
);
|
||||
}
|
||||
|
||||
for (var meridian = 0; meridian < 6; meridian++) {
|
||||
canvas.save();
|
||||
canvas.translate(center.dx, center.dy);
|
||||
canvas.rotate((meridian / 6) * math.pi + progress * math.pi * 2);
|
||||
canvas.scale(0.20, 1);
|
||||
canvas.drawCircle(Offset.zero, radius, paint);
|
||||
canvas.restore();
|
||||
}
|
||||
}
|
||||
|
||||
void _paintLand(Canvas canvas, Offset center, double radius) {
|
||||
final landPaint = Paint()
|
||||
..color = const Color(0xFF4EDC9B).withValues(alpha: 0.86);
|
||||
final highlightPaint = Paint()
|
||||
..color = const Color(0xFFFFD38A).withValues(alpha: 0.34);
|
||||
|
||||
for (final land in _landMasses) {
|
||||
final projected = _project(land.lon, land.lat, center, radius);
|
||||
if (projected == null) {
|
||||
continue;
|
||||
}
|
||||
final (:point, :depth) = projected;
|
||||
final alpha = (0.30 + depth * 0.70).clamp(0.0, 1.0);
|
||||
landPaint.color = const Color(0xFF4EDC9B).withValues(alpha: alpha * 0.88);
|
||||
highlightPaint.color = const Color(
|
||||
0xFFFFD38A,
|
||||
).withValues(alpha: alpha * 0.26);
|
||||
final landRect = Rect.fromCenter(
|
||||
center: point,
|
||||
width: radius * land.sx * (0.62 + depth * 0.58),
|
||||
height: radius * land.sy * (0.56 + depth * 0.36),
|
||||
);
|
||||
canvas.save();
|
||||
canvas.translate(point.dx, point.dy);
|
||||
canvas.rotate(math.sin(land.lon) * 0.65);
|
||||
canvas.translate(-point.dx, -point.dy);
|
||||
canvas.drawOval(landRect, landPaint);
|
||||
canvas.drawOval(landRect.deflate(radius * 0.018), highlightPaint);
|
||||
canvas.restore();
|
||||
}
|
||||
}
|
||||
|
||||
void _paintRoutes(Canvas canvas, Offset center, double radius) {
|
||||
final paint = Paint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeCap = StrokeCap.round
|
||||
..strokeWidth = 1.4
|
||||
..color = tokens.voiceAccentSoft.withValues(alpha: 0.38);
|
||||
final pulsePaint = Paint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeCap = StrokeCap.round
|
||||
..strokeWidth = 2.8
|
||||
..color = tokens.voiceAccent.withValues(alpha: 0.44);
|
||||
|
||||
for (var index = 0; index < _routes.length; index++) {
|
||||
final route = _routes[index];
|
||||
final from = _project(route.fromLon, route.fromLat, center, radius);
|
||||
final to = _project(route.toLon, route.toLat, center, radius);
|
||||
if (from == null || to == null) {
|
||||
continue;
|
||||
}
|
||||
final path = Path()
|
||||
..moveTo(from.point.dx, from.point.dy)
|
||||
..quadraticBezierTo(
|
||||
(from.point.dx + to.point.dx) / 2,
|
||||
math.min(from.point.dy, to.point.dy) - radius * (0.18 + index * 0.03),
|
||||
to.point.dx,
|
||||
to.point.dy,
|
||||
);
|
||||
canvas.drawPath(path, paint);
|
||||
final metric = path.computeMetrics().first;
|
||||
final head = ((progress + index * 0.19) % 1) * metric.length;
|
||||
final segment = metric.extractPath(
|
||||
math.max(0, head - metric.length * 0.12),
|
||||
head,
|
||||
);
|
||||
canvas.drawPath(segment, pulsePaint);
|
||||
}
|
||||
}
|
||||
|
||||
void _paintTerminator(
|
||||
Canvas canvas,
|
||||
Offset center,
|
||||
double radius,
|
||||
Rect sphere,
|
||||
) {
|
||||
canvas.drawOval(
|
||||
sphere.shift(Offset(radius * 0.22, radius * 0.06)),
|
||||
Paint()
|
||||
..shader = LinearGradient(
|
||||
begin: Alignment.centerLeft,
|
||||
end: Alignment.centerRight,
|
||||
colors: [
|
||||
Colors.transparent,
|
||||
Colors.black.withValues(alpha: 0.18),
|
||||
Colors.black.withValues(alpha: 0.68),
|
||||
],
|
||||
stops: const [0, 0.52, 1],
|
||||
).createShader(sphere),
|
||||
);
|
||||
canvas.drawCircle(
|
||||
center.translate(-radius * 0.28, -radius * 0.30),
|
||||
radius * 0.22,
|
||||
Paint()
|
||||
..shader =
|
||||
RadialGradient(
|
||||
colors: [
|
||||
Colors.white.withValues(alpha: 0.22),
|
||||
Colors.transparent,
|
||||
],
|
||||
).createShader(
|
||||
Rect.fromCircle(
|
||||
center: center.translate(-radius * 0.28, -radius * 0.30),
|
||||
radius: radius * 0.22,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
({Offset point, double depth})? _project(
|
||||
double lon,
|
||||
double lat,
|
||||
Offset center,
|
||||
double radius,
|
||||
) {
|
||||
final longitude = _radians(lon + progress * 360);
|
||||
final latitude = _radians(lat);
|
||||
final x = math.cos(latitude) * math.sin(longitude);
|
||||
final z = math.cos(latitude) * math.cos(longitude);
|
||||
if (z < -0.18) {
|
||||
return null;
|
||||
}
|
||||
final y = -math.sin(latitude) * 0.72;
|
||||
final depthScale = 0.72 + z * 0.28;
|
||||
return (
|
||||
point: Offset(
|
||||
center.dx + x * radius * depthScale,
|
||||
center.dy + y * radius * depthScale,
|
||||
),
|
||||
depth: ((z + 0.18) / 1.18).clamp(0.0, 1.0),
|
||||
);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
double _radians(double degrees) => degrees * math.pi / 180;
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _EarthBackdropPainter oldDelegate) {
|
||||
return oldDelegate.progress != progress ||
|
||||
oldDelegate.tokens != tokens ||
|
||||
oldDelegate.compact != compact;
|
||||
}
|
||||
}
|
||||
|
||||
class _MapLoading extends StatelessWidget {
|
||||
const _MapLoading();
|
||||
|
||||
@@ -1010,43 +1462,48 @@ class _MapLoading extends StatelessWidget {
|
||||
final tokens = context.mapflowTokens;
|
||||
return Scaffold(
|
||||
backgroundColor: tokens.darkSurface,
|
||||
body: 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: 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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
Text(
|
||||
'MapFlow',
|
||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||
color: tokens.onDark,
|
||||
fontWeight: FontWeight.w900,
|
||||
letterSpacing: 0,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
SizedBox(
|
||||
width: 96,
|
||||
child: LinearProgressIndicator(
|
||||
minHeight: 3,
|
||||
color: tokens.voiceAccent,
|
||||
backgroundColor: tokens.onDark.withValues(alpha: 0.12),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
Text(
|
||||
'MapFlow',
|
||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||
color: tokens.onDark,
|
||||
fontWeight: FontWeight.w900,
|
||||
letterSpacing: 0,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
SizedBox(
|
||||
width: 96,
|
||||
child: LinearProgressIndicator(
|
||||
minHeight: 3,
|
||||
color: tokens.voiceAccent,
|
||||
backgroundColor: tokens.onDark.withValues(alpha: 0.12),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user