import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:provider/provider.dart'; import 'package:qpay/models/responsive_policy.dart'; import 'package:timeago/timeago.dart' as timeago; import 'package:qpay/screens/home/home_controller.dart'; import 'package:qpay/screens/transaction_controller.dart'; import 'package:skeletonizer/skeletonizer.dart'; import 'dart:async'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:uuid/uuid.dart'; class HomeScreen extends StatefulWidget { const HomeScreen({super.key}); @override State createState() => _HomeScreenState(); } class _HomeScreenState extends State with SingleTickerProviderStateMixin { late AnimationController _controller; late TransactionController transactionController; late SharedPreferences prefs; late HomeController homeController; late final router = GoRouter.of(context); late String initials; bool _isLoggedIn = false; String _selectedFilter = 'All'; @override void initState() { super.initState(); _controller = AnimationController( duration: const Duration(milliseconds: 3000), vsync: this, )..forward(); setupDataAndTransitions(); } Future setupDataAndTransitions() async { router.routerDelegate.addListener(_onRouteChanged); transactionController = Provider.of( context, listen: false, ); // Reset the transaction controller model transactionController.model = TransactionModel(); homeController = HomeController(context); await homeController.getAccounts(); await homeController.getCategories(); await homeController.getProviders(); prefs = await SharedPreferences.getInstance(); if (prefs.getString("token") != null) { setState(() { _isLoggedIn = true; initials = prefs.getString("initials")!; }); } if (prefs.getString("userId") == null) { prefs.setString("userId", Uuid().v4()); } await homeController.getTransactions(prefs.getString("userId")!); } getBoxDecoration(String? clientId) { return BoxDecoration( border: Border.all(color: Colors.black87.withAlpha(20)), borderRadius: BorderRadius.circular(12), gradient: LinearGradient( colors: [ Theme.of(context).colorScheme.primary.withValues(alpha: 0.1), Theme.of(context).colorScheme.tertiary.withValues(alpha: 0.05), ], stops: const [0.3, 0.7], begin: Alignment.topRight, end: Alignment.bottomLeft, ), ); } void _onRouteChanged() async { final location = router.routerDelegate.currentConfiguration.uri.toString(); if (location.startsWith('/home')) { prefs = await SharedPreferences.getInstance(); if (prefs.getString("userId") == null) { prefs.setString("userId", Uuid().v4()); } await homeController.getTransactions(prefs.getString("userId")!); } } @override void dispose() { _controller.dispose(); homeController.dispose(); router.routerDelegate.removeListener(_onRouteChanged); super.dispose(); } @override Widget build(BuildContext context) { return LayoutBuilder( builder: (context, constraints) { return Padding( padding: const EdgeInsets.symmetric(vertical: 20.0, horizontal: 30), child: Center( child: ConstrainedBox( constraints: BoxConstraints( maxWidth: (constraints.maxWidth > ResponsivePolicy.lg) ? ResponsivePolicy.lg.toDouble() : constraints.maxWidth, // Set the maximum width here ), child: Scaffold( appBar: AppBar( title: (constraints.maxWidth < ResponsivePolicy.md) ? Image( image: AssetImage('assets/velocity.png'), width: 150, ) : SizedBox(), actions: [ _isLoggedIn ? Container( padding: const EdgeInsets.only(left: 10.0), child: IconButton( icon: Container( width: 30, height: 30, decoration: BoxDecoration( border: Border.all( color: Colors.black87.withAlpha(20), ), shape: BoxShape.circle, gradient: LinearGradient( colors: [ Theme.of( context, ).colorScheme.primary.withValues(alpha: 0.1), Theme.of(context).colorScheme.tertiary .withValues(alpha: 0.05), ], stops: const [0.3, 0.7], begin: Alignment.topRight, end: Alignment.bottomLeft, ), ), child: Center( child: Text( initials, style: TextStyle( fontSize: 12, fontWeight: FontWeight.bold, ), ), ), ), onPressed: () {}, ), ) : SizedBox(), if (_isLoggedIn) IconButton( icon: const Icon(Icons.turn_slight_right_sharp), onPressed: () { showDialog( context: context, builder: (BuildContext dialogContext) { return AlertDialog( title: const Text('Confirm Logout'), content: const Text( 'Are you sure you want to log out?', ), actions: [ TextButton( child: const Text('Cancel'), onPressed: () { Navigator.of( dialogContext, ).pop(); // Dismiss the dialog }, ), TextButton( child: const Text('Logout'), onPressed: () { Navigator.of( dialogContext, ).pop(); // Dismiss the dialog prefs.remove("token"); setState(() { _isLoggedIn = false; }); // It's generally not recommended to call initState directly. // Consider moving the logic from initState that needs to be rerun // into a separate method and call that method here. // For example, if you have a method like _reloadData(), call it here. // _reloadData(); setupDataAndTransitions(); // Or specific parts of it if not all is needed }, ), ], ); }, ); }, ), ], ), body: Padding( padding: const EdgeInsets.only(top: 20.0), child: SafeArea( child: RefreshIndicator( onRefresh: () async { await homeController.getTransactions( prefs.getString("userId")!, ); }, child: ListenableBuilder( listenable: homeController, builder: (context, child) { if (constraints.maxWidth > ResponsivePolicy.md) { return GridView.count( crossAxisCount: 2, crossAxisSpacing: 50, mainAxisSpacing: 50, shrinkWrap: true, children: [_buildProviders(), _buildTransactions()], ); } else { return SingleChildScrollView( child: Column( children: [ _buildProviders(), SizedBox(height: 10), _buildTransactions(), ], ), ); } }, ), ), ), ), ), ), ), ); }, ); } Widget _buildLogin() { return Column( children: [ Container( decoration: BoxDecoration( border: Border.all(color: Colors.black87.withAlpha(20)), borderRadius: BorderRadius.circular(12), ), padding: EdgeInsets.symmetric(vertical: 10, horizontal: 20), child: Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ Text( 'Register or Login for more', style: TextStyle(fontSize: 16), ), InkWell( onTap: () { _showFeaturesPopup(context); }, borderRadius: BorderRadius.circular(5), child: Padding( padding: EdgeInsets.symmetric(horizontal: 5, vertical: 2), child: Text( 'features', style: TextStyle( fontSize: 16, decoration: TextDecoration.underline, decorationColor: Theme.of( context, ).colorScheme.primary, color: Theme.of(context).colorScheme.primary, ), ), ), ), ], ), InkWell( onTap: () { context.push('/onboarding/landing'); }, borderRadius: BorderRadius.circular(12), child: Container( padding: EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: Theme.of( context, ).colorScheme.primary.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(12), ), child: Text('Get Started', style: TextStyle(fontSize: 12)), ), ), ], ), ), SizedBox(height: 10), ], ); } Widget _buildAccounts() { return Column( children: [ Row( children: [ ...homeController.model.accounts.map((e) => _buildAccountItem(e)), ], ), SizedBox(height: 10), ], ); } Widget _buildAccountItem(Account e) { int index = homeController.model.accounts.indexOf(e); Animation animation = Tween(begin: Offset(0, -0.20), end: Offset.zero).animate( CurvedAnimation( parent: _controller, curve: Interval(0.40 * index.toDouble(), 1.0, curve: Curves.easeIn), ), ); return Row( children: [ Skeletonizer( enabled: homeController.model.isLoading, child: SlideTransition( position: animation, child: InkWell( borderRadius: BorderRadius.circular(12), onTap: () { // context.push("/wallet"); }, child: Container( width: 170, padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 12, ), decoration: BoxDecoration( border: Border.all(color: Colors.black87.withAlpha(20)), borderRadius: BorderRadius.circular(12), gradient: LinearGradient( colors: [ Theme.of( context, ).colorScheme.primary.withValues(alpha: 0.1), Theme.of( context, ).colorScheme.tertiary.withValues(alpha: 0.05), ], stops: const [0.3, 0.7], begin: Alignment.topRight, end: Alignment.bottomLeft, ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.start, children: [ Image.asset('assets/${e.image}', width: 20), SizedBox(width: 10), Text( e.currency, style: TextStyle( fontSize: 22, color: Colors.black, fontWeight: FontWeight.w100, ), ), SizedBox(width: 5), Expanded( child: Text( e.balance, style: TextStyle( fontWeight: FontWeight.bold, fontSize: 22, color: Colors.black, overflow: TextOverflow.ellipsis, ), ), ), ], ), SizedBox(height: 5), InkWell( onTap: () { // context.push("/withdraw"); }, borderRadius: BorderRadius.circular(12), child: Container( padding: EdgeInsets.symmetric( horizontal: 12, vertical: 6, ), decoration: BoxDecoration( color: Theme.of( context, ).colorScheme.primary.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(12), ), child: Text( 'Add Money', style: TextStyle(fontSize: 12), ), ), ), ], ), ), ), ), ), SizedBox(width: 10), ], ); } Widget _buildTransactions() { return Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.center, children: [ const Text( 'Recent Activity', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), ], ), const SizedBox(height: 5), if (homeController.model.transactions.isEmpty) Column( children: [ SizedBox(height: 30), Center( child: Text( 'No transactions yet. Make a payment to see your recent activity.', style: TextStyle(fontSize: 16), textAlign: TextAlign.center, ), ), ], ), if (homeController.model.transactions.isNotEmpty) Column( children: [ ListView( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), children: [ ...homeController.model.transactions.map( (e) => _buildTransactionItem(e, context), ), ], ), SizedBox(height: 10), if (homeController.model.transactions.length > 2) OutlinedButton( child: Text('View All'), onPressed: () { context.go('/history'); }, ), ], ), ], ); } Widget _buildTransactionItem(Map e, BuildContext context) { int index = homeController.model.transactions.indexOf(e); Animation animation = Tween(begin: Offset(0, -0.25), end: Offset.zero).animate( CurvedAnimation( parent: _controller, curve: Interval(0.175 * index, 1.0, curve: Curves.easeIn), ), ); return SlideTransition( position: animation, child: Skeletonizer( enabled: homeController.model.isLoading, child: InkWell( customBorder: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), onTap: () { transactionController.updateReceiptData(e); context.push("/receipt"); }, child: ListTile( leading: Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: Theme.of( context, ).colorScheme.primary.withValues(alpha: 0.1), shape: BoxShape.circle, ), child: Image( image: AssetImage("assets/${e['paymentProcessorImage']}"), ), ), title: Row( children: [ Text( e['billName'], style: TextStyle(fontWeight: FontWeight.bold), ), SizedBox(width: 10), Icon( e['integrationStatus'] == 'SUCCESS' ? Icons.check_circle : e['integrationStatus'] == 'PENDING' ? Icons.pending : Icons.cancel, size: 16, color: e['integrationStatus'] == 'SUCCESS' ? Colors.green : e['integrationStatus'] == 'PENDING' ? Colors.orange : Colors.red, ), ], ), subtitle: Text( e['createdAt'] != null ? timeago.format(DateTime.parse(e['createdAt'])) : '', style: TextStyle(fontSize: 12), ), trailing: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( e['amount'].toStringAsFixed(2), style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18), ), Text( e['totalAmount'].toStringAsFixed(2), style: TextStyle(fontSize: 12), ), ], ), ), ), ), ); } Widget _buildProviders() { return Column( children: [ if (!_isLoggedIn) _buildLogin(), SizedBox(height: 10), // if (_isLoggedIn) _buildAccounts(homeController), // _buildFilterChips(homeController), ...homeController.model.filterableProviders.map( (e) => _buildProviderItem(e, context), ), ], ); } Column _buildProviderItem(BillProvider e, BuildContext context) { int index = homeController.model.filterableProviders.indexOf(e); Animation animation = Tween(begin: Offset(0, -0.20), end: Offset.zero).animate( CurvedAnimation( parent: _controller, curve: Interval(0.40 * index.toDouble(), 1.0, curve: Curves.easeIn), ), ); return Column( children: [ Skeletonizer( enabled: homeController.model.isLoading, child: SlideTransition( position: animation, child: Container( decoration: getBoxDecoration(e.clientId), child: InkWell( customBorder: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), onTap: () { transactionController .clearFormData(); // make sure we're starting fresh homeController.updateSelectedProvider(e); transactionController.updateSelectedProvider(e); context.push("/recipients"); }, child: ListTile( leading: Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: Theme.of( context, ).colorScheme.primary.withValues(alpha: 0.1), shape: BoxShape.circle, ), child: Image(image: AssetImage("assets/${e.image}")), ), title: Text( e.name, style: TextStyle(fontWeight: FontWeight.bold), ), subtitle: Text( e.description, style: TextStyle( fontWeight: FontWeight.normal, fontSize: 10, ), ), trailing: Icon( Icons.chevron_right, color: Theme.of( context, ).colorScheme.secondary.withValues(alpha: 0.7), ), ), ), ), ), ), SizedBox(height: 10), ], ); } Widget _buildFilterChips(HomeController controller) { return SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( children: [ _buildFilterChip('All', controller), ...controller.model.categories.map((category) { return _buildFilterChip(category.name, controller, category); }), ], ), ); } Widget _buildFilterChip( String label, HomeController controller, [ Category? category, ]) { final isSelected = _selectedFilter == label; return Skeletonizer( enabled: controller.model.isLoading, child: Padding( padding: const EdgeInsets.only(right: 8), child: FilterChip( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), side: BorderSide( color: Theme.of( context, ).colorScheme.primary.withValues(alpha: 0.2), ), ), selected: isSelected, label: Text(label), onSelected: (selected) { setState(() { _selectedFilter = label; }); if (category != null) { controller.updateSelectedCategory(category); } else { controller.updateSelectedCategory(null); } }, backgroundColor: Theme.of(context).colorScheme.surface, selectedColor: Theme.of(context).colorScheme.primary.withOpacity(0.2), checkmarkColor: Theme.of(context).colorScheme.primary, labelStyle: TextStyle( color: isSelected ? Theme.of(context).colorScheme.secondary.withValues(alpha: 0.7) : Theme.of(context).colorScheme.onSurface, ), ), ), ); } void _showFeaturesPopup(BuildContext context) { showDialog( context: context, builder: (BuildContext context) { return Dialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), elevation: 0, backgroundColor: Colors.transparent, child: ConstrainedBox( constraints: BoxConstraints(maxWidth: 600), child: Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( shape: BoxShape.rectangle, color: Theme.of(context).colorScheme.surface, borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 20, offset: const Offset(0, 10), ), ], ), child: Column( mainAxisSize: MainAxisSize.min, children: [ // Header with icon and title Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: Theme.of( context, ).colorScheme.primary.withValues(alpha: 0.1), shape: BoxShape.circle, ), child: Image( image: AssetImage("assets/favicon.png"), width: 45, ), ), const SizedBox(height: 16), Text( 'Unlock Additional Features', style: TextStyle( fontSize: 24, fontWeight: FontWeight.bold, color: Theme.of(context).colorScheme.onSurface, ), textAlign: TextAlign.center, ), const SizedBox(height: 8), Text( 'Register or login to access these great features', style: TextStyle( fontSize: 14, color: Theme.of( context, ).colorScheme.onSurface.withOpacity(0.7), ), textAlign: TextAlign.center, ), const SizedBox(height: 24), // Features list _buildFeatureItem( Icons.account_balance_wallet, 'Dual Currency Wallets', 'USD and ZWG wallets for prepayments that can be utilized later', ), const SizedBox(height: 16), _buildFeatureItem( Icons.history, 'Transaction History', 'All transactions are saved for reference from any device', ), const SizedBox(height: 16), _buildFeatureItem( Icons.people, 'Recipient Management', 'Save and manage recipient details for future use', ), const SizedBox(height: 16), _buildFeatureItem( Icons.schedule, 'Smart Payments', 'Schedule payments and split bills with friends', ), const SizedBox(height: 24), // Action buttons Row( children: [ Expanded( child: TextButton( onPressed: () => Navigator.of(context).pop(), style: TextButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), child: Text( 'Maybe Later', style: TextStyle( color: Theme.of( context, ).colorScheme.onSurface.withOpacity(0.6), fontSize: 16, ), ), ), ), const SizedBox(width: 12), Expanded( child: ElevatedButton( onPressed: () { Navigator.of(context).pop(); context.push('/onboarding/landing'); }, style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 12), backgroundColor: Theme.of( context, ).colorScheme.primary, foregroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), child: Text( 'Get Started', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, ), ), ), ), ], ), ], ), ), ), ); }, ); } Widget _buildFeatureItem(IconData icon, String title, String description) { return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: Theme.of(context).colorScheme.primary.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(8), ), child: Icon( icon, size: 20, color: Theme.of(context).colorScheme.primary, ), ), const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: Theme.of(context).colorScheme.onSurface, ), ), const SizedBox(height: 8), Text( description, style: TextStyle( fontSize: 14, color: Theme.of( context, ).colorScheme.onSurface.withOpacity(0.7), height: 1.4, ), ), ], ), ), ], ); } }