diff --git a/Frontend/monarch/lib/budget.dart b/Frontend/monarch/lib/budget.dart index 876ef73..b07d3b1 100644 --- a/Frontend/monarch/lib/budget.dart +++ b/Frontend/monarch/lib/budget.dart @@ -1,10 +1,8 @@ // ignore_for_file: deprecated_member_use, use_build_context_synchronously -import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:google_fonts/google_fonts.dart'; -import 'package:http/http.dart' as http; class BudgetScreen extends StatefulWidget { const BudgetScreen({super.key}); @@ -223,8 +221,19 @@ class _BudgetScreenState extends State } Widget _buildAddButton(BuildContext context) { - return GestureDetector( - onTap: _addTransaction, + return ElevatedButton( + onPressed: () { + final amount = double.tryParse(amountController.text); + if (amount != null && amount > 0) { + Navigator.pop(context, amount); // Simply return the value + } else if (amount == null || amount <= 0) { + _showCustomSnackBar( + message: 'Please enter a valid budget amount', + icon: Icons.error, + backgroundColor: Colors.red, + ); + } + }, child: Container( width: double.infinity, height: _responsiveHeight(60, context), @@ -273,7 +282,7 @@ class _BudgetScreenState extends State } void _handleKeyPress(String text) { - HapticFeedback.lightImpact(); + HapticFeedback.heavyImpact(); if (text == '⌫') { if (amountController.text.isNotEmpty) { amountController.text = amountController.text.substring( @@ -314,109 +323,6 @@ class _BudgetScreenState extends State } } - Future _addTransaction() async { - if (amountController.text.trim().isEmpty) { - _showCustomSnackBar( - message: 'Please enter an amount', - icon: Icons.warning_rounded, - backgroundColor: const Color(0xFFFF6B6B), - ); - return; - } - - showDialog( - context: context, - barrierDismissible: false, - builder: (_) => _buildLoadingDialog(), - ); - - try { - final response = await http - .post( - Uri.parse('http://192.168.1.9:3000/api/transaction/add'), - headers: {'Content-Type': 'application/json'}, - body: json.encode({ - 'amount': double.tryParse(amountController.text), - }), - ) - .timeout(const Duration(seconds: 10)); - - Navigator.of(context).pop(); - - if (response.statusCode == 200) { - _handleSuccessResponse(response); - } else { - _showError('Failed to add transaction'); - } - } catch (e) { - Navigator.of(context).pop(); - _showError('Network error occurred'); - } - } - - Widget _buildLoadingDialog() { - return Center( - child: Container( - padding: EdgeInsets.all(_responsiveWidth(24, context)), - decoration: BoxDecoration( - color: cardColor, - borderRadius: BorderRadius.circular(16), - boxShadow: [ - BoxShadow( - color: primaryColor.withOpacity(0.1), - blurRadius: 20, - offset: const Offset(0, 8), - ), - ], - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - CircularProgressIndicator( - valueColor: AlwaysStoppedAnimation(accentColor), - strokeWidth: 3, - ), - SizedBox(height: _responsiveHeight(16, context)), - Text( - 'Processing...', - style: GoogleFonts.inter( - fontSize: _responsiveText(16, context), - fontWeight: FontWeight.w500, - color: primaryColor, - ), - ), - ], - ), - ), - ); - } - - void _handleSuccessResponse(http.Response response) { - final responseData = json.decode(response.body); - final category = responseData['data']['category']; - - amountController.clear(); - setState(() => _displayAmount = ''); - - _showCustomSnackBar( - message: 'Added successfully!\nCategory: $category', - icon: Icons.check_circle_rounded, - backgroundColor: accentColor, - ); - - Future.delayed(const Duration(seconds: 1), () { - if (mounted) Navigator.pop(context); - }); - } - - void _showError(String message) { - _showCustomSnackBar( - message: message, - icon: Icons.error_rounded, - backgroundColor: const Color(0xFFFF6B6B), - ); - } - void _showCustomSnackBar({ required String message, required IconData icon, diff --git a/Frontend/monarch/lib/budget_manager.dart b/Frontend/monarch/lib/budget_manager.dart new file mode 100644 index 0000000..7b28aea --- /dev/null +++ b/Frontend/monarch/lib/budget_manager.dart @@ -0,0 +1,15 @@ +import 'package:shared_preferences/shared_preferences.dart'; + +class BudgetManager { + static const _budgetKey = 'user_budget'; + + static Future getBudget() async { + final prefs = await SharedPreferences.getInstance(); + return prefs.getDouble(_budgetKey) ?? 10000.0; // Default value + } + + static Future setBudget(double amount) async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setDouble(_budgetKey, amount); + } +} \ No newline at end of file diff --git a/Frontend/monarch/lib/homepage.dart b/Frontend/monarch/lib/homepage.dart index ca7617f..47b8d87 100644 --- a/Frontend/monarch/lib/homepage.dart +++ b/Frontend/monarch/lib/homepage.dart @@ -689,8 +689,9 @@ class _FinTrackHomePageState extends State accentColor: accentColor, primaryColor: primaryColor, cardColor: cardColor, + + floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, ), - floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, ); } } diff --git a/Frontend/monarch/lib/main.dart b/Frontend/monarch/lib/main.dart index 33a5032..001d8fa 100644 --- a/Frontend/monarch/lib/main.dart +++ b/Frontend/monarch/lib/main.dart @@ -22,7 +22,7 @@ class MyApp extends StatelessWidget { return MaterialApp( debugShowCheckedModeBanner: false, theme: ThemeData(primarySwatch: Colors.blue), - home: const BudgetScreen(), + home: const FinTrackHomePage(), ); } } diff --git a/Frontend/monarch/lib/navbar.dart b/Frontend/monarch/lib/navbar.dart index 3adf1a6..ccca636 100644 --- a/Frontend/monarch/lib/navbar.dart +++ b/Frontend/monarch/lib/navbar.dart @@ -20,7 +20,7 @@ class CustomNavBar extends StatelessWidget { required this.backgroundColor, required this.accentColor, required this.primaryColor, - required this.cardColor, + required this.cardColor, required FloatingActionButtonLocation floatingActionButtonLocation, }); // Define navigation items configuration diff --git a/Frontend/monarch/lib/static.dart b/Frontend/monarch/lib/static.dart index a4a985a..dff9a10 100644 --- a/Frontend/monarch/lib/static.dart +++ b/Frontend/monarch/lib/static.dart @@ -7,6 +7,8 @@ import 'package:fl_chart/fl_chart.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:http/http.dart' as http; import 'package:monarch/add.dart'; +import 'package:monarch/budget.dart'; +import 'package:monarch/budget_manager.dart'; import 'package:monarch/homepage.dart'; import 'package:monarch/navbar.dart'; @@ -46,7 +48,7 @@ class StatisticsState extends State with TickerProviderStateMixin { late AnimationController _animationController; late Animation _fadeAnimation; late Animation _slideAnimation; - + var budget = 10000.0; final List categories = [ 'All', 'Food', @@ -56,19 +58,28 @@ class StatisticsState extends State with TickerProviderStateMixin { 'Entertainment', 'Other', ]; - String selectedCategory = 'All'; - String _selectedPlan = 'Monthly'; - Map totalAmountPerCategory = {}; - // Modern color palette inspired by the image - final Color backgroundColor = Color(0xFFF8F9FA); // Light cream/white - final Color primaryColor = Color(0xFF2D3436); // Deep charcoal - final Color accentColor = Color(0xFF00B894); // Emerald green - final Color cardColor = Color(0xFFFFFFFF); // Pure white - final Color textSecondary = Color(0xFF636E72); // Muted gray - final Color surfaceColor = Color(0xFFF1F2F6); // Light surface + // Color Scheme + final Color backgroundColor = const Color(0xFFF8F9FA); + final Color primaryColor = const Color(0xFF2D3436); + final Color accentColor = const Color(0xFF00B894); + final Color cardColor = const Color(0xFFFFFFFF); + final Color textSecondary = const Color(0xFF636E72); + final Color surfaceColor = const Color(0xFFF1F2F6); + + // Responsive methods + double responsiveWidth(double size) => + size * (MediaQuery.of(context).size.width / 375); + double responsiveHeight(double size) => + size * (MediaQuery.of(context).size.height / 812); + double responsiveText(double size) { + final width = MediaQuery.of(context).size.width; + if (width < 350) return size * 0.9; + if (width > 600) return size * 1.2; + return size; + } @override void initState() { @@ -86,7 +97,6 @@ class StatisticsState extends State with TickerProviderStateMixin { ).animate( CurvedAnimation(parent: _animationController, curve: Curves.easeOut), ); - fetchTransactions(); _animationController.forward(); } @@ -97,30 +107,21 @@ class StatisticsState extends State with TickerProviderStateMixin { super.dispose(); } - //API Call for Fetch Future fetchTransactions({String category = 'All'}) async { - setState(() { - isLoading = true; - }); - + setState(() => isLoading = true); try { final uri = Uri.parse('http://192.168.1.9:3000/api/transactions').replace( queryParameters: category == 'All' ? null : {'category': category}, ); - final response = await http.get(uri); - if (response.statusCode == 200) { final data = json.decode(response.body); final List list = data['data']; - final fetched = list.map((json) => Transaction.fromJson(json)).toList(); - final totals = {}; for (var tx in fetched) { totals[tx.category] = (totals[tx.category] ?? 0) + tx.amount; } - setState(() { transactions = fetched; totalAmountPerCategory = totals; @@ -137,116 +138,130 @@ class StatisticsState extends State with TickerProviderStateMixin { duration: const Duration(seconds: 4), ); } finally { - setState(() { - isLoading = false; - }); + setState(() => isLoading = false); } } - void _showCustomSnackBar({ - required String message, - required IconData icon, - required Color backgroundColor, - required Color iconColor, - Duration duration = const Duration(seconds: 3), - }) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Container( - padding: const EdgeInsets.symmetric(vertical: 4), - child: Row( - children: [ - Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: Colors.white.withOpacity(0.2), - borderRadius: BorderRadius.circular(8), - ), - child: Icon(icon, color: iconColor, size: 20), - ), - const SizedBox(width: 12), - Expanded( - child: Text( - message, - style: GoogleFonts.inter( - fontWeight: FontWeight.w500, - color: Colors.white, - fontSize: 14, - height: 1.4, - ), - ), - ), - ], - ), - ), - backgroundColor: backgroundColor, - behavior: SnackBarBehavior.floating, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - margin: const EdgeInsets.all(16), - duration: duration, - elevation: 8, - ), - ); - } - Widget _buildHeader() { return Container( - padding: const EdgeInsets.fromLTRB(24, 60, 24, 20), + padding: EdgeInsets.fromLTRB( + responsiveWidth(24), + responsiveHeight(60), + responsiveWidth(24), + responsiveHeight(20), + ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Container( - width: 44, - height: 44, + width: responsiveWidth(44), + height: responsiveWidth(44), decoration: BoxDecoration( color: cardColor, - borderRadius: BorderRadius.circular(22), + borderRadius: BorderRadius.circular(responsiveWidth(22)), boxShadow: [ BoxShadow( color: primaryColor.withOpacity(0.08), - blurRadius: 20, + blurRadius: responsiveWidth(20), offset: const Offset(0, 4), ), ], ), child: Material( color: Colors.transparent, - borderRadius: BorderRadius.circular(22), + borderRadius: BorderRadius.circular(responsiveWidth(22)), child: InkWell( - borderRadius: BorderRadius.circular(22), + borderRadius: BorderRadius.circular(responsiveWidth(22)), onTap: () => Navigator.pop(context), child: Icon( Icons.arrow_back_ios_rounded, color: primaryColor, - size: 20, + size: responsiveText(20), ), ), ), ), Container( - width: 44, - height: 44, + width: responsiveWidth(44), + height: responsiveWidth(44), decoration: BoxDecoration( color: cardColor, - borderRadius: BorderRadius.circular(22), + borderRadius: BorderRadius.circular(responsiveWidth(22)), boxShadow: [ BoxShadow( color: primaryColor.withOpacity(0.08), - blurRadius: 20, + blurRadius: responsiveWidth(20), offset: const Offset(0, 4), ), ], ), - child: Material( - color: Colors.transparent, - borderRadius: BorderRadius.circular(22), - child: InkWell( - borderRadius: BorderRadius.circular(22), - onTap: () {}, - child: Icon( - Icons.more_horiz_rounded, - color: primaryColor, - size: 24, + child: PopupMenuButton( + onSelected: (String value) async { + final updatedBudget = await Navigator.push( + context, + MaterialPageRoute(builder: (context) => const BudgetScreen()), + ); + if (updatedBudget != null) { + setState(() => budget = updatedBudget); + await BudgetManager.setBudget(updatedBudget); + _showCustomSnackBar( + message: 'Budget updated to ₹${updatedBudget.toInt()}', + icon: Icons.check_circle_rounded, + backgroundColor: const Color(0xFF00B894), + iconColor: Colors.white, + ); + } + }, + itemBuilder: + (BuildContext context) => [ + PopupMenuItem( + value: 'update_budget', + child: Container( + padding: EdgeInsets.symmetric( + vertical: responsiveHeight(8), + horizontal: responsiveWidth(12), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.edit_rounded, + color: primaryColor, + size: responsiveText(18), + ), + SizedBox(width: responsiveWidth(12)), + Text( + 'Update Budget', + style: TextStyle( + color: primaryColor, + fontSize: responsiveText(14), + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + ), + ], + offset: Offset(0, responsiveHeight(50)), + elevation: 8, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(responsiveWidth(16)), + ), + color: cardColor, + child: Material( + color: Colors.transparent, + borderRadius: BorderRadius.circular(responsiveWidth(22)), + child: InkWell( + borderRadius: BorderRadius.circular(responsiveWidth(22)), + child: Container( + padding: EdgeInsets.all(responsiveWidth(10)), + child: Icon( + Icons.more_horiz_rounded, + color: primaryColor, + size: responsiveText(24), + ), + ), ), ), ), @@ -261,7 +276,6 @@ class StatisticsState extends State with TickerProviderStateMixin { 0, (sum, tx) => sum + tx.amount, ); - final budget = 10000.0; // You can make this dynamic final progress = totalSpent / budget; return SlideTransition( @@ -269,15 +283,15 @@ class StatisticsState extends State with TickerProviderStateMixin { child: FadeTransition( opacity: _fadeAnimation, child: Container( - margin: const EdgeInsets.symmetric(horizontal: 24), - padding: const EdgeInsets.all(32), + margin: EdgeInsets.symmetric(horizontal: responsiveWidth(24)), + padding: EdgeInsets.all(responsiveWidth(32)), decoration: BoxDecoration( color: cardColor, - borderRadius: BorderRadius.circular(28), + borderRadius: BorderRadius.circular(responsiveWidth(28)), boxShadow: [ BoxShadow( color: primaryColor.withOpacity(0.06), - blurRadius: 30, + blurRadius: responsiveWidth(30), offset: const Offset(0, 8), spreadRadius: 0, ), @@ -289,75 +303,74 @@ class StatisticsState extends State with TickerProviderStateMixin { Text( 'TOTAL SPENT', style: GoogleFonts.inter( - fontSize: 13, + fontSize: responsiveText(13), fontWeight: FontWeight.w500, color: textSecondary, letterSpacing: 1.2, ), ), - const SizedBox(height: 12), + SizedBox(height: responsiveHeight(12)), Text( '₹${totalSpent.toStringAsFixed(1)}', style: GoogleFonts.inter( - fontSize: 42, + fontSize: responsiveText(42), fontWeight: FontWeight.w700, color: primaryColor, height: 1.1, ), ), - const SizedBox(height: 32), - // Modern Circular Progress Ring + SizedBox(height: responsiveHeight(32)), Center( child: Container( - width: 160, - height: 160, + width: responsiveWidth(160), + height: responsiveWidth(160), child: Stack( alignment: Alignment.center, children: [ - // Background circle Container( - width: 160, - height: 160, + width: responsiveWidth(160), + height: responsiveWidth(160), decoration: BoxDecoration( shape: BoxShape.circle, - border: Border.all(color: surfaceColor, width: 8), + border: Border.all( + color: surfaceColor, + width: responsiveWidth(8), + ), ), ), - // Progress circle SizedBox( - width: 160, - height: 160, + width: responsiveWidth(160), + height: responsiveWidth(160), child: CircularProgressIndicator( value: progress > 1 ? 1 : progress, - strokeWidth: 8, + strokeWidth: responsiveWidth(8), backgroundColor: Colors.transparent, valueColor: AlwaysStoppedAnimation( progress > 0.8 - ? Color(0xFFE74C3C) + ? const Color(0xFFE74C3C) : progress > 0.6 - ? Color(0xFFF39C12) + ? const Color(0xFFF39C12) : accentColor, ), strokeCap: StrokeCap.round, ), ), - // Center content Column( mainAxisSize: MainAxisSize.min, children: [ Text( '${(progress * 100).toInt()}%', style: GoogleFonts.inter( - fontSize: 24, + fontSize: responsiveText(24), fontWeight: FontWeight.w700, color: primaryColor, ), ), - const SizedBox(height: 4), + SizedBox(height: responsiveHeight(4)), Text( 'of ₹${budget.toInt()}', style: GoogleFonts.inter( - fontSize: 12, + fontSize: responsiveText(12), fontWeight: FontWeight.w500, color: textSecondary, ), @@ -368,8 +381,7 @@ class StatisticsState extends State with TickerProviderStateMixin { ), ), ), - const SizedBox(height: 24), - // Modern category breakdown + SizedBox(height: responsiveHeight(24)), if (totalAmountPerCategory.isNotEmpty) _buildCategoryBreakdown(), ], ), @@ -390,13 +402,13 @@ class StatisticsState extends State with TickerProviderStateMixin { Text( 'TOP CATEGORIES', style: GoogleFonts.inter( - fontSize: 11, + fontSize: responsiveText(11), fontWeight: FontWeight.w600, color: textSecondary, letterSpacing: 1, ), ), - const SizedBox(height: 16), + SizedBox(height: responsiveHeight(16)), ...topCategories.map( (entry) => _buildCategoryRow(entry.key, entry.value), ), @@ -409,23 +421,23 @@ class StatisticsState extends State with TickerProviderStateMixin { final percentage = total > 0 ? (amount / total) : 0.0; return Padding( - padding: const EdgeInsets.only(bottom: 12), + padding: EdgeInsets.only(bottom: responsiveHeight(12)), child: Row( children: [ Container( - width: 8, - height: 8, + width: responsiveWidth(8), + height: responsiveWidth(8), decoration: BoxDecoration( color: _getCategoryColor(category), shape: BoxShape.circle, ), ), - const SizedBox(width: 12), + SizedBox(width: responsiveWidth(12)), Expanded( child: Text( category, style: GoogleFonts.inter( - fontSize: 14, + fontSize: responsiveText(14), fontWeight: FontWeight.w500, color: primaryColor, ), @@ -434,7 +446,7 @@ class StatisticsState extends State with TickerProviderStateMixin { Text( '${(percentage * 100).toInt()}%', style: GoogleFonts.inter( - fontSize: 14, + fontSize: responsiveText(14), fontWeight: FontWeight.w600, color: textSecondary, ), @@ -444,82 +456,25 @@ class StatisticsState extends State with TickerProviderStateMixin { ); } - Widget _buildToggleButtons() { - return Container( - margin: const EdgeInsets.symmetric(horizontal: 24, vertical: 24), - padding: const EdgeInsets.all(4), - decoration: BoxDecoration( - color: surfaceColor, - borderRadius: BorderRadius.circular(16), - ), - child: Row( - children: [ - Expanded(child: _buildToggleButton('Monthly')), - Expanded(child: _buildToggleButton('Yearly')), - ], - ), - ); - } - - Widget _buildToggleButton(String label) { - final bool isSelected = _selectedPlan == label; - - return GestureDetector( - onTap: () => setState(() => _selectedPlan = label), - child: AnimatedContainer( - duration: const Duration(milliseconds: 300), - curve: Curves.easeInOut, - padding: const EdgeInsets.symmetric(vertical: 16), - decoration: BoxDecoration( - color: isSelected ? cardColor : Colors.transparent, - borderRadius: BorderRadius.circular(12), - boxShadow: - isSelected - ? [ - BoxShadow( - color: primaryColor.withOpacity(0.08), - blurRadius: 10, - spreadRadius: 0, - offset: const Offset(0, 2), - ), - ] - : [], - ), - alignment: Alignment.center, - child: Text( - label, - style: GoogleFonts.inter( - color: isSelected ? primaryColor : textSecondary, - fontWeight: FontWeight.w600, - fontSize: 15, - ), - ), - ), - ); - } - Widget buildCategoryChips() { return Container( - height: 50, - margin: const EdgeInsets.only(bottom: 16), + height: responsiveHeight(50), + margin: EdgeInsets.only(bottom: responsiveHeight(16)), child: ListView.builder( scrollDirection: Axis.horizontal, - padding: const EdgeInsets.symmetric(horizontal: 20), + padding: EdgeInsets.symmetric(horizontal: responsiveWidth(20)), itemCount: categories.length, itemBuilder: (context, index) { final cat = categories[index]; final bool isSelected = cat == selectedCategory; - return Container( - margin: const EdgeInsets.only(right: 12), + margin: EdgeInsets.only(right: responsiveWidth(12)), child: FilterChip( label: Text(cat), selected: isSelected, onSelected: (selected) { if (selected) { - setState(() { - selectedCategory = cat; - }); + setState(() => selectedCategory = cat); fetchTransactions(category: cat); } }, @@ -529,11 +484,11 @@ class StatisticsState extends State with TickerProviderStateMixin { labelStyle: GoogleFonts.inter( color: isSelected ? cardColor : textSecondary, fontWeight: FontWeight.w500, - fontSize: 14, + fontSize: responsiveText(14), ), side: BorderSide.none, shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20), + borderRadius: BorderRadius.circular(responsiveWidth(20)), ), ), ); @@ -544,31 +499,37 @@ class StatisticsState extends State with TickerProviderStateMixin { Widget buildTransactionCard(Transaction tx) { return Container( - margin: const EdgeInsets.symmetric(horizontal: 24, vertical: 6), + margin: EdgeInsets.symmetric( + horizontal: responsiveWidth(24), + vertical: responsiveHeight(6), + ), decoration: BoxDecoration( color: cardColor, - borderRadius: BorderRadius.circular(20), + borderRadius: BorderRadius.circular(responsiveWidth(20)), boxShadow: [ BoxShadow( color: primaryColor.withOpacity(0.04), - blurRadius: 20, + blurRadius: responsiveWidth(20), offset: const Offset(0, 4), ), ], ), child: ListTile( - contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8), + contentPadding: EdgeInsets.symmetric( + horizontal: responsiveWidth(20), + vertical: responsiveHeight(8), + ), leading: Container( - width: 48, - height: 48, + width: responsiveWidth(48), + height: responsiveWidth(48), decoration: BoxDecoration( color: _getCategoryColor(tx.category), - borderRadius: BorderRadius.circular(14), + borderRadius: BorderRadius.circular(responsiveWidth(14)), ), child: Icon( _getCategoryIcon(tx.category), color: cardColor, - size: 24, + size: responsiveText(24), ), ), title: Text( @@ -576,16 +537,16 @@ class StatisticsState extends State with TickerProviderStateMixin { style: GoogleFonts.inter( color: primaryColor, fontWeight: FontWeight.w600, - fontSize: 16, + fontSize: responsiveText(16), ), ), subtitle: Padding( - padding: const EdgeInsets.only(top: 4), + padding: EdgeInsets.only(top: responsiveHeight(4)), child: Text( '${tx.category.toUpperCase()} • ${_formatTime(tx.date)}', style: GoogleFonts.inter( color: textSecondary, - fontSize: 13, + fontSize: responsiveText(13), fontWeight: FontWeight.w500, ), ), @@ -595,22 +556,70 @@ class StatisticsState extends State with TickerProviderStateMixin { style: GoogleFonts.inter( color: primaryColor, fontWeight: FontWeight.w700, - fontSize: 17, + fontSize: responsiveText(17), ), ), ), ); } + void _showCustomSnackBar({ + required String message, + required IconData icon, + required Color backgroundColor, + required Color iconColor, + Duration duration = const Duration(seconds: 3), + }) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Container( + padding: EdgeInsets.symmetric(vertical: responsiveHeight(4)), + child: Row( + children: [ + Container( + padding: EdgeInsets.all(responsiveWidth(8)), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.2), + borderRadius: BorderRadius.circular(responsiveWidth(8)), + ), + child: Icon(icon, color: iconColor, size: responsiveText(20)), + ), + SizedBox(width: responsiveWidth(12)), + Expanded( + child: Text( + message, + style: GoogleFonts.inter( + fontWeight: FontWeight.w500, + color: Colors.white, + fontSize: responsiveText(14), + height: 1.4, + ), + ), + ), + ], + ), + ), + backgroundColor: backgroundColor, + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(responsiveWidth(12)), + ), + margin: EdgeInsets.all(responsiveWidth(16)), + duration: duration, + elevation: 8, + ), + ); + } + Color _getCategoryColor(String category) { final colors = [ - Color(0xFF6C5CE7), - Color(0xFFE17055), - Color(0xFF00B894), - Color(0xFF0984E3), - Color(0xFFE84393), - Color(0xFFFD79A8), - Color(0xFF6C5CE7), + const Color(0xFF6C5CE7), + const Color(0xFFE17055), + const Color(0xFF00B894), + const Color(0xFF0984E3), + const Color(0xFFE84393), + const Color(0xFFFD79A8), + const Color(0xFF6C5CE7), ]; return colors[categories.indexOf(category) % colors.length]; } @@ -635,7 +644,6 @@ class StatisticsState extends State with TickerProviderStateMixin { String _formatTime(DateTime date) { final now = DateTime.now(); final difference = now.difference(date); - if (difference.inDays == 0) { return '${date.hour.toString().padLeft(2, '0')}:${date.minute.toString().padLeft(2, '0')}'; } else { @@ -653,7 +661,6 @@ class StatisticsState extends State with TickerProviderStateMixin { children: [ _buildHeader(), _buildTotalSpentCard(), - _buildToggleButtons(), buildCategoryChips(), Expanded( child: @@ -671,15 +678,15 @@ class StatisticsState extends State with TickerProviderStateMixin { children: [ Icon( Icons.analytics_outlined, - size: 64, + size: responsiveWidth(64), color: textSecondary.withOpacity(0.5), ), - const SizedBox(height: 16), + SizedBox(height: responsiveHeight(16)), Text( 'No transactions found', style: GoogleFonts.inter( color: textSecondary, - fontSize: 16, + fontSize: responsiveText(16), fontWeight: FontWeight.w500, ), ), @@ -687,7 +694,10 @@ class StatisticsState extends State with TickerProviderStateMixin { ), ) : ListView.builder( - padding: const EdgeInsets.only(top: 8, bottom: 120), + padding: EdgeInsets.only( + top: responsiveHeight(8), + bottom: responsiveHeight(120), + ), itemCount: transactions.length, itemBuilder: (context, index) => @@ -698,15 +708,13 @@ class StatisticsState extends State with TickerProviderStateMixin { ), floatingActionButton: CustomNavBar( currentIndex: _selectedIndex, - onTap: (index) { - setState(() => _selectedIndex = index); - }, + onTap: (index) => setState(() => _selectedIndex = index), backgroundColor: backgroundColor, accentColor: accentColor, primaryColor: primaryColor, cardColor: cardColor, + floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, ), - floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, ); } }