import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_contacts/flutter_contacts.dart'; import 'package:go_router/go_router.dart'; import 'package:provider/provider.dart'; import 'package:qpay/models/responsive_policy.dart'; import 'package:qpay/screens/transaction_controller.dart'; import 'package:skeletonizer/skeletonizer.dart'; import 'package:qpay/screens/recipient/recipients_controller.dart'; import 'package:shared_preferences/shared_preferences.dart'; class RecipientsScreen extends StatefulWidget { const RecipientsScreen({super.key}); @override State createState() => _RecipientsScreenState(); } class _RecipientsScreenState extends State with TickerProviderStateMixin { late SharedPreferences prefs; late AnimationController _animationController; late RecipientsController controller; late TransactionController transactionController; final List> _alignListAnimations = []; final _accountController = TextEditingController(); final _queryParams = {}; @override void initState() { super.initState(); controller = RecipientsController(context); setupData(); controller.model.creditAccount = _accountController.text; transactionController = Provider.of( context, listen: false, ); _animationController = AnimationController( duration: const Duration(milliseconds: 600), vsync: this, )..forward(); List tranListIndices = [0, 1]; for (int i = 0; i < tranListIndices.length; i++) { _alignListAnimations.add( Tween(begin: Offset(0, -0.25), end: Offset.zero).animate( CurvedAnimation( parent: _animationController, curve: Interval(0.075 * i, 1.0, curve: Curves.easeOut), ), ), ); } } Future setupData() async { prefs = await SharedPreferences.getInstance(); _queryParams['userId'] = prefs.getString("userId")!; controller.getRecipients(_queryParams); } @override void dispose() { _animationController.dispose(); controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Recipients", style: TextStyle(color: Colors.black87)), ), body: ListenableBuilder( listenable: controller, builder: (context, child) { return SafeArea( child: SingleChildScrollView( padding: const EdgeInsets.all(20), child: Center( child: LayoutBuilder( builder: (context, constraints) { return SizedBox( width: double.parse( constraints.maxWidth > ResponsivePolicy.md ? ResponsivePolicy.md.toString() : constraints.maxWidth.toString(), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildTransactionDetailsCard( label: "Provider", value: controller.model.selectedProvider?.name ?? "", ), const SizedBox(height: 20), _buildAccountField(controller), const SizedBox(height: 20), controller.model.creditAccount != null && controller.model.creditAccount!.isNotEmpty ? SlideTransition( position: _alignListAnimations[0], child: ElevatedButton( onPressed: () { String phone = ''; if (!transactionController .model .selectedProvider! .requiresAccount) { phone = _accountController.text; } transactionController.model.formData = transactionController.model.formData .copyWith( creditAccount: _accountController.text, creditPhone: phone, ); context.push("/make-payment"); }, style: ElevatedButton.styleFrom( backgroundColor: Theme.of( context, ).colorScheme.primary, foregroundColor: Theme.of( context, ).colorScheme.onPrimary, ), child: Row( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ Text( "Use as new recipient", style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), SizedBox(width: 5), Icon(Icons.arrow_forward_ios, size: 12), ], ), ), ) : SizedBox(), const SizedBox(height: 20), Text( "Recent Recipients", style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 10), _buildRecipientItems(controller), ], ), ); }, ), ), ), ); }, ), ); } Widget _buildAccountField(RecipientsController controller) { String fieldName = controller.model.selectedProvider?.accountFieldName ?? "Account Number"; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(fieldName, style: TextStyle(fontSize: 16)), if (!kIsWeb) TextButton( onPressed: () async { if (await FlutterContacts.requestPermission()) { final contact = await FlutterContacts.openExternalPick(); if (contact != null) { _accountController.text = contact.phones.first.number; controller.updateCreditAccount( contact.phones.first.number, ); } } }, child: Text( "Fetch from contacts", style: TextStyle(fontSize: 12, color: Colors.red), ), ), ], ), const SizedBox(height: 12), Row( children: [ Expanded( child: TextFormField( controller: _accountController, keyboardType: TextInputType.number, decoration: InputDecoration( hintText: 'Enter / Search $fieldName', focusedErrorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide( color: Theme.of(context).colorScheme.secondary, ), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide( color: Theme.of( context, ).colorScheme.primary.withValues(alpha: 0.2), ), ), ), onChanged: (value) { controller.updateCreditAccount(value); }, validator: (value) { if (value == null || value.isEmpty) { return 'Please enter an amount'; } if (double.tryParse(value) == null) { return 'Please enter a valid amount'; } return null; }, ), ), IconButton( onPressed: () { _queryParams.clear(); setState(() { _queryParams['account'] = _accountController.text; _queryParams['email'] = _accountController.text; _queryParams['phoneNumber'] = _accountController.text; _queryParams['name'] = _accountController.text; _queryParams['userId'] = prefs.getString("userId")!; controller.getRecipients(_queryParams); }); }, icon: Icon( Icons.search, color: Theme.of(context).colorScheme.primary, size: 24, ), ), ], ), ], ); } Widget _buildRecipientItems(RecipientsController controller) { if (controller.model.recipients.isEmpty && !controller.model.isLoading) { return Center( child: Padding( padding: const EdgeInsets.symmetric(vertical: 40), child: Column( children: [ Icon(Icons.people_outline, size: 64, color: Colors.grey.shade400), const SizedBox(height: 16), Text( "No recipients yet", style: TextStyle( fontSize: 16, color: Colors.grey.shade600, fontWeight: FontWeight.w500, ), ), ], ), ), ); } return ListView.separated( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemCount: controller.model.recipients.length, separatorBuilder: (_, __) => const SizedBox(height: 10), itemBuilder: (context, index) { final recipient = controller.model.recipients[index]; final animIdx = index < _alignListAnimations.length ? index : _alignListAnimations.length - 1; return Skeletonizer( enabled: controller.model.isLoading, child: SlideTransition( position: _alignListAnimations[animIdx.clamp( 0, _alignListAnimations.length - 1, )], child: Material( color: Colors.transparent, child: InkWell( onTap: () { String account = recipient.account ?? ''; if (!transactionController .model .selectedProvider! .requiresAccount) { account = recipient.phoneNumber ?? ''; } transactionController .model .formData = transactionController.model.formData.copyWith( creditAccount: account, creditName: recipient.name ?? '', creditPhone: recipient.phoneNumber ?? '', creditEmail: recipient.email ?? '', providerLabel: transactionController .model .formData .providerLabel .isEmpty ? recipient.latestProviderLabel ?? '' : transactionController.model.formData.providerLabel, ); context.push("/make-payment"); }, borderRadius: BorderRadius.circular(16), child: Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), border: Border.all(color: Colors.grey.shade200, width: 1), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.04), blurRadius: 8, offset: const Offset(0, 2), ), ], ), child: Padding( padding: const EdgeInsets.symmetric( vertical: 14, horizontal: 16, ), child: Row( children: [ // Avatar Container( width: 50, height: 50, decoration: BoxDecoration( gradient: LinearGradient( colors: [ Theme.of(context).colorScheme.primary, Theme.of( context, ).colorScheme.primary.withValues(alpha: 0.7), ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), shape: BoxShape.circle, boxShadow: [ BoxShadow( color: Theme.of( context, ).colorScheme.primary.withValues(alpha: 0.25), blurRadius: 6, offset: const Offset(0, 2), ), ], ), alignment: Alignment.center, child: Text( recipient.initials ?? 'PK', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.white, ), ), ), const SizedBox(width: 16), // Details Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Name if (recipient.name != null && recipient.name!.isNotEmpty) Text( recipient.name ?? '', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: Colors.black87, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), if (recipient.name != null && recipient.name!.isNotEmpty) const SizedBox(height: 4), // Account number and provider label row Row( children: [ if (recipient.latestProviderLabel != null && recipient.latestProviderLabel!.isNotEmpty) Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 2, ), decoration: BoxDecoration( color: Theme.of(context) .colorScheme .primary .withValues(alpha: 0.08), borderRadius: BorderRadius.circular(6), ), child: Text( recipient.latestProviderLabel ?? '', style: TextStyle( fontSize: 11, fontWeight: FontWeight.w600, color: Theme.of( context, ).colorScheme.primary, ), ), ), if (recipient.latestProviderLabel != null && recipient .latestProviderLabel! .isNotEmpty && recipient.account != null && recipient.account!.isNotEmpty) const SizedBox(width: 6), if (recipient.account != null && recipient.account!.isNotEmpty) Expanded( child: Text( recipient.account ?? '', style: TextStyle( fontSize: 13, color: Colors.grey.shade700, fontWeight: FontWeight.w500, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), ), ], ), const SizedBox(height: 4), // Phone / Email row if ((recipient.phoneNumber != null && recipient.phoneNumber!.isNotEmpty) || (recipient.email != null && recipient.email!.isNotEmpty)) Row( spacing: 8, children: [ if (recipient.phoneNumber != null && recipient.phoneNumber!.isNotEmpty) _buildContactChip( Icons.phone_outlined, recipient.phoneNumber!, Colors.grey.shade600, ), if (recipient.email != null && recipient.email!.isNotEmpty) _buildContactChip( Icons.email_outlined, recipient.email!, Colors.grey.shade500, ), ], ), ], ), ), // Chevron Container( width: 28, height: 28, decoration: BoxDecoration( color: Colors.grey.shade100, shape: BoxShape.circle, ), child: Icon( Icons.arrow_forward_ios, size: 12, color: Colors.grey.shade400, ), ), ], ), ), ), ), ), ), ); }, ); } Widget _buildContactChip(IconData icon, String text, Color color) { return Row( mainAxisSize: MainAxisSize.min, children: [ Icon(icon, size: 13, color: color), const SizedBox(width: 3), Flexible( child: Text( text, style: TextStyle(fontSize: 12, color: color), maxLines: 1, overflow: TextOverflow.ellipsis, ), ), ], ); } Widget _buildTransactionDetailsCard({ required String label, String? value, List? children, }) { return Container( width: double.infinity, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), border: Border.all(color: Colors.grey.shade200, width: 1), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.04), blurRadius: 8, offset: const Offset(0, 2), ), ], ), child: Padding( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon( Icons.receipt_long_outlined, size: 18, color: Theme.of(context).colorScheme.primary, ), const SizedBox(width: 8), Text( "TRANSACTION DETAILS", style: TextStyle( fontSize: 13, fontWeight: FontWeight.w700, color: Theme.of(context).colorScheme.primary, letterSpacing: 1.0, ), ), ], ), const SizedBox(height: 16), Divider(height: 1, color: Colors.grey.shade200), const SizedBox(height: 16), if (children != null) ...children else _buildDetailRow( icon: Icons.business_outlined, label: label, value: value ?? '', ), ], ), ), ); } Widget _buildDetailRow({ required IconData icon, required String label, required String value, }) { return Padding( padding: const EdgeInsets.only(bottom: 12), child: Row( children: [ Icon(icon, size: 16, color: Colors.grey.shade500), const SizedBox(width: 10), Text( label, style: TextStyle(fontSize: 14, color: Colors.grey.shade600), ), const Spacer(), Text( value, style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: Colors.black87, ), ), ], ), ); } }