updated the look and feel of the home page

This commit is contained in:
2026-05-28 11:47:38 +02:00
parent 6fdd3183fb
commit 88aad6ec3e
12 changed files with 1225 additions and 735 deletions

View File

@@ -7,9 +7,10 @@ import 'package:qpay/models/api_response.dart';
import 'package:qpay/screens/pay/pay_controller.dart' as pc;
import 'package:qpay/screens/transaction_controller.dart' as tc;
import 'package:qpay/screens/home/home_controller.dart' as hc;
import 'package:qpay/screens/transactions/transaction_model.dart';
class ReceiptModel {
Map<String, dynamic>? receiptData;
TransactionModel? transaction;
bool isLoading = false;
String? errorMessage;
String status = '';
@@ -35,7 +36,7 @@ class ReceiptController extends ChangeNotifier {
notifyListeners();
}
Future<ApiResponse<Map<String, dynamic>>> doIntegration(String id) async {
Future<ApiResponse<TransactionModel>> doIntegration(String id) async {
try {
model.isLoading = true;
model.status = '';
@@ -51,9 +52,13 @@ class ReceiptController extends ChangeNotifier {
if (response['status'] == 'SUCCESS') {
model.status = response['status'];
transactionController.updateReceiptData(response);
transactionController.updateReceiptData(
Map<String, dynamic>.from(response),
);
notifyListeners();
return ApiResponse.success(Map<String, dynamic>.from(response));
return ApiResponse.success(
TransactionModel.fromJson(Map<String, dynamic>.from(response)),
);
} else {
model.status = response['status'];
model.errorMessage = response['errorMessage'];
@@ -70,10 +75,10 @@ class ReceiptController extends ChangeNotifier {
}
}
Future<void> repeatTransaction(BuildContext context) async {
Future<void> repeatTransaction(TransactionModel transaction) async {
setLoading(true);
// fetch provider & update tran controller
String providerId = model.receiptData?['billClientId'] ?? '';
String providerId = transaction.billClientId ?? '';
Map<String, dynamic> provider = await http.get(
'/public/providers/client/$providerId',
);
@@ -84,16 +89,16 @@ class ReceiptController extends ChangeNotifier {
// update tran controller formdata with credit account, email, name, phone & providerLabel
transactionController.model.formData = transactionController.model.formData
.copyWith(
creditAccount: model.receiptData?['creditAccount'] ?? '',
creditPhone: model.receiptData?['creditPhone'] ?? '',
creditName: model.receiptData?['creditName'] ?? '',
creditEmail: model.receiptData?['creditEmail'] ?? '',
creditAccount: transaction.creditAccount ?? '',
creditPhone: transaction.creditPhone ?? '',
creditName: transaction.creditName ?? '',
creditEmail: transaction.creditEmail ?? '',
);
// fetch product and update tran controller formdata
String providerUid =
transactionController.model.selectedProvider?.uid ?? '';
String productUid = model.receiptData?['productUid'] ?? '';
String productUid = transaction.productUid ?? '';
if (productUid.isNotEmpty) {
Map<String, dynamic> product = await http.get(
@@ -105,27 +110,26 @@ class ReceiptController extends ChangeNotifier {
}
// update credit amount, notification phone number, payment processor
transactionController
.model
.formData = transactionController.model.formData.copyWith(
amount: model.receiptData?['amount'].toStringAsFixed(2) ?? '',
debitPhone: model.receiptData?['debitPhone'] ?? '',
paymentProcessorLabel: model.receiptData?['paymentProcessorLabel'] ?? '',
paymentProcessorName: model.receiptData?['paymentProcessorName'] ?? '',
id: null,
);
transactionController.model.formData = transactionController.model.formData
.copyWith(
amount: transaction.amount.toStringAsFixed(2),
debitPhone: transaction.debitPhone,
paymentProcessorLabel: transaction.paymentProcessorLabel,
paymentProcessorName: transaction.paymentProcessorName,
id: null,
);
setLoading(false);
context.push('/make-payment');
}
Future<ApiResponse<Map<String, dynamic>>> getReceiptData(String id) async {
Future<ApiResponse<TransactionModel>> getReceiptData(String id) async {
setLoading(true);
try {
Map<String, dynamic> response = await http.get('/public/transaction/$id');
model.receiptData = response;
final transaction = TransactionModel.fromJson(response);
model.transaction = transaction;
setLoading(false);
return ApiResponse.success(response);
return ApiResponse.success(transaction);
} catch (e) {
setLoading(false);
return ApiResponse.failure(
@@ -134,8 +138,8 @@ class ReceiptController extends ChangeNotifier {
}
}
void updateReceiptData(Map<String, dynamic> data) {
model.receiptData = data;
void updateReceiptTransaction(TransactionModel data) {
model.transaction = data;
notifyListeners();
}
}

View File

@@ -3,15 +3,20 @@ import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
import 'package:qpay/models/responsive_policy.dart';
import 'package:qpay/screens/gateway/gateway_controller.dart';
import 'package:qpay/screens/transaction_controller.dart';
import 'package:qpay/screens/transaction_controller.dart'
show TransactionController;
import 'package:go_router/go_router.dart';
import 'package:qpay/screens/receipt/receipt_controller.dart';
import 'package:qpay/screens/transactions/transaction_model.dart';
import 'package:share_plus/share_plus.dart';
import 'package:skeletonizer/skeletonizer.dart';
import 'package:widgets_to_image/widgets_to_image.dart';
import 'package:decimal/decimal.dart';
class ReceiptScreen extends StatefulWidget {
const ReceiptScreen({super.key});
final String? transactionId;
const ReceiptScreen({super.key, this.transactionId});
@override
State<ReceiptScreen> createState() => _ReceiptScreenState();
@@ -36,6 +41,7 @@ class _ReceiptScreenState extends State<ReceiptScreen>
late Animation<double> _contentFadeAnimation;
int _selectedTabIndex = 0;
TransactionModel? _transaction;
late ReceiptController receiptController;
final _formKey = GlobalKey<FormState>();
@@ -52,9 +58,7 @@ class _ReceiptScreenState extends State<ReceiptScreen>
receiptController = ReceiptController(context);
gatewayController = GatewayController(context);
receiptController.getReceiptData(
transactionController.model.receiptData?["id"],
);
_fetchTransaction();
// Header animation
_headerAnimController = AnimationController(
@@ -114,6 +118,22 @@ class _ReceiptScreenState extends State<ReceiptScreen>
});
}
Future<void> _fetchTransaction() async {
final id =
widget.transactionId ??
transactionController.model.receiptData?["id"] ??
receiptController.model.transaction?.id;
if (id == null) return;
final response = await receiptController.getReceiptData(id);
if (response.isSuccess && response.data != null) {
setState(() {
_transaction = response.data;
});
}
}
Future<void> _captureImage() async {
final image = await controller.capture();
if (image != null) {
@@ -129,25 +149,21 @@ class _ReceiptScreenState extends State<ReceiptScreen>
_retry() async {
receiptController.setLoading(true);
await receiptController.doIntegration(
receiptController.model.receiptData?["id"],
);
final id = _transaction?.id ?? '';
await receiptController.doIntegration(id);
if (receiptController.model.status == "SUCCESS") {
receiptController.getReceiptData(
receiptController.model.receiptData?["id"],
);
await _fetchTransaction();
}
receiptController.setLoading(false);
}
_check() async {
receiptController.setLoading(true);
await gatewayController.poll(receiptController.model.receiptData?["id"]);
final id = _transaction?.id ?? '';
await gatewayController.poll(id);
if (gatewayController.model.status == "SUCCESS") {
receiptController.getReceiptData(
transactionController.model.receiptData?["id"],
);
if (transactionController.model.receiptData?["pollingStatus"]) {
await _fetchTransaction();
if (_transaction?.pollingStatus == "SUCCESS") {
receiptController.setLoading(false);
return;
}
@@ -193,22 +209,10 @@ class _ReceiptScreenState extends State<ReceiptScreen>
? const Color(0xFF0D0D0D)
: const Color(0xFFF8F9FA),
leading: context.canPop()
? Padding(
padding: const EdgeInsets.only(left: 8),
child: Container(
margin: const EdgeInsets.symmetric(vertical: 8),
decoration: BoxDecoration(
color: theme.colorScheme.primary.withValues(
alpha: 0.08,
),
borderRadius: BorderRadius.circular(14),
),
child: IconButton(
onPressed: () => context.pop(),
icon: const Icon(Icons.arrow_back_rounded),
color: theme.colorScheme.primary,
),
),
? IconButton(
onPressed: () => context.pop(),
icon: const Icon(Icons.arrow_back_rounded),
color: theme.colorScheme.primary,
)
: const SizedBox.shrink(),
title: Text(
@@ -359,13 +363,11 @@ class _ReceiptScreenState extends State<ReceiptScreen>
// Receipt Header Card
// ──────────────────────────────────────────────────────────────
Widget _buildReceiptHeader(ThemeData theme, bool isDark) {
final status =
transactionController.model.receiptData?["integrationStatus"];
final amount = receiptController.model.receiptData?["amount"];
final currency =
receiptController.model.receiptData?["debitCurrency"] ?? "";
final billName = receiptController.model.receiptData?["billName"] ?? "";
final createdAt = receiptController.model.receiptData?["createdAt"];
final status = _transaction?.integrationStatus;
final amount = _transaction?.amount;
final currency = _transaction?.debitCurrency ?? "";
final billName = _transaction?.billName ?? "";
final createdAt = _transaction?.createdAt;
final primaryColor = theme.colorScheme.primary;
@@ -458,7 +460,7 @@ class _ReceiptScreenState extends State<ReceiptScreen>
// Date
if (createdAt != null)
Text(
DateFormat.yMMMd().add_jm().format(DateTime.parse(createdAt)),
DateFormat.yMMMd().add_jm().format(createdAt),
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w400,
@@ -502,9 +504,8 @@ class _ReceiptScreenState extends State<ReceiptScreen>
// Action Buttons Row
// ──────────────────────────────────────────────────────────────
Widget _buildActionButtons(ThemeData theme) {
final integrationStatus =
receiptController.model.receiptData?["integrationStatus"];
final paymentStatus = receiptController.model.receiptData?["paymentStatus"];
final integrationStatus = _transaction?.integrationStatus;
final paymentStatus = _transaction?.paymentStatus;
return Wrap(
spacing: 12,
@@ -516,7 +517,14 @@ class _ReceiptScreenState extends State<ReceiptScreen>
label: "Repeat",
color: theme.colorScheme.primary,
isLoading: receiptController.model.isLoading,
onPressed: () => receiptController.repeatTransaction(context),
onPressed: () async {
await receiptController.repeatTransaction(
receiptController.model.transaction!,
);
if (mounted) {
context.push('/make-payment');
}
},
),
_modernActionButton(
icon: Icons.ios_share_rounded,
@@ -534,8 +542,7 @@ class _ReceiptScreenState extends State<ReceiptScreen>
onPressed: _retry,
),
],
if (receiptController.model.receiptData?["pollingStatus"] !=
"SUCCESS") ...[
if (_transaction?.pollingStatus != "SUCCESS") ...[
_modernActionButton(
icon: Icons.search_rounded,
label: "Check",
@@ -705,8 +712,8 @@ class _ReceiptScreenState extends State<ReceiptScreen>
// Provider Details Tab
// ──────────────────────────────────────────────────────────────
Widget _buildProviderDetailsTab(ThemeData theme, bool isDark) {
final receiptData = receiptController.model.receiptData;
final additionalData = receiptData?["additionalData"];
final receiptData = receiptController.model.transaction;
final additionalData = receiptData?.additionalData;
final items = <_DetailItem>[];
@@ -715,14 +722,13 @@ class _ReceiptScreenState extends State<ReceiptScreen>
_DetailItem(
icon: Icons.circle_outlined,
label: "Status",
value:
transactionController.model.receiptData?["integrationStatus"] ?? "",
value: _transaction?.integrationStatus ?? "",
),
);
// Error message
final errorMsg = transactionController.model.receiptData?["errorMessage"];
if (errorMsg != null && errorMsg != "") {
final errorMsg = _transaction?.errorMessage;
if (errorMsg != null && errorMsg.isNotEmpty) {
items.add(
_DetailItem(
icon: Icons.warning_amber_rounded,
@@ -751,37 +757,37 @@ class _ReceiptScreenState extends State<ReceiptScreen>
_DetailItem(
icon: Icons.tag_rounded,
label: "Debit Reference",
value: transactionController.model.receiptData?["debitRef"] ?? "",
value: _transaction?.debitRef ?? "",
),
);
items.add(
_DetailItem(
icon: Icons.phone_iphone_rounded,
label: "From",
value: receiptData?["debitPhone"] ?? "",
value: receiptData?.debitPhone ?? "",
),
);
items.add(
_DetailItem(
icon: Icons.account_balance_wallet_rounded,
label: "To",
value: receiptData?["creditAccount"] ?? "",
value: receiptData?.creditAccount ?? "",
),
);
items.add(
_DetailItem(
icon: Icons.memory_rounded,
label: "Processor",
value: receiptData?["paymentProcessorName"] ?? "",
value: receiptData?.paymentProcessorName ?? "",
),
);
if (receiptData?["billProductName"] != null) {
if (receiptData?.billProductName != null) {
items.add(
_DetailItem(
icon: Icons.category_rounded,
label: "Bill Product",
value: receiptData?["billProductName"] ?? "",
value: receiptData?.billProductName ?? "",
),
);
}
@@ -822,12 +828,12 @@ class _ReceiptScreenState extends State<ReceiptScreen>
// Transaction Details Tab
// ──────────────────────────────────────────────────────────────
Widget _buildTransactionDetailsTab(ThemeData theme, bool isDark) {
final receiptData = transactionController.model.receiptData;
final amount = receiptData?["amount"] ?? 0;
final charge = receiptData?["charge"] ?? 0;
final gatewayCharge = receiptData?["gatewayCharge"] ?? 0;
final tax = receiptData?["tax"] ?? 0;
final totalAmount = receiptData?["totalAmount"] ?? 0;
final t = _transaction;
final amount = t?.amount ?? Decimal.zero;
final charge = t?.charge ?? Decimal.zero;
final gatewayCharge = t?.gatewayCharge ?? Decimal.zero;
final tax = t?.tax ?? Decimal.zero;
final totalAmount = t?.totalAmount ?? Decimal.zero;
final items = <_DetailItem>[
_DetailItem(
@@ -842,7 +848,7 @@ class _ReceiptScreenState extends State<ReceiptScreen>
),
];
if (charge != 0) {
if (charge != Decimal.zero) {
items.add(
_DetailItem(
icon: Icons.percent_rounded,
@@ -851,7 +857,7 @@ class _ReceiptScreenState extends State<ReceiptScreen>
),
);
}
if (gatewayCharge != 0) {
if (gatewayCharge != Decimal.zero) {
items.add(
_DetailItem(
icon: Icons.account_balance_rounded,
@@ -860,7 +866,7 @@ class _ReceiptScreenState extends State<ReceiptScreen>
),
);
}
if (tax != 0) {
if (tax != Decimal.zero) {
items.add(
_DetailItem(
icon: Icons.receipt_rounded,