import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:lucide_icons_flutter/lucide_icons.dart'; import '../core/theme.dart'; import '../models/compliance.dart'; import '../state/profile_controller.dart'; import '../state/seed.dart'; import '../widgets/common.dart'; /// Only called if something happens during a shift. Managers never see these. class EmergencyContactsScreen extends ConsumerWidget { const EmergencyContactsScreen({super.key}); Future _edit( BuildContext context, WidgetRef ref, { EmergencyContact? existing, int? index, }) async { final name = TextEditingController(text: existing?.name ?? ''); final phone = TextEditingController(text: existing?.phone ?? ''); var relationship = existing?.relationship ?? 'Spouse'; const options = ['Spouse', 'Parent', 'Sibling', 'Child', 'Friend', 'Other']; final saved = await showModalBottomSheet( context: context, isScrollControlled: true, showDragHandle: true, builder: (context) => Padding( padding: EdgeInsets.only( left: 20, right: 20, bottom: MediaQuery.of(context).viewInsets.bottom + 20, ), child: StatefulBuilder( builder: (context, setSheetState) => Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text( existing == null ? 'Add a contact' : 'Edit contact', style: const TextStyle( fontSize: 20, fontWeight: FontWeight.w700, ), ), const SizedBox(height: 16), TextField( controller: name, textCapitalization: TextCapitalization.words, decoration: const InputDecoration( labelText: 'Full name', prefixIcon: Icon(LucideIcons.user, size: 18), ), ), const SizedBox(height: 12), TextField( controller: phone, keyboardType: TextInputType.phone, inputFormatters: [ FilteringTextInputFormatter.allow(RegExp(r'[0-9 +]')), ], decoration: const InputDecoration( labelText: 'Mobile', prefixIcon: Icon(LucideIcons.phone, size: 18), ), ), const SizedBox(height: 12), DropdownButtonFormField( initialValue: relationship, decoration: const InputDecoration(labelText: 'Relationship'), items: [ for (final o in options) DropdownMenuItem(value: o, child: Text(o)), ], onChanged: (v) => setSheetState(() => relationship = v ?? relationship), ), const SizedBox(height: 20), FilledButton( onPressed: () => Navigator.pop(context, true), child: const Text('Save contact'), ), const SizedBox(height: 8), ], ), ), ), ); if (saved != true) return; if (name.text.trim().isEmpty || phone.text.trim().isEmpty) return; ref .read(profileProvider.notifier) .saveContact( EmergencyContact( name: name.text.trim(), phone: phone.text.trim(), relationship: relationship, ), index: index, ); } @override Widget build(BuildContext context, WidgetRef ref) { final contacts = ref.watch(profileProvider).contacts; return Scaffold( appBar: AppBar(title: const Text('Emergency contacts')), body: ListView( padding: const EdgeInsets.fromLTRB(20, 8, 20, 28), children: [ const Text( 'We only call these people if something happens to you during a ' 'shift. Managers never see them.', style: TextStyle( fontSize: 13.5, height: 1.55, color: KrowPalette.mute, ), ), const SectionLabel('Your contacts'), if (contacts.isEmpty) const EmptyState( icon: LucideIcons.phone, title: 'No contact yet', message: 'Add at least one before your first shift.', ) else for (var i = 0; i < contacts.length; i++) Padding( padding: const EdgeInsets.only(bottom: 10), child: KrowCard( onTap: () => _edit(context, ref, existing: contacts[i], index: i), padding: const EdgeInsets.all(14), child: Row( children: [ Container( width: 42, height: 42, decoration: BoxDecoration( color: KrowPalette.blueTint, borderRadius: BorderRadius.circular(12), ), child: const Icon( LucideIcons.user, size: 19, color: KrowPalette.blue, ), ), const SizedBox(width: 13), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( contacts[i].name, style: const TextStyle( fontSize: 15.5, fontWeight: FontWeight.w700, letterSpacing: -0.3, ), ), const SizedBox(height: 2), Text( '${contacts[i].relationship} · ' '${contacts[i].phone}', style: const TextStyle( fontSize: 12.5, color: KrowPalette.mute, ), ), ], ), ), IconButton( onPressed: () => ref.read(profileProvider.notifier).removeContact(i), icon: const Icon(LucideIcons.trash2, size: 18), color: const Color(0xFF949FA5), ), ], ), ), ), const SizedBox(height: 12), OutlinedButton.icon( onPressed: () => _edit(context, ref), icon: const Icon(LucideIcons.plus, size: 18), label: const Text('Add a contact'), ), ], ), ); } } /// More picks mean more shifts offered. class SkillsScreen extends ConsumerStatefulWidget { const SkillsScreen({super.key}); @override ConsumerState createState() => _SkillsScreenState(); } class _SkillsScreenState extends ConsumerState { final _custom = TextEditingController(); @override void dispose() { _custom.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final profile = ref.watch(profileProvider); final controller = ref.read(profileProvider.notifier); final extras = profile.skills.where((s) => !Seed.skills.contains(s)); return Scaffold( appBar: AppBar( title: const Text('Experience & skills'), actions: [ Padding( padding: const EdgeInsets.only(right: 16), child: Center( child: StatusChip( '${profile.settings.length + profile.skills.length} selected', tone: ChipTone.yellow, ), ), ), ], ), body: ListView( padding: const EdgeInsets.fromLTRB(20, 8, 20, 28), children: [ const Text( "Where you've worked", style: TextStyle( fontSize: 24, fontWeight: FontWeight.w700, letterSpacing: -0.8, ), ), const SizedBox(height: 8), const Text( 'Pick every setting you have worked in. More picks mean more ' 'shifts offered.', style: TextStyle( fontSize: 13.5, height: 1.55, color: KrowPalette.mute, ), ), const SizedBox(height: 18), Wrap( spacing: 7, runSpacing: 7, children: [ for (final s in Seed.workSettings) _Tag( label: s, selected: profile.settings.contains(s), onTap: () => controller.toggleSetting(s), ), ], ), const SectionLabel('Skills and trades'), Wrap( spacing: 7, runSpacing: 7, children: [ for (final s in Seed.skills) _Tag( label: s, selected: profile.skills.contains(s), onTap: () => controller.toggleSkill(s), ), ], ), const SectionLabel('Anything else you do'), Row( children: [ Expanded( child: TextField( controller: _custom, textInputAction: TextInputAction.done, onSubmitted: (v) { controller.addSkill(v); _custom.clear(); }, decoration: const InputDecoration( hintText: 'Type a skill...', ), ), ), const SizedBox(width: 9), SizedBox( width: 52, height: 52, child: FilledButton( onPressed: () { controller.addSkill(_custom.text); _custom.clear(); }, style: FilledButton.styleFrom( padding: EdgeInsets.zero, minimumSize: const Size(52, 52), ), child: const Icon(LucideIcons.plus, size: 20), ), ), ], ), if (extras.isNotEmpty) ...[ const SizedBox(height: 12), Wrap( spacing: 7, runSpacing: 7, children: [ for (final s in extras) Chip( label: Text(s), backgroundColor: KrowPalette.yellow, side: BorderSide.none, labelStyle: const TextStyle( fontSize: 12.5, fontWeight: FontWeight.w600, color: KrowPalette.ink, ), deleteIcon: const Icon(LucideIcons.x, size: 14), onDeleted: () => controller.toggleSkill(s), ), ], ), ], const SizedBox(height: 24), FilledButton( onPressed: () { ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text('Skills saved'))); Navigator.of(context).pop(); }, child: const Text('Save skills'), ), ], ), ); } } class _Tag extends StatelessWidget { const _Tag({ required this.label, required this.selected, required this.onTap, }); final String label; final bool selected; final VoidCallback onTap; @override Widget build(BuildContext context) { return FilterChip( label: Text(label), selected: selected, showCheckmark: false, onSelected: (_) => onTap(), selectedColor: KrowPalette.blue, backgroundColor: Colors.white, side: BorderSide(color: selected ? KrowPalette.blue : KrowPalette.line), labelStyle: TextStyle( fontSize: 12.5, fontWeight: FontWeight.w500, color: selected ? Colors.white : KrowPalette.mute, ), ); } } /// Where your pay lands. Needed before the first pay date. class DirectDepositScreen extends ConsumerStatefulWidget { const DirectDepositScreen({super.key}); @override ConsumerState createState() => _DirectDepositScreenState(); } class _DirectDepositScreenState extends ConsumerState { final _bank = TextEditingController(); final _routing = TextEditingController(); final _account = TextEditingController(); String _type = 'Chequing'; bool _editing = false; @override void dispose() { _bank.dispose(); _routing.dispose(); _account.dispose(); super.dispose(); } void _save() { if (_bank.text.trim().isEmpty || _routing.text.length != 9 || _account.text.length < 4) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Check the bank name, 9-digit routing and account'), ), ); return; } ref .read(profileProvider.notifier) .saveBank( BankAccount( bankName: _bank.text.trim(), routing: _routing.text, accountLast4: _account.text.substring(_account.text.length - 4), accountType: _type, ), ); setState(() => _editing = false); ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text('Account saved'))); } @override Widget build(BuildContext context) { final bank = ref.watch(profileProvider).bank; return Scaffold( appBar: AppBar(title: const Text('Direct deposit')), body: ListView( padding: const EdgeInsets.fromLTRB(20, 8, 20, 28), children: [ const Text( 'Where your pay lands. You need this before the first pay date.', style: TextStyle( fontSize: 13.5, height: 1.55, color: KrowPalette.mute, ), ), const SizedBox(height: 18), KrowCard( padding: const EdgeInsets.all(15), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( width: 36, height: 36, decoration: BoxDecoration( color: KrowPalette.blueTint, borderRadius: BorderRadius.circular(10), ), child: const Icon( LucideIcons.shield, size: 17, color: KrowPalette.blue, ), ), const SizedBox(width: 12), const Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Bank-grade encryption', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, ), ), SizedBox(height: 3), Text( 'Account numbers are tokenised. KROW staff never see ' 'them, and neither do the businesses you work for.', style: TextStyle( fontSize: 12, height: 1.45, color: KrowPalette.mute, ), ), ], ), ), ], ), ), const SectionLabel('Your account'), if (bank != null && !_editing) KrowCard( padding: const EdgeInsets.symmetric(horizontal: 16), child: Column( children: [ DetailRow('Bank', bank.bankName), const Divider(height: 1), DetailRow('Type', bank.accountType), const Divider(height: 1), DetailRow('Routing', bank.routing), const Divider(height: 1), DetailRow('Account', '····${bank.accountLast4}'), ], ), ) else if (!_editing) EmptyState( icon: LucideIcons.landmark, title: 'No account added', message: 'Add one before the next pay date to get paid on time.', action: FilledButton.icon( onPressed: () => setState(() => _editing = true), icon: const Icon(LucideIcons.plus, size: 18), label: const Text('Add an account'), ), ) else Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ TextField( controller: _bank, textCapitalization: TextCapitalization.words, decoration: const InputDecoration(labelText: 'Bank name'), ), const SizedBox(height: 12), TextField( controller: _routing, keyboardType: TextInputType.number, inputFormatters: [ FilteringTextInputFormatter.digitsOnly, LengthLimitingTextInputFormatter(9), ], decoration: const InputDecoration( labelText: 'Routing number', helperText: 'Nine digits, on the bottom of a cheque', ), ), const SizedBox(height: 12), TextField( controller: _account, keyboardType: TextInputType.number, inputFormatters: [ FilteringTextInputFormatter.digitsOnly, LengthLimitingTextInputFormatter(17), ], decoration: const InputDecoration( labelText: 'Account number', helperText: 'Chequing or savings, in your own name', ), ), const SizedBox(height: 12), DropdownButtonFormField( initialValue: _type, decoration: const InputDecoration(labelText: 'Account type'), items: const [ DropdownMenuItem( value: 'Chequing', child: Text('Chequing'), ), DropdownMenuItem(value: 'Savings', child: Text('Savings')), ], onChanged: (v) => setState(() => _type = v ?? _type), ), ], ), const SizedBox(height: 22), if (_editing) Row( children: [ Expanded( child: OutlinedButton( onPressed: () => setState(() => _editing = false), child: const Text('Cancel'), ), ), const SizedBox(width: 10), Expanded( child: FilledButton( onPressed: _save, child: const Text('Save account'), ), ), ], ) else if (bank != null) OutlinedButton( onPressed: () => setState(() => _editing = true), child: const Text('Replace account'), ), ], ), ); } } /// Policies and balances, per staffing vendor. class BenefitsScreen extends ConsumerWidget { const BenefitsScreen({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { // Sick-leave accrual starts after 30 days worked. const daysWorked = 12; const threshold = 30; return Scaffold( appBar: AppBar( title: const Text('Your benefits'), actions: const [ Padding( padding: EdgeInsets.only(right: 16), child: Center( child: StatusChip('Legendary Events', tone: ChipTone.yellow), ), ), ], ), body: ListView( padding: const EdgeInsets.fromLTRB(20, 8, 20, 28), children: [ const Text( 'Policies and balances, per staffing vendor.', style: TextStyle(fontSize: 13.5, color: KrowPalette.mute), ), const SectionLabel('Active policies'), Container( padding: const EdgeInsets.symmetric(horizontal: 22, vertical: 30), decoration: BoxDecoration( borderRadius: BorderRadius.circular(16), border: Border.all(color: KrowPalette.line), ), child: Column( children: [ Container( width: 56, height: 56, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), border: Border.all(color: KrowPalette.line), ), child: const Icon( LucideIcons.gift, size: 24, color: Color(0xFF949FA5), ), ), const SizedBox(height: 14), const Text( 'No policy active yet', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w700), ), const SizedBox(height: 5), const Text( 'Sick-leave accrual starts after 30 days worked. ' 'You are at day $daysWorked.', textAlign: TextAlign.center, style: TextStyle( fontSize: 13, height: 1.5, color: KrowPalette.mute, ), ), const SizedBox(height: 16), const Padding( padding: EdgeInsets.symmetric(horizontal: 24), child: KrowProgressBar( daysWorked / threshold, color: KrowPalette.yellow, ), ), const SizedBox(height: 9), const Text( '${threshold - daysWorked} days to go', style: TextStyle(fontSize: 11.5, color: Color(0xFF949FA5)), ), ], ), ), const SectionLabel('Balance history'), KrowCard( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'A read-only record of accruals, usage, adjustments and ' 'reversals.', style: TextStyle( fontSize: 12.5, height: 1.5, color: KrowPalette.mute, ), ), const SizedBox(height: 14), Container( width: double.infinity, padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: KrowPalette.sage, borderRadius: BorderRadius.circular(12), ), child: const Text( 'Nothing recorded yet', textAlign: TextAlign.center, style: TextStyle(fontSize: 13, color: KrowPalette.mute), ), ), ], ), ), const SizedBox(height: 16), const InfoBanner( 'Working for a different vendor? Switch at the top to see their ' 'policies.', ), ], ), ); } }