187 lines
5.8 KiB
Dart

import 'dart:async';
import 'dart:math' as math;
import 'package:flutter/material.dart';
import '../../core/theme/app_colors.dart';
import '../../core/theme/app_decorations.dart';
import '../../core/theme/app_text_styles.dart';
class WalkGuideLoadingScreen extends StatefulWidget {
final String subtitle;
const WalkGuideLoadingScreen({
super.key,
required this.subtitle,
});
@override
State<WalkGuideLoadingScreen> createState() => _WalkGuideLoadingScreenState();
}
class _WalkGuideLoadingScreenState extends State<WalkGuideLoadingScreen>
with SingleTickerProviderStateMixin {
late final AnimationController _controller;
late final Animation<double> _logoScale;
bool _showName = false;
Timer? _nameTimer;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 1200),
)..repeat();
_logoScale = TweenSequence<double>([
TweenSequenceItem(
tween: Tween(begin: 1.0, end: 1.08).chain(
CurveTween(curve: Curves.easeInOut),
),
weight: 50,
),
TweenSequenceItem(
tween: Tween(begin: 1.08, end: 1.0).chain(
CurveTween(curve: Curves.easeInOut),
),
weight: 50,
),
]).animate(_controller);
_nameTimer = Timer(const Duration(milliseconds: 300), () {
if (mounted) setState(() => _showName = true);
});
}
@override
void dispose() {
_nameTimer?.cancel();
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.softBlueBg,
body: DecoratedBox(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [AppColors.softBlueBg, Colors.white],
),
),
child: SafeArea(
child: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 340),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 28),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
AnimatedBuilder(
animation: _logoScale,
child: Container(
width: 92,
height: 92,
decoration: BoxDecoration(
gradient: AppDecorations.blueGradient,
borderRadius: BorderRadius.circular(28),
boxShadow: const [
BoxShadow(
color: Color(0x334A90D9),
blurRadius: 28,
offset: Offset(0, 14),
),
],
),
child: const Icon(
Icons.navigation_rounded,
color: Colors.white,
size: 54,
),
),
builder: (context, child) => Transform.scale(
scale: _logoScale.value,
child: child,
),
),
const SizedBox(height: 24),
AnimatedOpacity(
opacity: _showName ? 1 : 0,
duration: const Duration(milliseconds: 420),
curve: Curves.easeOutCubic,
child: Column(
children: [
Text(
'WalkGuide',
textAlign: TextAlign.center,
style: AppTextStyles.heading.copyWith(
fontSize: 32,
fontWeight: FontWeight.w800,
),
),
const SizedBox(height: 8),
Text(
widget.subtitle,
textAlign: TextAlign.center,
style: AppTextStyles.body.copyWith(
color: AppColors.textMuted,
fontWeight: FontWeight.w500,
),
),
],
),
),
const SizedBox(height: 34),
_DotWave(controller: _controller),
],
),
),
),
),
),
),
);
}
}
class _DotWave extends StatelessWidget {
final AnimationController controller;
const _DotWave({required this.controller});
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: controller,
builder: (context, child) {
return Row(
mainAxisSize: MainAxisSize.min,
children: List.generate(
3,
(index) {
final phase = (controller.value + index * 0.18) % 1.0;
final y = math.sin(phase * math.pi * 2) * -5;
final opacity = 0.45 + (math.sin(phase * math.pi * 2) + 1) * 0.25;
return Transform.translate(
offset: Offset(0, y),
child: Container(
width: 9,
height: 9,
margin: const EdgeInsets.symmetric(horizontal: 5),
decoration: BoxDecoration(
color: AppColors.primaryBlue.withValues(alpha: opacity),
shape: BoxShape.circle,
),
),
);
},
),
);
},
);
}
}