import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:lucide_icons_flutter/lucide_icons.dart'; import '../core/theme.dart'; import '../state/session_controller.dart'; /// Brand wordmark. Drawn rather than shipped as an asset so it tints cleanly /// on either ground. class KrowWordmark extends StatelessWidget { const KrowWordmark({super.key, this.size = 34, this.color}); final double size; final Color? color; @override Widget build(BuildContext context) { return Text( 'KROW', style: TextStyle( fontSize: size, fontWeight: FontWeight.w800, letterSpacing: size * 0.06, color: color ?? KrowPalette.yellow, ), ); } } /// Splash. Routes on to onboarding, sign-in or the app depending on how far /// the worker got last time. class SplashScreen extends ConsumerStatefulWidget { const SplashScreen({super.key}); @override ConsumerState createState() => _SplashScreenState(); } class _SplashScreenState extends ConsumerState { Timer? _timer; @override void initState() { super.initState(); _timer = Timer(const Duration(milliseconds: 1400), _go); } @override void dispose() { _timer?.cancel(); super.dispose(); } void _go() { if (!mounted) return; final s = ref.read(sessionProvider); if (!s.onboardingSeen) { context.go('/onboarding'); } else if (!s.signedIn) { context.go('/welcome'); } else if (!s.permissionsDone) { context.go('/permissions'); } else { context.go('/today'); } } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: KrowPalette.blue, body: Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ const KrowWordmark(size: 46), const SizedBox(height: 22), Text( 'WORK · HIRE · ELEVATE', style: TextStyle( fontSize: 10.5, letterSpacing: 3, fontWeight: FontWeight.w600, color: Colors.white.withValues(alpha: 0.6), ), ), const SizedBox(height: 44), SizedBox( width: 140, child: ClipRRect( borderRadius: BorderRadius.circular(2), child: LinearProgressIndicator( minHeight: 3, backgroundColor: Colors.white24, valueColor: const AlwaysStoppedAnimation(KrowPalette.yellow), ), ), ), ], ), ), ); } } class _Slide { const _Slide(this.eyebrow, this.title, this.body, this.icon); final String eyebrow; final String title; final String body; final IconData icon; } const _slides = [ _Slide( 'Find work', 'Shifts that fit around your week.', 'Browse open hospitality shifts near you, filtered to the hours you said ' 'you can work.', LucideIcons.search, ), _Slide( 'Know your pay', 'See the money before you say yes.', 'Every shift shows the hourly rate and the total. No surprises on the ' 'payslip.', LucideIcons.banknote, ), _Slide( 'Get paid', 'Clock in on site. Paid on schedule.', 'Tap in when you arrive, tap out when you leave. Timecards submit ' 'themselves.', LucideIcons.clock, ), ]; class OnboardingScreen extends ConsumerStatefulWidget { const OnboardingScreen({super.key}); @override ConsumerState createState() => _OnboardingScreenState(); } class _OnboardingScreenState extends ConsumerState { final _controller = PageController(); int _page = 0; @override void dispose() { _controller.dispose(); super.dispose(); } void _finish() { ref.read(sessionProvider.notifier).completeOnboarding(); context.go('/welcome'); } @override Widget build(BuildContext context) { final isLast = _page == _slides.length - 1; return Scaffold( backgroundColor: KrowPalette.blue, body: Column( children: [ SafeArea( bottom: false, child: Padding( padding: const EdgeInsets.fromLTRB(22, 8, 12, 0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const KrowWordmark(size: 20), TextButton( onPressed: _finish, style: TextButton.styleFrom( foregroundColor: Colors.white70, ), child: const Text('Skip'), ), ], ), ), ), Expanded( child: PageView.builder( controller: _controller, itemCount: _slides.length, onPageChanged: (i) => setState(() => _page = i), itemBuilder: (context, i) { final slide = _slides[i]; return Center( child: Container( width: 190, height: 190, decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.10), shape: BoxShape.circle, ), child: Icon( slide.icon, size: 76, color: KrowPalette.yellow, ), ), ); }, ), ), Container( width: double.infinity, decoration: const BoxDecoration( color: KrowPalette.sage, borderRadius: BorderRadius.vertical(top: Radius.circular(24)), ), child: SafeArea( top: false, child: Padding( padding: const EdgeInsets.fromLTRB(24, 26, 24, 20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text( _slides[_page].eyebrow.toUpperCase(), style: const TextStyle( fontSize: 10.5, fontWeight: FontWeight.w600, letterSpacing: 1.6, color: Color(0xFF949FA5), ), ), const SizedBox(height: 10), Text( _slides[_page].title, style: const TextStyle( fontSize: 29, fontWeight: FontWeight.w700, height: 1.1, letterSpacing: -1, ), ), const SizedBox(height: 12), Text( _slides[_page].body, style: const TextStyle( fontSize: 14.5, height: 1.55, color: KrowPalette.mute, ), ), const SizedBox(height: 22), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ for (var i = 0; i < _slides.length; i++) AnimatedContainer( duration: const Duration(milliseconds: 220), margin: const EdgeInsets.symmetric(horizontal: 3), width: i == _page ? 24 : 6, height: 6, decoration: BoxDecoration( color: i == _page ? KrowPalette.blue : const Color(0xFFC6D6CC), borderRadius: BorderRadius.circular(3), ), ), ], ), const SizedBox(height: 18), FilledButton( onPressed: () { if (isLast) { _finish(); } else { _controller.nextPage( duration: const Duration(milliseconds: 260), curve: Curves.easeOut, ); } }, child: Text(isLast ? 'Create your account' : 'Next'), ), if (isLast) Center( child: TextButton( onPressed: _finish, child: const Text('I already have an account'), ), ), ], ), ), ), ), ], ), ); } } class WelcomeScreen extends StatelessWidget { const WelcomeScreen({super.key}); @override Widget build(BuildContext context) { return Scaffold( backgroundColor: KrowPalette.blue, body: Column( children: [ Expanded( child: SafeArea( bottom: false, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 30), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const KrowWordmark(size: 40), const SizedBox(height: 34), const Text( 'Work the shifts\nyou actually want.', textAlign: TextAlign.center, style: TextStyle( fontSize: 32, height: 1.1, letterSpacing: -1.1, fontWeight: FontWeight.w700, color: Colors.white, ), ), const SizedBox(height: 14), Text( 'Hospitality work in your city, priced up front and paid ' 'on a fixed schedule.', textAlign: TextAlign.center, style: TextStyle( fontSize: 14.5, height: 1.55, color: Colors.white.withValues(alpha: 0.64), ), ), const SizedBox(height: 34), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ _WelcomeStat('4,200', 'Shifts this month'), Container( width: 1, height: 38, margin: const EdgeInsets.symmetric(horizontal: 26), color: Colors.white24, ), _WelcomeStat(r'$21+', 'Average rate'), ], ), ], ), ), ), ), Container( width: double.infinity, decoration: const BoxDecoration( color: KrowPalette.sage, borderRadius: BorderRadius.vertical(top: Radius.circular(24)), ), child: SafeArea( top: false, child: Padding( padding: const EdgeInsets.fromLTRB(22, 24, 22, 16), child: Column( mainAxisSize: MainAxisSize.min, children: [ FilledButton( onPressed: () => context.push('/phone'), child: const Text('Continue with mobile'), ), const SizedBox(height: 10), OutlinedButton( onPressed: () => ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text( 'Invite codes open in the next release', ), ), ), child: const Text('I have an invite code'), ), const SizedBox(height: 12), const Text( 'By continuing you accept the Worker Terms.', style: TextStyle( fontSize: 11.5, color: Color(0xFF949FA5), ), ), ], ), ), ), ), ], ), ); } } class _WelcomeStat extends StatelessWidget { const _WelcomeStat(this.value, this.caption); final String value; final String caption; @override Widget build(BuildContext context) { return Column( children: [ Text( value, style: const TextStyle( fontSize: 22, fontWeight: FontWeight.w700, letterSpacing: -0.8, color: KrowPalette.yellow, ), ), const SizedBox(height: 3), Text( caption.toUpperCase(), style: TextStyle( fontSize: 9.5, letterSpacing: 1.1, fontWeight: FontWeight.w600, color: Colors.white.withValues(alpha: 0.55), ), ), ], ); } }