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

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