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';
|
||||
|
||||
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)}';
|
||||
|
||||
@@ -370,6 +370,15 @@ class _ConfirmScreenState extends State<ConfirmScreen>
|
||||
children: [
|
||||
_buildDetailRow(
|
||||
icon: Icons.monetization_on_outlined,
|
||||
label: "Currency",
|
||||
value: controller
|
||||
.transactionController
|
||||
.model
|
||||
.confirmationData["debitCurrency"]
|
||||
.toString(),
|
||||
),
|
||||
_buildDetailRow(
|
||||
icon: Icons.money,
|
||||
label: "Amount",
|
||||
value: controller
|
||||
.transactionController
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.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/transaction_controller.dart';
|
||||
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:skeletonizer/skeletonizer.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);
|
||||
|
||||
bool _isLoggedIn = false;
|
||||
bool _isZWGSelected = false;
|
||||
String initials = '';
|
||||
String _selectedCurrency = 'USD';
|
||||
|
||||
late AnimationController _providersAnimController;
|
||||
late Animation<Offset> _providersSlideAnimation;
|
||||
@@ -107,7 +107,10 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
|
||||
homeController = HomeController(context);
|
||||
await homeController.getAccounts();
|
||||
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();
|
||||
|
||||
@@ -127,10 +130,22 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
|
||||
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 {
|
||||
await homeController.getAccounts();
|
||||
await homeController.getCategories();
|
||||
await homeController.getProviders(_isZWGSelected ? "ZWG" : "USD");
|
||||
await homeController.getProviders(_selectedCurrency);
|
||||
|
||||
prefs = await SharedPreferences.getInstance();
|
||||
if (prefs.getString("userId") != null) {
|
||||
@@ -153,9 +168,21 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
|
||||
|
||||
/// Handles the repeat action for a transaction.
|
||||
Future<void> _repeatTransaction(Map<String, dynamic> item) async {
|
||||
final receiptController = ReceiptController(context);
|
||||
final transaction = txn.TransactionModel.fromJson(item);
|
||||
await receiptController.repeatTransaction(transaction);
|
||||
try {
|
||||
final receiptController = ReceiptController(context);
|
||||
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) {
|
||||
context.push('/make-payment');
|
||||
}
|
||||
@@ -370,7 +397,11 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
|
||||
_buildLoginPrompt(theme, isDark),
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
if (_isLoggedIn) const AccountBalanceWidget(),
|
||||
if (_isLoggedIn)
|
||||
AccountBalanceWidget(
|
||||
selectedCurrency: _selectedCurrency,
|
||||
onCurrencySelected: _onCurrencySelected,
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
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:flutter/material.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:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import '../../../models/user_model.dart';
|
||||
|
||||
class LoginScreenModel {
|
||||
String username = "";
|
||||
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
|
||||
// auth-gated redirects (e.g. the groups flow) are re-evaluated
|
||||
// and the user is sent to their originally requested page.
|
||||
|
||||
@@ -89,7 +89,7 @@ class PayController extends ChangeNotifier {
|
||||
type: "CONFIRM",
|
||||
billClientId: model.selectedProvider?.clientId ?? '',
|
||||
debitRef: 'Velocity',
|
||||
debitCurrency: 'USD',
|
||||
debitCurrency: transactionController.model.currency,
|
||||
creditAccount: transactionController.model.formData.creditAccount,
|
||||
creditPhone: transactionController.model.formData.creditPhone,
|
||||
billName: model.selectedProvider?.name ?? '',
|
||||
|
||||
@@ -259,6 +259,12 @@ class _PayScreenState extends State<PayScreen> with TickerProviderStateMixin {
|
||||
.formData
|
||||
.creditAccount,
|
||||
),
|
||||
_buildDetailRow(
|
||||
icon: Icons.monetization_on,
|
||||
label: "Currency",
|
||||
value:
|
||||
transactionController.model.currency,
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
@@ -643,7 +649,7 @@ class _PayScreenState extends State<PayScreen> with TickerProviderStateMixin {
|
||||
const SizedBox(height: 5),
|
||||
DropdownButtonHideUnderline(
|
||||
child: DropdownButtonFormField<String>(
|
||||
initialValue: controller.model.selectedProduct?.uid,
|
||||
initialValue: transactionController.model.selectedProduct?.name,
|
||||
isExpanded: true,
|
||||
icon: const Icon(Icons.arrow_drop_down),
|
||||
decoration: InputDecoration(
|
||||
@@ -661,10 +667,10 @@ class _PayScreenState extends State<PayScreen> with TickerProviderStateMixin {
|
||||
DropdownMenuItem(child: Text("Select Product")),
|
||||
...controller.model.products.map(
|
||||
(e) => DropdownMenuItem<String>(
|
||||
value: e.uid,
|
||||
value: e.name,
|
||||
child: Text(
|
||||
'${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),
|
||||
Text(
|
||||
"TRANSACTION DETAILS",
|
||||
"PROVIDER DETAILS",
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w700,
|
||||
|
||||
@@ -82,8 +82,8 @@ class ReceiptController extends ChangeNotifier {
|
||||
Map<String, dynamic> provider = await http.get(
|
||||
'/public/providers/client/$providerId',
|
||||
);
|
||||
transactionController.model.selectedProvider = hc.BillProvider.fromJson(
|
||||
provider,
|
||||
transactionController.updateSelectedProvider(
|
||||
hc.BillProvider.fromJson(provider),
|
||||
);
|
||||
|
||||
// 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
|
||||
String providerUid =
|
||||
transactionController.model.selectedProvider?.uid ?? '';
|
||||
String productUid = transaction.productUid ?? '';
|
||||
String providerUid = transactionController.model.selectedProvider?.id ?? '';
|
||||
String productName = transaction.billProductName ?? '';
|
||||
|
||||
if (productUid.isNotEmpty) {
|
||||
if (productName.isNotEmpty) {
|
||||
Map<String, dynamic> product = await http.get(
|
||||
'/public/providers/$providerUid/products/$productUid',
|
||||
'/public/providers/$providerUid/products/$productName',
|
||||
);
|
||||
transactionController.model.selectedProduct = pc.BillProduct.fromJson(
|
||||
product,
|
||||
transactionController.updateSelectedProduct(
|
||||
pc.BillProduct.fromJson(product),
|
||||
);
|
||||
}
|
||||
|
||||
transactionController.updateCurrency(transaction.debitCurrency);
|
||||
|
||||
// update credit amount, notification phone number, payment processor
|
||||
transactionController.model.formData = transactionController.model.formData
|
||||
.copyWith(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/foundation.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:provider/provider.dart';
|
||||
import 'package:qpay/models/responsive_policy.dart';
|
||||
import 'package:qpay/models/user_model.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';
|
||||
|
||||
import '../onboarding/auth_provider.dart';
|
||||
|
||||
class RecipientsScreen extends StatefulWidget {
|
||||
const RecipientsScreen({super.key});
|
||||
|
||||
@@ -66,6 +70,16 @@ class _RecipientsScreenState extends State<RecipientsScreen>
|
||||
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
|
||||
void dispose() {
|
||||
_animationController.dispose();
|
||||
@@ -577,7 +591,7 @@ class _RecipientsScreenState extends State<RecipientsScreen>
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
"TRANSACTION DETAILS",
|
||||
"PROVIDER DETAILS",
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w700,
|
||||
@@ -592,12 +606,18 @@ class _RecipientsScreenState extends State<RecipientsScreen>
|
||||
const SizedBox(height: 16),
|
||||
if (children != null)
|
||||
...children
|
||||
else
|
||||
else ...[
|
||||
_buildDetailRow(
|
||||
icon: Icons.business_outlined,
|
||||
label: label,
|
||||
value: value ?? '',
|
||||
),
|
||||
_buildDetailRow(
|
||||
icon: Icons.monetization_on,
|
||||
label: "Currency",
|
||||
value: transactionController.model.currency,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -14,6 +14,7 @@ class TransactionModel {
|
||||
BillProduct? selectedProduct;
|
||||
BillProvider? selectedProvider;
|
||||
Map<String, dynamic>? receiptData;
|
||||
String currency = 'USD';
|
||||
FormData formData = FormData(
|
||||
type: '',
|
||||
billClientId: '',
|
||||
@@ -118,6 +119,11 @@ class TransactionController extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
updateCurrency(String currency) {
|
||||
model.currency = currency;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
clearFormData() {
|
||||
model.formData = FormData(
|
||||
id: null,
|
||||
|
||||
@@ -46,13 +46,13 @@ class _TransactionItemWidgetState extends State<TransactionItemWidget> {
|
||||
final statusIcon = isSuccess
|
||||
? Icons.check_circle_rounded
|
||||
: isPending
|
||||
? Icons.pending_rounded
|
||||
: Icons.cancel_rounded;
|
||||
? Icons.pending_rounded
|
||||
: Icons.cancel_rounded;
|
||||
final statusColor = isSuccess
|
||||
? const Color(0xFF10B981)
|
||||
: isPending
|
||||
? const Color(0xFFF59E0B)
|
||||
: const Color(0xFFEF4444);
|
||||
? const Color(0xFFF59E0B)
|
||||
: const Color(0xFFEF4444);
|
||||
|
||||
return Skeletonizer(
|
||||
enabled: widget.enabled,
|
||||
@@ -66,8 +66,7 @@ class _TransactionItemWidgetState extends State<TransactionItemWidget> {
|
||||
context.push("/receipt/${widget.transaction['id']}");
|
||||
},
|
||||
child: Container(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 12, horizontal: 14),
|
||||
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 14),
|
||||
decoration: BoxDecoration(
|
||||
color: isDark ? const Color(0xFF1A1A2E) : Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
@@ -138,11 +137,7 @@ class _TransactionItemWidgetState extends State<TransactionItemWidget> {
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Icon(
|
||||
statusIcon,
|
||||
size: 16,
|
||||
color: statusColor,
|
||||
),
|
||||
Icon(statusIcon, size: 16, color: statusColor),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
@@ -172,9 +167,7 @@ class _TransactionItemWidgetState extends State<TransactionItemWidget> {
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
(widget.transaction['amount'] as num?)
|
||||
?.toStringAsFixed(2) ??
|
||||
'',
|
||||
'${(widget.transaction['amount'] as num?)?.toStringAsFixed(2) ?? ''} ${widget.transaction['debitCurrency']}',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w700,
|
||||
@@ -182,9 +175,7 @@ class _TransactionItemWidgetState extends State<TransactionItemWidget> {
|
||||
),
|
||||
),
|
||||
Text(
|
||||
(widget.transaction['totalAmount'] as num?)
|
||||
?.toStringAsFixed(2) ??
|
||||
'',
|
||||
'${(widget.transaction['totalAmount'] as num?)?.toStringAsFixed(2) ?? ''} ${widget.transaction['debitCurrency']}',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
@@ -246,4 +237,4 @@ class _TransactionItemWidgetState extends State<TransactionItemWidget> {
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user