added zwg support

This commit is contained in:
Prince
2026-06-17 14:42:46 +02:00
parent cc4b02f3c9
commit 64eaa38079
12 changed files with 324 additions and 247 deletions

View File

@@ -0,0 +1,43 @@
class UserModel {
final String token;
final String type;
final String username;
final String email;
final String phone;
final String firstName;
final String uuid;
UserModel({
required this.token,
required this.type,
required this.username,
required this.email,
required this.phone,
required this.firstName,
required this.uuid,
});
factory UserModel.fromJson(Map<String, dynamic> json) {
return UserModel(
token: json['token'] as String,
type: json['type'] as String,
username: json['username'] as String,
email: json['email'] as String,
phone: json['phone'] as String,
firstName: json['firstName'] as String,
uuid: json['uuid'] as String,
);
}
Map<String, dynamic> toJson() {
return {
'token': token,
'type': type,
'username': username,
'email': email,
'phone': phone,
'firstName': firstName,
'uuid': uuid,
};
}
}

View File

@@ -5,14 +5,20 @@ import 'package:qpay/screens/accounts/models/account_model.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
class AccountBalanceWidget extends StatefulWidget { class AccountBalanceWidget extends StatefulWidget {
const AccountBalanceWidget({super.key}); final String? selectedCurrency;
final ValueChanged<String>? onCurrencySelected;
const AccountBalanceWidget({
super.key,
this.selectedCurrency,
this.onCurrencySelected,
});
@override @override
State<AccountBalanceWidget> createState() => _AccountBalanceWidgetState(); State<AccountBalanceWidget> createState() => _AccountBalanceWidgetState();
} }
class _AccountBalanceWidgetState extends State<AccountBalanceWidget> class _AccountBalanceWidgetState extends State<AccountBalanceWidget> {
with SingleTickerProviderStateMixin {
late AccountProvider _accountProvider; late AccountProvider _accountProvider;
AccountModel? _usdAccount; AccountModel? _usdAccount;
@@ -23,22 +29,15 @@ class _AccountBalanceWidgetState extends State<AccountBalanceWidget>
String? _zwgError; String? _zwgError;
String? _phoneNumber; String? _phoneNumber;
late AnimationController _pulseController;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_pulseController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 800),
);
_accountProvider = AccountProvider(); _accountProvider = AccountProvider();
_loadBalances(); _loadBalances();
} }
@override @override
void dispose() { void dispose() {
_pulseController.dispose();
_accountProvider.dispose(); _accountProvider.dispose();
super.dispose(); super.dispose();
} }
@@ -85,10 +84,6 @@ class _AccountBalanceWidgetState extends State<AccountBalanceWidget>
} }
}); });
} }
if (mounted) {
_pulseController.forward();
}
} }
@override @override
@@ -101,46 +96,39 @@ class _AccountBalanceWidgetState extends State<AccountBalanceWidget>
} }
return Padding( return Padding(
padding: const EdgeInsets.only(bottom: 16), padding: const EdgeInsets.only(bottom: 12),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
_buildSectionLabel(
theme,
isDark,
Icons.account_balance_wallet_rounded,
'Account Balances',
),
const SizedBox(height: 12),
Row( Row(
children: [ children: [
Expanded( Expanded(
child: _buildBalanceCard( child: _buildCompactCard(
theme: theme, theme: theme,
isDark: isDark, isDark: isDark,
currencyCode: 'USD', currencyCode: 'USD',
currencyName: 'US Dollar',
flagAsset: 'united-states.png', flagAsset: 'united-states.png',
balance: _usdAccount?.balance, balance: _usdAccount?.balance,
isLoading: _isLoadingUsd, isLoading: _isLoadingUsd,
error: _usdError, error: _usdError,
isSelected: widget.selectedCurrency == 'USD',
gradientColors: [ gradientColors: [
const Color(0xFF0D47A1), const Color(0xFF0D47A1),
const Color(0xFF1976D2), const Color(0xFF1976D2),
], ],
), ),
), ),
const SizedBox(width: 12), const SizedBox(width: 10),
Expanded( Expanded(
child: _buildBalanceCard( child: _buildCompactCard(
theme: theme, theme: theme,
isDark: isDark, isDark: isDark,
currencyCode: 'ZWG', currencyCode: 'ZWG',
currencyName: 'Zimbabwe Gold',
flagAsset: 'zimbabwe.png', flagAsset: 'zimbabwe.png',
balance: _zwgAccount?.balance, balance: _zwgAccount?.balance,
isLoading: _isLoadingZwG, isLoading: _isLoadingZwG,
error: _zwgError, error: _zwgError,
isSelected: widget.selectedCurrency == 'ZWG',
gradientColors: [ gradientColors: [
const Color(0xFF1B5E20), const Color(0xFF1B5E20),
const Color(0xFF388E3C), const Color(0xFF388E3C),
@@ -149,42 +137,38 @@ class _AccountBalanceWidgetState extends State<AccountBalanceWidget>
), ),
], ],
), ),
const SizedBox(height: 8), const SizedBox(height: 6),
Center( Center(
child: TextButton.icon( child: TextButton.icon(
onPressed: _isLoadingUsd || _isLoadingZwG onPressed: _isLoadingUsd || _isLoadingZwG
? null ? null
: () { : () {
_pulseController.reset();
_loadBalances(); _loadBalances();
}, },
icon: AnimatedBuilder( icon: Icon(
animation: _pulseController, Icons.refresh_rounded,
builder: (context, child) { size: 14,
return Transform.rotate( color: _isLoadingUsd || _isLoadingZwG
angle: _pulseController.value * 6.2832, ? (isDark ? Colors.white24 : Colors.grey.shade400)
child: Icon( : theme.colorScheme.primary,
Icons.refresh_rounded,
size: 16,
color: _isLoadingUsd || _isLoadingZwG
? (isDark ? Colors.white24 : Colors.grey.shade400)
: theme.colorScheme.primary,
),
);
},
), ),
label: Text( label: Text(
_isLoadingUsd || _isLoadingZwG _isLoadingUsd || _isLoadingZwG
? 'Refreshing...' ? 'Refreshing...'
: 'Refresh Balances', : 'Refresh',
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 11,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: _isLoadingUsd || _isLoadingZwG color: _isLoadingUsd || _isLoadingZwG
? (isDark ? Colors.white24 : Colors.grey.shade400) ? (isDark ? Colors.white24 : Colors.grey.shade400)
: theme.colorScheme.primary, : theme.colorScheme.primary,
), ),
), ),
style: TextButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
minimumSize: Size.zero,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
), ),
), ),
], ],
@@ -192,197 +176,158 @@ class _AccountBalanceWidgetState extends State<AccountBalanceWidget>
); );
} }
Widget _buildBalanceCard({ Widget _buildCompactCard({
required ThemeData theme, required ThemeData theme,
required bool isDark, required bool isDark,
required String currencyCode, required String currencyCode,
required String currencyName,
required String flagAsset, required String flagAsset,
double? balance, double? balance,
required bool isLoading, required bool isLoading,
String? error, String? error,
required bool isSelected,
required List<Color> gradientColors, required List<Color> gradientColors,
}) { }) {
return Container( return GestureDetector(
padding: const EdgeInsets.all(16), onTap: () {
decoration: BoxDecoration( widget.onCurrencySelected?.call(currencyCode);
borderRadius: BorderRadius.circular(20), },
gradient: LinearGradient( child: AnimatedContainer(
colors: gradientColors, duration: const Duration(milliseconds: 200),
begin: Alignment.topLeft, padding: const EdgeInsets.all(12),
end: Alignment.bottomRight, decoration: BoxDecoration(
), borderRadius: BorderRadius.circular(16),
boxShadow: [ gradient: LinearGradient(
BoxShadow( colors: gradientColors,
color: gradientColors[0].withValues(alpha: 0.3), begin: Alignment.topLeft,
blurRadius: 12, end: Alignment.bottomRight,
offset: const Offset(0, 4),
), ),
], boxShadow: [
), BoxShadow(
child: Column( color: gradientColors[0].withValues(alpha: isSelected ? 0.5 : 0.25),
crossAxisAlignment: CrossAxisAlignment.start, blurRadius: isSelected ? 12 : 6,
children: [ offset: Offset(0, isSelected ? 4 : 2),
// Top row: flag + currency code ),
Row( ],
mainAxisAlignment: MainAxisAlignment.spaceBetween, border: isSelected
children: [ ? Border.all(
Row( color: Colors.white.withValues(alpha: 0.8),
mainAxisSize: MainAxisSize.min, width: 2,
children: [ )
Container( : Border.all(
width: 24, color: Colors.white.withValues(alpha: 0.15),
height: 24, width: 1,
decoration: BoxDecoration( ),
shape: BoxShape.circle, ),
border: Border.all( child: Column(
color: Colors.white.withValues(alpha: 0.3), crossAxisAlignment: CrossAxisAlignment.start,
width: 1.5, mainAxisSize: MainAxisSize.min,
), children: [
// Top row: flag + currency code
Row(
children: [
Container(
width: 22,
height: 22,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: Colors.white.withValues(alpha: 0.3),
width: 1.5,
), ),
child: ClipOval( ),
child: Image.asset( child: ClipOval(
'assets/$flagAsset', child: Image.asset(
fit: BoxFit.cover, 'assets/$flagAsset',
errorBuilder: (_, __, ___) => Icon( fit: BoxFit.cover,
Icons.monetization_on_outlined, errorBuilder: (_, __, ___) => Icon(
size: 14, Icons.monetization_on_outlined,
color: Colors.white.withValues(alpha: 0.8), size: 12,
), color: Colors.white.withValues(alpha: 0.8),
), ),
), ),
), ),
const SizedBox(width: 8), ),
Text( const SizedBox(width: 6),
Expanded(
child: Text(
currencyCode, currencyCode,
style: const TextStyle( style: const TextStyle(
fontSize: 13, fontSize: 12,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: Colors.white, color: Colors.white,
letterSpacing: 0.5, letterSpacing: 0.5,
), ),
), ),
],
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.2),
borderRadius: BorderRadius.circular(8),
), ),
child: Text( if (isSelected)
currencyName, Container(
style: TextStyle( width: 18,
fontSize: 8, height: 18,
fontWeight: FontWeight.w500, decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.9), color: Colors.white.withValues(alpha: 0.3),
shape: BoxShape.circle,
),
child: const Icon(
Icons.check_rounded,
size: 12,
color: Colors.white,
),
), ),
],
),
const SizedBox(height: 10),
// Balance amount
if (isLoading)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 70,
height: 12,
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.3),
borderRadius: BorderRadius.circular(4),
),
),
],
)
else if (error != null)
Row(
children: [
Icon(
Icons.error_outline_rounded,
color: Colors.white.withValues(alpha: 0.7),
size: 16,
),
const SizedBox(width: 4),
Text(
'Unavailable',
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.w500,
color: Colors.white.withValues(alpha: 0.7),
),
),
],
)
else ...[
Text(
_formatBalance(balance ?? 0.0),
style: const TextStyle(
fontSize: 17,
fontWeight: FontWeight.w800,
color: Colors.white,
letterSpacing: 0.5,
height: 1.1,
), ),
), ),
], ],
),
const SizedBox(height: 16),
// Balance amount
if (isLoading)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 80,
height: 14,
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.3),
borderRadius: BorderRadius.circular(4),
),
),
const SizedBox(height: 4),
Container(
width: 50,
height: 10,
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.2),
borderRadius: BorderRadius.circular(4),
),
),
],
)
else if (error != null)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(
Icons.error_outline_rounded,
color: Colors.white.withValues(alpha: 0.7),
size: 20,
),
const SizedBox(height: 4),
Text(
'Unavailable',
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w500,
color: Colors.white.withValues(alpha: 0.7),
),
),
],
)
else ...[
Text(
_formatBalance(balance ?? 0.0),
style: const TextStyle(
fontSize: 22,
fontWeight: FontWeight.w800,
color: Colors.white,
letterSpacing: 0.5,
height: 1.1,
),
),
const SizedBox(height: 4),
Text(
currencyCode == 'USD' ? 'Available Balance' : 'Available Balance',
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.w400,
color: Colors.white.withValues(alpha: 0.7),
),
),
], ],
], ),
), ),
); );
} }
Widget _buildSectionLabel(
ThemeData theme,
bool isDark,
IconData icon,
String label,
) {
return Row(
children: [
Container(
width: 32,
height: 32,
decoration: BoxDecoration(
color: theme.colorScheme.primary.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(10),
),
child: Icon(icon, size: 16, color: theme.colorScheme.primary),
),
const SizedBox(width: 10),
Text(
label,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: isDark ? Colors.white : Colors.black87,
letterSpacing: 0.3,
),
),
],
);
}
String _formatBalance(double balance) { String _formatBalance(double balance) {
final formatter = NumberFormat('#,##0.00', 'en_US'); final formatter = NumberFormat('#,##0.00', 'en_US');
return '\$${formatter.format(balance)}'; return '\$${formatter.format(balance)}';

View File

@@ -370,6 +370,15 @@ class _ConfirmScreenState extends State<ConfirmScreen>
children: [ children: [
_buildDetailRow( _buildDetailRow(
icon: Icons.monetization_on_outlined, icon: Icons.monetization_on_outlined,
label: "Currency",
value: controller
.transactionController
.model
.confirmationData["debitCurrency"]
.toString(),
),
_buildDetailRow(
icon: Icons.money,
label: "Amount", label: "Amount",
value: controller value: controller
.transactionController .transactionController

View File

@@ -1,3 +1,4 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@@ -8,7 +9,6 @@ import 'package:qpay/screens/home/home_controller.dart';
import 'package:qpay/screens/receipt/receipt_controller.dart'; import 'package:qpay/screens/receipt/receipt_controller.dart';
import 'package:qpay/screens/transaction_controller.dart'; import 'package:qpay/screens/transaction_controller.dart';
import 'package:qpay/screens/transactions/transaction_model.dart' as txn; import 'package:qpay/screens/transactions/transaction_model.dart' as txn;
import 'package:qpay/widgets/app_snack_bar.dart';
import 'package:qpay/widgets/transaction_item_widget.dart'; import 'package:qpay/widgets/transaction_item_widget.dart';
import 'package:skeletonizer/skeletonizer.dart'; import 'package:skeletonizer/skeletonizer.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
@@ -28,8 +28,8 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
late final router = GoRouter.of(context); late final router = GoRouter.of(context);
bool _isLoggedIn = false; bool _isLoggedIn = false;
bool _isZWGSelected = false;
String initials = ''; String initials = '';
String _selectedCurrency = 'USD';
late AnimationController _providersAnimController; late AnimationController _providersAnimController;
late Animation<Offset> _providersSlideAnimation; late Animation<Offset> _providersSlideAnimation;
@@ -107,7 +107,10 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
homeController = HomeController(context); homeController = HomeController(context);
await homeController.getAccounts(); await homeController.getAccounts();
await homeController.getCategories(); await homeController.getCategories();
await homeController.getProviders(_isZWGSelected ? "ZWG" : "USD"); await homeController.getProviders(_selectedCurrency);
transactionController.model.formData = transactionController.model.formData
.copyWith(debitCurrency: _selectedCurrency);
prefs = await SharedPreferences.getInstance(); prefs = await SharedPreferences.getInstance();
@@ -127,10 +130,22 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
await homeController.getTransactions(prefs.getString("userId")!); await homeController.getTransactions(prefs.getString("userId")!);
} }
void _onCurrencySelected(String currency) {
if (_selectedCurrency == currency) return;
setState(() {
_selectedCurrency = currency;
});
homeController.getProviders(currency);
transactionController.updateCurrency(currency);
}
Future<void> _onRefresh() async { Future<void> _onRefresh() async {
await homeController.getAccounts(); await homeController.getAccounts();
await homeController.getCategories(); await homeController.getCategories();
await homeController.getProviders(_isZWGSelected ? "ZWG" : "USD"); await homeController.getProviders(_selectedCurrency);
prefs = await SharedPreferences.getInstance(); prefs = await SharedPreferences.getInstance();
if (prefs.getString("userId") != null) { if (prefs.getString("userId") != null) {
@@ -153,9 +168,21 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
/// Handles the repeat action for a transaction. /// Handles the repeat action for a transaction.
Future<void> _repeatTransaction(Map<String, dynamic> item) async { Future<void> _repeatTransaction(Map<String, dynamic> item) async {
final receiptController = ReceiptController(context); try {
final transaction = txn.TransactionModel.fromJson(item); final receiptController = ReceiptController(context);
await receiptController.repeatTransaction(transaction); final transaction = txn.TransactionModel.fromJson(item);
await receiptController.repeatTransaction(transaction);
} catch (e, s) {
if (kDebugMode) {
print(e);
print(s);
}
if (mounted) {
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text('Error repeating transaction')));
}
}
if (mounted) { if (mounted) {
context.push('/make-payment'); context.push('/make-payment');
} }
@@ -370,7 +397,11 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
_buildLoginPrompt(theme, isDark), _buildLoginPrompt(theme, isDark),
const SizedBox(height: 20), const SizedBox(height: 20),
], ],
if (_isLoggedIn) const AccountBalanceWidget(), if (_isLoggedIn)
AccountBalanceWidget(
selectedCurrency: _selectedCurrency,
onCurrencySelected: _onCurrencySelected,
),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [

View File

@@ -0,0 +1,18 @@
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
import '../../models/user_model.dart';
class AuthProvider {
// get the user model from shared preferences
static Future<UserModel?> getUserModel() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
String? userModelString = prefs.getString("userModel");
if (userModelString != null) {
Map<String, dynamic> userModelJson = jsonDecode(userModelString);
return UserModel.fromJson(userModelJson);
}
return null;
}
}

View File

@@ -1,3 +1,5 @@
import 'dart:convert';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:qpay/auth_state.dart'; import 'package:qpay/auth_state.dart';
@@ -5,6 +7,8 @@ import 'package:qpay/http/http.dart';
import 'package:qpay/models/api_response.dart'; import 'package:qpay/models/api_response.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import '../../../models/user_model.dart';
class LoginScreenModel { class LoginScreenModel {
String username = ""; String username = "";
String password = ""; String password = "";
@@ -72,6 +76,9 @@ class LoginController extends ChangeNotifier {
: ''), : ''),
); );
UserModel userModel = UserModel.fromJson(response);
prefs.setString("userModel", jsonEncode(userModel));
// Notify the router that the user is now signed in so any // Notify the router that the user is now signed in so any
// auth-gated redirects (e.g. the groups flow) are re-evaluated // auth-gated redirects (e.g. the groups flow) are re-evaluated
// and the user is sent to their originally requested page. // and the user is sent to their originally requested page.

View File

@@ -89,7 +89,7 @@ class PayController extends ChangeNotifier {
type: "CONFIRM", type: "CONFIRM",
billClientId: model.selectedProvider?.clientId ?? '', billClientId: model.selectedProvider?.clientId ?? '',
debitRef: 'Velocity', debitRef: 'Velocity',
debitCurrency: 'USD', debitCurrency: transactionController.model.currency,
creditAccount: transactionController.model.formData.creditAccount, creditAccount: transactionController.model.formData.creditAccount,
creditPhone: transactionController.model.formData.creditPhone, creditPhone: transactionController.model.formData.creditPhone,
billName: model.selectedProvider?.name ?? '', billName: model.selectedProvider?.name ?? '',

View File

@@ -259,6 +259,12 @@ class _PayScreenState extends State<PayScreen> with TickerProviderStateMixin {
.formData .formData
.creditAccount, .creditAccount,
), ),
_buildDetailRow(
icon: Icons.monetization_on,
label: "Currency",
value:
transactionController.model.currency,
),
], ],
), ),
SizedBox(height: 20), SizedBox(height: 20),
@@ -643,7 +649,7 @@ class _PayScreenState extends State<PayScreen> with TickerProviderStateMixin {
const SizedBox(height: 5), const SizedBox(height: 5),
DropdownButtonHideUnderline( DropdownButtonHideUnderline(
child: DropdownButtonFormField<String>( child: DropdownButtonFormField<String>(
initialValue: controller.model.selectedProduct?.uid, initialValue: transactionController.model.selectedProduct?.name,
isExpanded: true, isExpanded: true,
icon: const Icon(Icons.arrow_drop_down), icon: const Icon(Icons.arrow_drop_down),
decoration: InputDecoration( decoration: InputDecoration(
@@ -661,10 +667,10 @@ class _PayScreenState extends State<PayScreen> with TickerProviderStateMixin {
DropdownMenuItem(child: Text("Select Product")), DropdownMenuItem(child: Text("Select Product")),
...controller.model.products.map( ...controller.model.products.map(
(e) => DropdownMenuItem<String>( (e) => DropdownMenuItem<String>(
value: e.uid, value: e.name,
child: Text( child: Text(
'${e.displayName} - ' '${e.displayName} - '
'${transactionController.model.formData.debitCurrency}${e.defaultAmount}', '${transactionController.model.currency}${e.defaultAmount}',
), ),
), ),
), ),
@@ -718,7 +724,7 @@ class _PayScreenState extends State<PayScreen> with TickerProviderStateMixin {
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
Text( Text(
"TRANSACTION DETAILS", "PROVIDER DETAILS",
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,

View File

@@ -82,8 +82,8 @@ class ReceiptController extends ChangeNotifier {
Map<String, dynamic> provider = await http.get( Map<String, dynamic> provider = await http.get(
'/public/providers/client/$providerId', '/public/providers/client/$providerId',
); );
transactionController.model.selectedProvider = hc.BillProvider.fromJson( transactionController.updateSelectedProvider(
provider, hc.BillProvider.fromJson(provider),
); );
// update tran controller formdata with credit account, email, name, phone & providerLabel // update tran controller formdata with credit account, email, name, phone & providerLabel
@@ -96,19 +96,20 @@ class ReceiptController extends ChangeNotifier {
); );
// fetch product and update tran controller formdata // fetch product and update tran controller formdata
String providerUid = String providerUid = transactionController.model.selectedProvider?.id ?? '';
transactionController.model.selectedProvider?.uid ?? ''; String productName = transaction.billProductName ?? '';
String productUid = transaction.productUid ?? '';
if (productUid.isNotEmpty) { if (productName.isNotEmpty) {
Map<String, dynamic> product = await http.get( Map<String, dynamic> product = await http.get(
'/public/providers/$providerUid/products/$productUid', '/public/providers/$providerUid/products/$productName',
); );
transactionController.model.selectedProduct = pc.BillProduct.fromJson( transactionController.updateSelectedProduct(
product, pc.BillProduct.fromJson(product),
); );
} }
transactionController.updateCurrency(transaction.debitCurrency);
// update credit amount, notification phone number, payment processor // update credit amount, notification phone number, payment processor
transactionController.model.formData = transactionController.model.formData transactionController.model.formData = transactionController.model.formData
.copyWith( .copyWith(

View File

@@ -1,4 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -6,11 +7,14 @@ import 'package:flutter_contacts/flutter_contacts.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:qpay/models/responsive_policy.dart'; import 'package:qpay/models/responsive_policy.dart';
import 'package:qpay/models/user_model.dart';
import 'package:qpay/screens/transaction_controller.dart'; import 'package:qpay/screens/transaction_controller.dart';
import 'package:skeletonizer/skeletonizer.dart'; import 'package:skeletonizer/skeletonizer.dart';
import 'package:qpay/screens/recipient/recipients_controller.dart'; import 'package:qpay/screens/recipient/recipients_controller.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import '../onboarding/auth_provider.dart';
class RecipientsScreen extends StatefulWidget { class RecipientsScreen extends StatefulWidget {
const RecipientsScreen({super.key}); const RecipientsScreen({super.key});
@@ -66,6 +70,16 @@ class _RecipientsScreenState extends State<RecipientsScreen>
controller.getRecipients(_queryParams); controller.getRecipients(_queryParams);
} }
Future<UserModel?> getAuthenticatedUser() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
String? userModelString = prefs.getString("userModel");
if (userModelString != null) {
Map<String, dynamic> userModelJson = jsonDecode(userModelString);
return UserModel.fromJson(userModelJson);
}
return null;
}
@override @override
void dispose() { void dispose() {
_animationController.dispose(); _animationController.dispose();
@@ -577,7 +591,7 @@ class _RecipientsScreenState extends State<RecipientsScreen>
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
Text( Text(
"TRANSACTION DETAILS", "PROVIDER DETAILS",
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
@@ -592,12 +606,18 @@ class _RecipientsScreenState extends State<RecipientsScreen>
const SizedBox(height: 16), const SizedBox(height: 16),
if (children != null) if (children != null)
...children ...children
else else ...[
_buildDetailRow( _buildDetailRow(
icon: Icons.business_outlined, icon: Icons.business_outlined,
label: label, label: label,
value: value ?? '', value: value ?? '',
), ),
_buildDetailRow(
icon: Icons.monetization_on,
label: "Currency",
value: transactionController.model.currency,
),
],
], ],
), ),
), ),

View File

@@ -14,6 +14,7 @@ class TransactionModel {
BillProduct? selectedProduct; BillProduct? selectedProduct;
BillProvider? selectedProvider; BillProvider? selectedProvider;
Map<String, dynamic>? receiptData; Map<String, dynamic>? receiptData;
String currency = 'USD';
FormData formData = FormData( FormData formData = FormData(
type: '', type: '',
billClientId: '', billClientId: '',
@@ -118,6 +119,11 @@ class TransactionController extends ChangeNotifier {
notifyListeners(); notifyListeners();
} }
updateCurrency(String currency) {
model.currency = currency;
notifyListeners();
}
clearFormData() { clearFormData() {
model.formData = FormData( model.formData = FormData(
id: null, id: null,

View File

@@ -46,13 +46,13 @@ class _TransactionItemWidgetState extends State<TransactionItemWidget> {
final statusIcon = isSuccess final statusIcon = isSuccess
? Icons.check_circle_rounded ? Icons.check_circle_rounded
: isPending : isPending
? Icons.pending_rounded ? Icons.pending_rounded
: Icons.cancel_rounded; : Icons.cancel_rounded;
final statusColor = isSuccess final statusColor = isSuccess
? const Color(0xFF10B981) ? const Color(0xFF10B981)
: isPending : isPending
? const Color(0xFFF59E0B) ? const Color(0xFFF59E0B)
: const Color(0xFFEF4444); : const Color(0xFFEF4444);
return Skeletonizer( return Skeletonizer(
enabled: widget.enabled, enabled: widget.enabled,
@@ -66,8 +66,7 @@ class _TransactionItemWidgetState extends State<TransactionItemWidget> {
context.push("/receipt/${widget.transaction['id']}"); context.push("/receipt/${widget.transaction['id']}");
}, },
child: Container( child: Container(
padding: padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 14),
const EdgeInsets.symmetric(vertical: 12, horizontal: 14),
decoration: BoxDecoration( decoration: BoxDecoration(
color: isDark ? const Color(0xFF1A1A2E) : Colors.white, color: isDark ? const Color(0xFF1A1A2E) : Colors.white,
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
@@ -138,11 +137,7 @@ class _TransactionItemWidgetState extends State<TransactionItemWidget> {
), ),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
Icon( Icon(statusIcon, size: 16, color: statusColor),
statusIcon,
size: 16,
color: statusColor,
),
], ],
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
@@ -172,9 +167,7 @@ class _TransactionItemWidgetState extends State<TransactionItemWidget> {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Text( Text(
(widget.transaction['amount'] as num?) '${(widget.transaction['amount'] as num?)?.toStringAsFixed(2) ?? ''} ${widget.transaction['debitCurrency']}',
?.toStringAsFixed(2) ??
'',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
@@ -182,9 +175,7 @@ class _TransactionItemWidgetState extends State<TransactionItemWidget> {
), ),
), ),
Text( Text(
(widget.transaction['totalAmount'] as num?) '${(widget.transaction['totalAmount'] as num?)?.toStringAsFixed(2) ?? ''} ${widget.transaction['debitCurrency']}',
?.toStringAsFixed(2) ??
'',
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
@@ -246,4 +237,4 @@ class _TransactionItemWidgetState extends State<TransactionItemWidget> {
), ),
); );
} }
} }