import 'dart:async'; 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: [ Container( decoration: BoxDecoration( border: Border.all(color: Colors.black87.withAlpha(20)), borderRadius: BorderRadius.circular(12), ), padding: EdgeInsets.symmetric(vertical: 10, horizontal: 20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "TRANSACTIONS DETAILS", style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), const SizedBox(height: 10), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text("Provider", style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, )), Text( controller.model.selectedProvider?.name ?? "", style: TextStyle(fontSize: 16), ), ], ), ], ), ), 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)), 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.withOpacity(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: Text("No recipients yet")); } return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ ...controller.model.recipients.map( (recipient) => Skeletonizer( enabled: controller.model.isLoading, 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(12), child: Container( padding: EdgeInsets.all(5), child: SlideTransition( position: _alignListAnimations[0], child: Row( mainAxisAlignment: MainAxisAlignment.start, children: [ Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Theme.of( context, ).colorScheme.primary.withValues(alpha: 0.1), shape: BoxShape.circle, ), child: Text( recipient.initials ?? 'PK', style: TextStyle( fontSize: 25, fontWeight: FontWeight.bold, ), ), ), SizedBox(width: 20), Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text( recipient.latestProviderLabel ?? '', style: TextStyle(fontWeight: FontWeight.bold), ), SizedBox(width: 5), Text( recipient.account ?? '', style: TextStyle(fontWeight: FontWeight.normal), ), ], ), if ((recipient.phoneNumber != null && recipient.phoneNumber!.isNotEmpty) || (recipient.email != null && recipient.email!.isNotEmpty)) Row( spacing: 5, children: [ if (recipient.phoneNumber != null && recipient.phoneNumber!.isNotEmpty) Text( recipient.phoneNumber ?? '', style: TextStyle( fontWeight: FontWeight.bold, ), ), if (recipient.email != null && recipient.email!.isNotEmpty) Text( recipient.email ?? '', style: TextStyle( fontWeight: FontWeight.normal, ), ), ], ), if (recipient.name != null && recipient.name!.isNotEmpty) Text( recipient.name ?? '', style: TextStyle(fontWeight: FontWeight.normal), ), ], ), ], ), ), ), ), ), ), ], ); } }