import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:intl/intl.dart'; import '../../core/constants.dart'; import 'provider.dart'; final _currencyFmt = NumberFormat.currency(symbol: '\$', decimalDigits: 2); class CategoriesScreen extends ConsumerStatefulWidget { const CategoriesScreen({super.key}); @override ConsumerState createState() => _CategoriesScreenState(); } class _CategoriesScreenState extends ConsumerState { int _touchedIndex = -1; @override Widget build(BuildContext context) { final data = ref.watch(categoryExpenseProvider); final total = data.values.fold(0.0, (a, b) => a + b); return Scaffold( backgroundColor: AppColors.background, body: SafeArea( child: Padding( padding: const EdgeInsets.fromLTRB(20, 24, 20, 0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Categories', style: Theme.of(context).textTheme.headlineSmall?.copyWith( fontWeight: FontWeight.w700, color: AppColors.textPrimary, ), ), Text( 'Expense breakdown', style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: AppColors.textSecondary, ), ), const SizedBox(height: 24), if (data.isEmpty) const Expanded(child: _EmptyState()) else Expanded( child: ListView( children: [ _PieChartCard( data: data, total: total, touchedIndex: _touchedIndex, onTouch: (i) => setState(() => _touchedIndex = i), ), const SizedBox(height: 20), ...data.entries.map((e) => Padding( padding: const EdgeInsets.only(bottom: 10), child: _CategoryRow( category: e.key, amount: e.value, total: total, ), )), const SizedBox(height: 80), ], ), ), ], ), ), ), ); } } class _PieChartCard extends StatelessWidget { final Map data; final double total; final int touchedIndex; final ValueChanged onTouch; const _PieChartCard({ required this.data, required this.total, required this.touchedIndex, required this.onTouch, }); @override Widget build(BuildContext context) { final entries = data.entries.toList(); return Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: AppColors.surface, borderRadius: BorderRadius.circular(20), border: Border.all(color: AppColors.divider), ), child: Column( children: [ SizedBox( height: 220, child: Stack( alignment: Alignment.center, children: [ PieChart( PieChartData( pieTouchData: PieTouchData( touchCallback: (event, response) { if (!event.isInterestedForInteractions || response == null || response.touchedSection == null) { onTouch(-1); return; } onTouch(response.touchedSection!.touchedSectionIndex); }, ), sectionsSpace: 3, centerSpaceRadius: 60, sections: List.generate(entries.length, (i) { final isTouched = i == touchedIndex; final cat = entries[i].key; final val = entries[i].value; final color = AppCategories.colors[cat] ?? AppColors.accent; return PieChartSectionData( color: color, value: val, title: isTouched ? '${(val / total * 100).toStringAsFixed(1)}%' : '', radius: isTouched ? 60 : 50, titleStyle: const TextStyle( fontSize: 13, fontWeight: FontWeight.w700, color: Colors.white, ), ); }), ), ), Column( mainAxisSize: MainAxisSize.min, children: [ Text( 'Total', style: Theme.of(context).textTheme.bodySmall?.copyWith( color: AppColors.textSecondary, ), ), Text( _currencyFmt.format(total), style: Theme.of(context).textTheme.titleMedium?.copyWith( color: AppColors.textPrimary, fontWeight: FontWeight.w700, ), ), ], ), ], ), ), ], ), ); } } class _CategoryRow extends StatelessWidget { final String category; final double amount; final double total; const _CategoryRow({ required this.category, required this.amount, required this.total, }); @override Widget build(BuildContext context) { final color = AppCategories.colors[category] ?? AppColors.accent; final icon = AppCategories.icons[category] ?? Icons.category_rounded; final pct = total > 0 ? amount / total : 0.0; return Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: AppColors.surface, borderRadius: BorderRadius.circular(16), border: Border.all(color: AppColors.divider), ), child: Column( children: [ Row( children: [ Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: color.withOpacity(0.15), borderRadius: BorderRadius.circular(10), ), child: Icon(icon, color: color, size: 18), ), const SizedBox(width: 12), Expanded( child: Text( category, style: Theme.of(context).textTheme.bodyMedium?.copyWith( fontWeight: FontWeight.w600, color: AppColors.textPrimary, ), ), ), Text( _currencyFmt.format(amount), style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: AppColors.expense, fontWeight: FontWeight.w700, ), ), ], ), const SizedBox(height: 10), ClipRRect( borderRadius: BorderRadius.circular(4), child: LinearProgressIndicator( value: pct, backgroundColor: AppColors.divider, valueColor: AlwaysStoppedAnimation(color), minHeight: 6, ), ), const SizedBox(height: 4), Align( alignment: Alignment.centerRight, child: Text( '${(pct * 100).toStringAsFixed(1)}%', style: Theme.of(context).textTheme.bodySmall?.copyWith( color: AppColors.textSecondary, ), ), ), ], ), ); } } class _EmptyState extends StatelessWidget { const _EmptyState(); @override Widget build(BuildContext context) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( padding: const EdgeInsets.all(24), decoration: const BoxDecoration( color: AppColors.surface, shape: BoxShape.circle, ), child: const Icon( Icons.pie_chart_outline_rounded, size: 48, color: AppColors.textSecondary, ), ), const SizedBox(height: 16), Text( 'No expense data', style: Theme.of(context).textTheme.titleMedium?.copyWith( color: AppColors.textPrimary, fontWeight: FontWeight.w600, ), ), const SizedBox(height: 6), Text( 'Add some expenses to see the breakdown', style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: AppColors.textSecondary, ), ), ], ), ); } }