Files
please-pay-me/mobile/lib/features/splash/splash_screen.dart
T
2026-09-21 04:06:43 +03:00

130 lines
4.0 KiB
Dart

import 'package:flutter/cupertino.dart';
import 'package:please_pay_me/core/branding/app_brand.dart';
import 'package:please_pay_me/theme/theme.dart';
import 'package:please_pay_me/ui/ui.dart';
/// Branded launch screen. Native Android/iOS splash uses the same background
/// and mark so the first Flutter frame does not flash.
class SplashScreen extends StatefulWidget {
const SplashScreen({super.key});
@override
State<SplashScreen> createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> with SingleTickerProviderStateMixin {
late final AnimationController _controller;
late final Animation<double> _mark;
late final Animation<double> _copy;
late final Animation<double> _spinner;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 720),
)..forward();
_mark = CurvedAnimation(
parent: _controller,
curve: const Interval(0, 0.55, curve: Curves.easeOutCubic),
);
_copy = CurvedAnimation(
parent: _controller,
curve: const Interval(0.28, 0.85, curve: Curves.easeOut),
);
_spinner = CurvedAnimation(
parent: _controller,
curve: const Interval(0.55, 1, curve: Curves.easeOut),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final dark = CupertinoTheme.of(context).brightness == Brightness.dark;
return CupertinoPageScaffold(
backgroundColor: AppColors.of(context, AppColors.groupedBackground),
child: AnimatedBuilder(
animation: _controller,
builder: (context, _) {
return Stack(
fit: StackFit.expand,
children: [
_Wash(dark: dark),
SafeArea(
child: Column(
children: [
const Spacer(flex: 3),
Opacity(
opacity: _mark.value,
child: Transform.scale(
scale: 0.86 + (0.14 * _mark.value),
child: const AppBrandMark(),
),
),
const SizedBox(height: AppSpacing.s5),
Opacity(
opacity: _copy.value,
child: Transform.translate(
offset: Offset(0, 10 * (1 - _copy.value)),
child: const Column(
children: [
AppText.largeTitle(AppBrand.name, textAlign: TextAlign.center),
SizedBox(height: AppSpacing.s2),
AppText.subhead(
'Бюджет от зарплаты до зарплаты',
color: AppColors.secondaryLabel,
textAlign: TextAlign.center,
),
],
),
),
),
const Spacer(flex: 4),
Opacity(
opacity: _spinner.value,
child: const Padding(
padding: EdgeInsets.only(bottom: AppSpacing.s7),
child: AppSpinner(),
),
),
],
),
),
],
);
},
),
);
}
}
class _Wash extends StatelessWidget {
const _Wash({required this.dark});
final bool dark;
@override
Widget build(BuildContext context) {
final accent = AppColors.of(context, AppColors.accent).withValues(alpha: dark ? 0.14 : 0.1);
return IgnorePointer(
child: DecoratedBox(
decoration: BoxDecoration(
gradient: RadialGradient(
center: const Alignment(0, -0.18),
radius: 0.85,
colors: [accent, const Color(0x00000000)],
),
),
),
);
}
}