import 'package:flutter/material.dart'; import 'package:qpay/http/http.dart'; import 'package:qpay/models/api_response.dart'; import 'package:qpay/models/pageable_model.dart'; class HistoryModel { PageableModel? pageableModel; List> transactions = []; bool isLoading = false; bool isLoadingMore = false; bool isActing = false; String? errorMessage; int currentPage = 0; int totalPages = 0; String searchQuery = ''; String? statusFilter; String? typeFilter; bool selectMode = false; Set selectedTransactionIds = {}; } class HistoryController extends ChangeNotifier { final HistoryModel model = HistoryModel(); final Http http = Http(); BuildContext context; bool _disposed = false; HistoryController(this.context); @override void dispose() { _disposed = true; super.dispose(); } void _safeNotify() { if (!_disposed) notifyListeners(); } String buildQueryParameters(Map params) { if (params.isEmpty) { return ''; } String query = ''; params.forEach((key, value) { query += '$key=$value&'; }); return query.substring(0, query.length - 1); } Future>>> getTransactions( Map params, ) async { final isFirstPage = params['page'] == '0' || params['page'] == null; if (isFirstPage) { model.isLoading = true; model.transactions = []; model.currentPage = 0; } else { model.isLoadingMore = true; } _safeNotify(); params['partyType'] = 'INDIVIDUAL'; try { Map response = await http.get( '/public/transaction?${buildQueryParameters(params)}', ); model.pageableModel = PageableModel.fromJson(response); model.currentPage = model.pageableModel!.number; model.totalPages = model.pageableModel!.totalPages; final newItems = model.pageableModel!.content .map((e) => Map.from(e as Map)) .toList(); if (isFirstPage) { model.transactions = newItems; } else { model.transactions.addAll(newItems); } model.isLoading = false; model.isLoadingMore = false; _safeNotify(); return ApiResponse.success(model.transactions); } catch (e) { logger.e(e); model.errorMessage = e.toString(); if (isFirstPage) { model.transactions = []; } model.isLoading = false; model.isLoadingMore = false; _safeNotify(); return ApiResponse.failure( "Problem fetching transactions, are you connected to the internet?", ); } } Future searchTransactions( String userId, { String? search, String? status, String? type, }) async { model.searchQuery = search ?? ''; model.statusFilter = status; model.typeFilter = type; final params = { 'userId': userId, 'page': '0', 'size': '20', 'sort': 'createdAt,desc', }; if (search != null && search.isNotEmpty) { params['creditAccount'] = search; params['creditEmail'] = search; params['creditPhone'] = search; params['creditName'] = search; params['billName'] = search; params['amount'] = search; } if (status != null && status.isNotEmpty) { params['status'] = status; } if (type != null && type.isNotEmpty) { params['type'] = type; } await getTransactions(params); } Future loadNextPage(String userId) async { if (model.isLoadingMore || model.currentPage + 1 >= model.totalPages) { return; } final params = { 'userId': userId, 'page': (model.currentPage + 1).toString(), 'size': '20', 'sort': 'createdAt,desc', }; if (model.searchQuery.isNotEmpty) { params['creditAccount'] = model.searchQuery; params['creditEmail'] = model.searchQuery; params['creditPhone'] = model.searchQuery; params['creditName'] = model.searchQuery; params['billName'] = model.searchQuery; params['amount'] = model.searchQuery; } if (model.statusFilter != null && model.statusFilter!.isNotEmpty) { params['status'] = model.statusFilter!; } if (model.typeFilter != null && model.typeFilter!.isNotEmpty) { params['type'] = model.typeFilter!; } await getTransactions(params); } void toggleSelectMode() { model.selectMode = !model.selectMode; if (!model.selectMode) { model.selectedTransactionIds.clear(); } _safeNotify(); } void toggleTransactionSelection(String id) { if (id.isEmpty) return; if (model.selectedTransactionIds.contains(id)) { model.selectedTransactionIds.remove(id); } else { model.selectedTransactionIds.add(id); } _safeNotify(); } /// Removes selected transactions from the local list. The history /// endpoint does not support deletes, so this is a client-side action. void deleteSelectedTransactions() { final ids = List.from(model.selectedTransactionIds); model.transactions.removeWhere((t) => ids.contains(t['id'] as String?)); model.selectMode = false; model.selectedTransactionIds.clear(); _safeNotify(); } void clearFilters() { model.statusFilter = null; model.typeFilter = null; _safeNotify(); } List> getFakeTransactions() { return [ { "id": "77b2c479-b803-49fc-b5c3-acbf52cba633", "trace": "22a3044b-8f53-4e43-b4f5-c77dcec48d2d", "amount": 25.0, "charge": 0.0, "gatewayCharge": 0.25, "tax": 0.5, "totalAmount": 25.75, "reference": "c838136c-c015-4a0b-9c7c-52f127c8c109", "paymentProcessorLabel": "MPGS", "paymentProcessorImage": "visa-mastercard.png", "debitPhone": "", "debitAccount": "", "debitCurrency": "USD", "debitRef": "Velocity", "creditPhone": "", "creditAccount": "07088597534", "billClientId": "powertel_zesa", "billName": "Zesa", "type": "REQUEST", "authType": "WEB", "errorMessage": "", "responseCode": "00", "status": "SUCCESS", "sdkActionId": "0aa0d90d-6132-48a7-a5d8-e796f757c73f", }, ]; } }