added zwg support
This commit is contained in:
43
lib/models/user_model.dart
Normal file
43
lib/models/user_model.dart
Normal 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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)}';
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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: [
|
||||||
|
|||||||
18
lib/screens/onboarding/auth_provider.dart
Normal file
18
lib/screens/onboarding/auth_provider.dart
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.
|
||||||
|
|||||||
@@ -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 ?? '',
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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,
|
||||||
|
),
|
||||||
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user