import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:focusflow_shared/focusflow_shared.dart'; import 'package:go_router/go_router.dart'; import '../../../../core/theme/app_colors.dart'; import '../../../../core/widgets/streak_ring.dart'; import '../bloc/streak_bloc.dart'; /// Streak overview screen. /// /// - Active streaks as cards with [StreakRing] widget. /// - "Frozen" indicator for paused streaks. /// - Tap to see history. /// - "New Streak" FAB. /// - Grace day info shown positively. class StreaksScreen extends StatefulWidget { const StreaksScreen({super.key}); @override State createState() => _StreaksScreenState(); } class _StreaksScreenState extends State { late final StreakBloc _bloc; @override void initState() { super.initState(); _bloc = StreakBloc()..add(const StreaksLoaded()); } @override void dispose() { _bloc.close(); super.dispose(); } @override Widget build(BuildContext context) { final theme = Theme.of(context); return BlocProvider.value( value: _bloc, child: Scaffold( appBar: AppBar( leading: IconButton( icon: const Icon(Icons.arrow_back_rounded), onPressed: () => context.go('/'), ), title: const Text('Your Streaks'), ), body: BlocBuilder( builder: (context, state) { if (state is StreakLoading) { return const Center(child: CircularProgressIndicator()); } if (state is StreakError) { return Center( child: Padding( padding: const EdgeInsets.all(32), child: Column( mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.cloud_off_rounded, size: 48, color: AppColors.skipped), const SizedBox(height: 12), Text(state.message, style: theme.textTheme.bodyLarge, textAlign: TextAlign.center), const SizedBox(height: 16), FilledButton( onPressed: () => _bloc.add(const StreaksLoaded()), child: const Text('Retry'), ), ], ), ), ); } if (state is StreakLoaded) { if (state.streaks.isEmpty) { return Center( child: Padding( padding: const EdgeInsets.all(32), child: Column( mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.local_fire_department_rounded, size: 56, color: AppColors.tertiary), const SizedBox(height: 12), Text('No streaks yet', style: theme.textTheme.titleMedium), const SizedBox(height: 4), Text( 'Start a streak to build consistency — with grace days built in.', style: theme.textTheme.bodyMedium, textAlign: TextAlign.center, ), ], ), ), ); } return ListView.builder( padding: const EdgeInsets.only(top: 8, bottom: 100), itemCount: state.streaks.length, itemBuilder: (context, index) { final streak = state.streaks[index]; return _StreakCard( streak: streak, onComplete: () => _bloc.add(StreakCompleted(streak.id)), ); }, ); } return const SizedBox.shrink(); }, ), floatingActionButton: FloatingActionButton.extended( onPressed: () { // TODO: navigate to create streak screen. }, icon: const Icon(Icons.add_rounded), label: const Text('New Streak'), ), ), ); } } class _StreakCard extends StatelessWidget { final Streak streak; final VoidCallback onComplete; const _StreakCard({required this.streak, required this.onComplete}); @override Widget build(BuildContext context) { final theme = Theme.of(context); final isFrozen = streak.frozenUntil != null && streak.frozenUntil!.isAfter(DateTime.now()); final graceDaysRemaining = streak.graceDays - streak.graceUsed; return Card( child: InkWell( borderRadius: BorderRadius.circular(16), onTap: () { // TODO: navigate to streak history. }, child: Padding( padding: const EdgeInsets.all(16), child: Row( children: [ StreakRing( currentCount: streak.currentCount, graceDaysRemaining: graceDaysRemaining, totalGraceDays: streak.graceDays, isFrozen: isFrozen, size: 72, ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: Text( streak.name, style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w600, ), ), ), if (isFrozen) Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: AppColors.primaryLight.withAlpha(40), borderRadius: BorderRadius.circular(12), ), child: const Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.ac_unit_rounded, size: 14, color: AppColors.primaryLight), SizedBox(width: 4), Text('Frozen', style: TextStyle(fontSize: 12, color: AppColors.primaryLight)), ], ), ), ], ), const SizedBox(height: 4), Text( 'Current: ${streak.currentCount} days | Best: ${streak.longestCount}', style: theme.textTheme.bodySmall, ), if (graceDaysRemaining > 0 && !isFrozen) Padding( padding: const EdgeInsets.only(top: 4), child: Text( '$graceDaysRemaining grace days remaining', style: theme.textTheme.bodySmall?.copyWith( color: AppColors.primary, fontWeight: FontWeight.w500, ), ), ), ], ), ), if (!isFrozen) IconButton( icon: const Icon(Icons.check_circle_outline_rounded, color: AppColors.completed), onPressed: onComplete, tooltip: 'Complete today', ), ], ), ), ), ); } }