import 'dart:async'; import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../core/auth/auth_notifier.dart'; import '../../core/auth/onboarding_intent_provider.dart'; import '../../core/theme/halo_tokens.dart'; /// S1 ยท Splash โ€” figma-bestie/project/screens/onboarding.jsx /// /// Self-driving: waits for both a 1s minimum hold AND the auth provider to /// resolve, then navigates to the appropriate destination. The router's /// redirect leaves /splash alone โ€” see [router.dart]. class SplashScreen extends ConsumerStatefulWidget { const SplashScreen({super.key}); @override ConsumerState createState() => _SplashScreenState(); } class _SplashScreenState extends ConsumerState { bool _holdElapsed = false; bool _navigated = false; @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { if (!mounted) return; // 2.5s here because the native Android launch splash covers Flutter for // ~1-1.5s on cold start. By the time the user actually sees this widget, // ~1s has already burned โ€” 2.5s leaves a comfortable ~1.2s of visible // splash before we navigate. Future.delayed(const Duration(milliseconds: 2500), () { if (!mounted) return; setState(() => _holdElapsed = true); _maybeLeave(); }); }); } void _maybeLeave() { if (_navigated || !mounted || !_holdElapsed) return; final authState = ref.read(authProvider); if (authState is AsyncLoading) return; _navigated = true; final data = authState.valueOrNull; if (data is AuthNeedsDisplayNameData) { context.go('/auth/set-name'); } else if (data is AuthForceRegisterData) { context.go('/auth/force-register'); } else if (data is AuthAuthenticatedData && ref.read(onboardingIntentProvider) == OnboardingIntent.onboarding) { context.go('/payment/entry'); } else { context.go('/home'); } } @override Widget build(BuildContext context) { ref.listen(authProvider, (_, __) => _maybeLeave()); return Scaffold( body: Container( decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, stops: [0.0, 0.6, 1.0], colors: [ HaloTokens.brandSofter, HaloTokens.bg, HaloTokens.accentSoft, ], ), ), child: Stack( children: [ const Positioned( top: 80, right: -40, child: _Orb( size: 180, color: HaloTokens.brand, opacity: 0.31, blurSigma: 20, ), ), const Positioned( bottom: 120, left: -40, child: _Orb( size: 160, color: HaloTokens.accent, opacity: 0.38, blurSigma: 24, ), ), SafeArea( child: Padding( padding: const EdgeInsets.fromLTRB(28, 40, 28, 40), child: Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( width: 96, height: 96, decoration: BoxDecoration( color: HaloTokens.brandLogoBg, borderRadius: BorderRadius.circular(24), boxShadow: HaloShadows.soft, ), clipBehavior: Clip.antiAlias, // logo.png has ~25% internal whitespace around the // glyph; scale up so the visible art reaches the edges // of the tile. The Container clip keeps it inside the // rounded square. child: Transform.scale( scale: 1.4, child: Image.asset( 'assets/icons/logo.png', fit: BoxFit.cover, ), ), ), const SizedBox(height: 28), const Text( 'HaloBestie', style: TextStyle( fontFamily: HaloTokens.fontDisplay, fontSize: 44, fontWeight: FontWeight.w700, height: 1.05, letterSpacing: -1.1, color: HaloTokens.brandDark, ), ), const SizedBox(height: 18), ConstrainedBox( constraints: const BoxConstraints(maxWidth: 280), child: const Text( 'kamu gak harus ngerasain\nini sendirian.', textAlign: TextAlign.center, style: TextStyle( fontFamily: HaloTokens.fontBody, fontSize: 15, height: 1.5, color: HaloTokens.inkSoft, ), ), ), ], ), ), ), ), ], ), ), ); } } class _Orb extends StatelessWidget { const _Orb({ required this.size, required this.color, required this.opacity, required this.blurSigma, }); final double size; final Color color; final double opacity; final double blurSigma; @override Widget build(BuildContext context) { return IgnorePointer( child: ImageFiltered( imageFilter: ImageFilter.blur(sigmaX: blurSigma, sigmaY: blurSigma), child: Container( width: size, height: size, decoration: BoxDecoration( shape: BoxShape.circle, gradient: RadialGradient( center: const Alignment(-0.4, -0.4), radius: 0.7, colors: [color.withValues(alpha: opacity), color.withValues(alpha: 0)], ), ), ), ), ); } }