// Haveno App extends the features of Haveno, supporting mobile devices and more. // Copyright (C) 2024 Kewbit (https://kewbit.org) // Source Code: https://git.haveno.com/haveno/haveno-app.git // // Author: Kewbit // Website: https://kewbit.org // Contact Email: me@kewbit.org // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . import 'package:flutter/material.dart'; import 'package:haveno/grpc_models.dart'; import 'package:haveno/haveno_client.dart'; import 'package:haveno/profobuf_models.dart'; import 'package:haveno_app/models/schema.dart'; import 'package:haveno_app/utils/database_helper.dart'; class PaymentAccountsProvider with ChangeNotifier, CooldownMixin { final HavenoChannel _havenoChannel; final DatabaseHelper _databaseHelper = DatabaseHelper.instance; List _paymentMethods = []; List _cryptoCurrencyPaymentMethods = []; List _paymentAccounts = []; final List _paymentAccountForms = []; final Map _paymentMethodIdToPaymentAccountFormIdMap = {}; final Map _paymentAccountFormIdToPaymentAccountFormMap = {}; PaymentAccountsProvider(this._havenoChannel) { setCooldownDurations({ 'getPaymentAccounts': const Duration(minutes: 1), // 2 minutes cooldown for getOffers }); } List get paymentMethods => _paymentMethods; List get cryptoCurrencyPaymentMethods => _cryptoCurrencyPaymentMethods; List get paymentAccounts => _paymentAccounts; List? get paymentAccountForms => _paymentAccountForms.isNotEmpty ? _paymentAccountForms : null; Future?> getPaymentMethods() async { await _havenoChannel.onConnected; try { // Attempt to load payment methods from the local database _paymentMethods = await _databaseHelper.getAllPaymentMethods(); if (_paymentMethods.isNotEmpty) { return _paymentMethods; } } catch (e) { print("Failed to load payment methods from the local database: $e"); } // If the local database does not have the data, fetch it from the remote service try { final getPaymentMethodsReply = await _havenoChannel.paymentAccountsClient! .getPaymentMethods(GetPaymentMethodsRequest()); _paymentMethods = getPaymentMethodsReply.paymentMethods; // Optionally, save the retrieved payment methods to the local database if (_paymentMethods.isNotEmpty) { for (var paymentMethod in _paymentMethods) { await _databaseHelper.insertPaymentMethod(paymentMethod); } } } catch (e) { print("Failed to get payment methods from the remote service: $e"); rethrow; } return _paymentMethods; } Future> getPaymentAccounts() async { await _havenoChannel.onConnected; if (await isCooldownValid('getPaymentAccounts')) { try { // Attempt to load payment accounts from the local database _paymentAccounts = await _databaseHelper.getAllPaymentAccounts(); if (_paymentAccounts.isNotEmpty) { return _paymentAccounts; } } catch (e) { print("Failed to load payment accounts from the local database: $e"); } } else { try { final getPaymentAccountsReply = await _havenoChannel .paymentAccountsClient! .getPaymentAccounts(GetPaymentAccountsRequest()); _paymentAccounts = getPaymentAccountsReply.paymentAccounts; print("Found ${_paymentAccounts.length} payment accounts on the daemon, will try caching them..."); updateCooldown('getPaymentAccounts'); } catch (e) { updateCooldown('getPaymentAccounts'); print("Failed to get payment accounts from daemon: $e"); rethrow; } try { for (var paymentAccount in _paymentAccounts) { await _databaseHelper.insertPaymentAccount(paymentAccount); } print("Successfully synced ${_paymentAccounts.length} payment accounts from the daemon to local storage"); } catch (e) { print("Could not add the payment accounts from the daemon to the local database: ${e.toString()}"); } return _paymentAccounts; } return []; } Future?> getCryptoCurrencyPaymentMethods() async { await _havenoChannel.onConnected; try { final getCryptoCurrencyPaymentMethodsReply = await _havenoChannel .paymentAccountsClient! .getCryptoCurrencyPaymentMethods( GetCryptoCurrencyPaymentMethodsRequest()); _cryptoCurrencyPaymentMethods = getCryptoCurrencyPaymentMethodsReply.paymentMethods; } catch (e) { print("Failed to get payment accounts: $e"); } return paymentMethods; } Future getPaymentAccountForm(String paymentMethodId) async { await _havenoChannel.onConnected; PaymentAccountForm? paymentAccountForm; bool didLoadFromDb = false; try { // Attempt to load payment account form from the local database paymentAccountForm = await _databaseHelper.getPaymentAccountFormByPaymentMethodId(paymentMethodId); if (paymentAccountForm != null) { didLoadFromDb = true; print("Loaded payment account form from local database for paymentMethod ID: $paymentMethodId."); return paymentAccountForm; } } catch (e) { print("Failed to load payment account form from the local database: $e"); } if (!didLoadFromDb) { try { // Fetch from the remote service if not found locally final paymentAccountFormReply = await _havenoChannel.paymentAccountsClient!.getPaymentAccountForm( GetPaymentAccountFormRequest(paymentMethodId: paymentMethodId), ); if (paymentAccountFormReply.hasPaymentAccountForm()) { paymentAccountForm = paymentAccountFormReply.paymentAccountForm; print("Loaded payment account form from remote service for paymentMethod ID: $paymentMethodId."); // Store the fetched form in the local database for future use await _databaseHelper.insertPaymentAccountForm(paymentMethodId, paymentAccountForm); // Add a delay to respect network cooldown limitations, but this needs improving #TODO CooldownManager, or // EVEN BETTER, remove cooldowns from the daemon completely, I'mm a await Future.delayed(const Duration(seconds: 4)); } else { return null; } } catch (e) { print("Failed to get the payment form from remote service: $e"); rethrow; } } return paymentAccountForm; } Future?> getAllPaymentAccountForms() async { await _havenoChannel.onConnected; if (_paymentMethods.isEmpty) { _paymentMethods = (await getPaymentMethods())!; } for (var paymentMethod in _paymentMethods) { var paymentAccountForm = await getPaymentAccountForm(paymentMethod.id); if (paymentAccountForm == null) continue; if (!_paymentAccountForms.contains(paymentAccountForm)) { _paymentAccountForms.add(paymentAccountForm); } } print("There are currently ${(await _databaseHelper.getAllPaymentAccountForms())?.length ?? 'an unknown number of'} payment account forms in the database."); return _paymentAccountForms; } Future createPaymentAccount(String paymentMethodId, PaymentAccountForm form) async { await _havenoChannel.onConnected; try { notifyListeners(); final createdPaymentAccount = await _havenoChannel.paymentAccountsClient! .createPaymentAccount( CreatePaymentAccountRequest(paymentAccountForm: form)); var paymentAccount = createdPaymentAccount.paymentAccount; print("Created Payment Account: $paymentAccount"); notifyListeners(); return paymentAccount; } catch (e) { print("Failed to create payment account: $e"); rethrow; } } }