diff options
Diffstat (limited to 'wallet/src/main/java/net/taler/wallet/MainViewModel.kt')
-rw-r--r-- | wallet/src/main/java/net/taler/wallet/MainViewModel.kt | 151 |
1 files changed, 96 insertions, 55 deletions
diff --git a/wallet/src/main/java/net/taler/wallet/MainViewModel.kt b/wallet/src/main/java/net/taler/wallet/MainViewModel.kt index 2ad6f6b..82eb8d7 100644 --- a/wallet/src/main/java/net/taler/wallet/MainViewModel.kt +++ b/wallet/src/main/java/net/taler/wallet/MainViewModel.kt @@ -22,127 +22,146 @@ import androidx.annotation.UiThread import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.distinctUntilChanged import androidx.lifecycle.viewModelScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.getAndUpdate import kotlinx.coroutines.launch +import kotlinx.serialization.encodeToString import net.taler.common.Amount import net.taler.common.AmountParserException import net.taler.common.Event import net.taler.common.toEvent import net.taler.wallet.accounts.AccountManager +import net.taler.wallet.backend.BackendManager import net.taler.wallet.backend.NotificationPayload import net.taler.wallet.backend.NotificationReceiver +import net.taler.wallet.backend.TalerErrorInfo import net.taler.wallet.backend.VersionReceiver import net.taler.wallet.backend.WalletBackendApi import net.taler.wallet.backend.WalletCoreVersion -import net.taler.wallet.balances.BalanceItem -import net.taler.wallet.balances.BalanceResponse +import net.taler.wallet.backend.WalletRunConfig +import net.taler.wallet.backend.WalletRunConfig.Testing +import net.taler.wallet.balances.BalanceManager +import net.taler.wallet.balances.ScopeInfo import net.taler.wallet.deposit.DepositManager +import net.taler.wallet.events.ObservabilityEvent import net.taler.wallet.exchanges.ExchangeManager import net.taler.wallet.payment.PaymentManager import net.taler.wallet.peer.PeerManager -import net.taler.wallet.pending.PendingOperationsManager import net.taler.wallet.refund.RefundManager import net.taler.wallet.settings.SettingsManager -import net.taler.wallet.tip.TipManager import net.taler.wallet.transactions.TransactionManager import net.taler.wallet.withdraw.WithdrawManager import org.json.JSONObject const val TAG = "taler-wallet" +const val OBSERVABILITY_LIMIT = 100 private val transactionNotifications = listOf( - "proposal-accepted", - "refresh-revealed", - "withdraw-group-finished" + "transaction-state-transition", +) + +private val observabilityNotifications = listOf( + "task-observability-event", + "request-observability-event", ) class MainViewModel( app: Application, ) : AndroidViewModel(app), VersionReceiver, NotificationReceiver { - private val mBalances = MutableLiveData<List<BalanceItem>>() - val balances: LiveData<List<BalanceItem>> = mBalances.distinctUntilChanged() + private val mDevMode = MutableLiveData(BuildConfig.DEBUG) + val devMode: LiveData<Boolean> = mDevMode - val devMode = MutableLiveData(BuildConfig.DEBUG) val showProgressBar = MutableLiveData<Boolean>() + var walletVersion: String? = null + private set + var walletVersionHash: String? = null + private set var exchangeVersion: String? = null private set var merchantVersion: String? = null private set - private val api = WalletBackendApi(app, this, this) + @set:Synchronized + private var walletConfig = WalletRunConfig( + testing = Testing( + emitObservabilityEvents = true, + devModeActive = devMode.value ?: false, + ) + ) + private val api = WalletBackendApi(app, walletConfig, this, this) + + val networkManager = NetworkManager(app.applicationContext) val withdrawManager = WithdrawManager(api, viewModelScope) - val tipManager = TipManager(api, viewModelScope) val paymentManager = PaymentManager(api, viewModelScope) - val pendingOperationsManager: PendingOperationsManager = - PendingOperationsManager(api, viewModelScope) val transactionManager: TransactionManager = TransactionManager(api, viewModelScope) val refundManager = RefundManager(api, viewModelScope) + val balanceManager = BalanceManager(api, viewModelScope) val exchangeManager: ExchangeManager = ExchangeManager(api, viewModelScope) val peerManager: PeerManager = PeerManager(api, exchangeManager, viewModelScope) - val settingsManager: SettingsManager = SettingsManager(app.applicationContext, viewModelScope) + val settingsManager: SettingsManager = SettingsManager(app.applicationContext, api, viewModelScope) val accountManager: AccountManager = AccountManager(api, viewModelScope) val depositManager: DepositManager = DepositManager(api, viewModelScope) - private val mTransactionsEvent = MutableLiveData<Event<String>>() - val transactionsEvent: LiveData<Event<String>> = mTransactionsEvent + private val mTransactionsEvent = MutableLiveData<Event<ScopeInfo>>() + val transactionsEvent: LiveData<Event<ScopeInfo>> = mTransactionsEvent + + private val mObservabilityLog = MutableStateFlow<List<ObservabilityEvent>>(emptyList()) + val observabilityLog: StateFlow<List<ObservabilityEvent>> = mObservabilityLog private val mScanCodeEvent = MutableLiveData<Event<Boolean>>() val scanCodeEvent: LiveData<Event<Boolean>> = mScanCodeEvent override fun onVersionReceived(versionInfo: WalletCoreVersion) { + walletVersion = versionInfo.implementationSemver + walletVersionHash = versionInfo.implementationGitHash exchangeVersion = versionInfo.exchange merchantVersion = versionInfo.merchant } override fun onNotificationReceived(payload: NotificationPayload) { if (payload.type == "waiting-for-retry") return // ignore ping) - Log.i(TAG, "Received notification from wallet-core: $payload") - loadBalances() + val str = BackendManager.json.encodeToString(payload) + Log.i(TAG, "Received notification from wallet-core: $str") + + // Only update balances when we're told they changed + if (payload.type == "balance-change") viewModelScope.launch(Dispatchers.Main) { + balanceManager.loadBalances() + } + + if (payload.type in observabilityNotifications && payload.event != null) { + mObservabilityLog.getAndUpdate { logs -> + logs.takeLast(OBSERVABILITY_LIMIT) + .toMutableList().apply { + add(payload.event) + } + } + } + if (payload.type in transactionNotifications) viewModelScope.launch(Dispatchers.Main) { // TODO notification API should give us a currency to update // update currently selected transaction list transactionManager.loadTransactions() } - // refresh pending ops and history with each notification - if (devMode.value == true) { - pendingOperationsManager.getPending() - } - } - - @UiThread - fun loadBalances(): Job = viewModelScope.launch { - showProgressBar.value = true - val response = api.request("getBalances", BalanceResponse.serializer()) - showProgressBar.value = false - response.onError { - // TODO expose in UI - Log.e(TAG, "Error retrieving balances: $it") - } - response.onSuccess { - mBalances.value = it.balances - } } /** - * Navigates to the given currency's transaction list, when [MainFragment] is shown. + * Navigates to the given scope info's transaction list, when [MainFragment] is shown. */ @UiThread - fun showTransactions(currency: String) { - mTransactionsEvent.value = currency.toEvent() + fun showTransactions(scopeInfo: ScopeInfo) { + mTransactionsEvent.value = scopeInfo.toEvent() } @UiThread - fun getCurrencies(): List<String> { - return balances.value?.map { balanceItem -> - balanceItem.currency - } ?: emptyList() - } + fun getCurrencies() = balanceManager.balances.value?.map { balanceItem -> + balanceItem.currency + } ?: emptyList() @UiThread fun createAmount(amountText: String, currency: String): AmountResult { @@ -157,7 +176,7 @@ class MainViewModel( @UiThread fun hasSufficientBalance(amount: Amount): Boolean { - balances.value?.forEach { balanceItem -> + balanceManager.balances.value?.forEach { balanceItem -> if (balanceItem.currency == amount.currency) { return balanceItem.available >= amount } @@ -167,11 +186,8 @@ class MainViewModel( @UiThread fun dangerouslyReset() { - viewModelScope.launch { - api.sendRequest("reset") - } withdrawManager.testWithdrawalStatus.value = null - mBalances.value = emptyList() + balanceManager.resetBalances() } fun startTunnel() { @@ -197,13 +213,30 @@ class MainViewModel( mScanCodeEvent.value = true.toEvent() } + fun setDevMode(enabled: Boolean, onError: (error: TalerErrorInfo) -> Unit) { + mDevMode.postValue(enabled) + viewModelScope.launch { + val config = walletConfig.copy( + testing = walletConfig.testing?.copy( + devModeActive = enabled, + ) ?: Testing( + devModeActive = enabled, + ), + ) + + api.setWalletConfig(config) + .onSuccess { + walletConfig = config + }.onError(onError) + } + } + fun runIntegrationTest() { viewModelScope.launch { - api.request<Unit>("runIntegrationTest") { + api.request<Unit>("runIntegrationTestV2") { put("amountToWithdraw", "KUDOS:42") put("amountToSpend", "KUDOS:23") - put("bankBaseUrl", "https://bank.demo.taler.net/") - put("bankAccessApiBaseUrl", "https://bank.demo.taler.net/demobanks/default/access-api/") + put("corebankApiBaseUrl", "https://bank.demo.taler.net/") put("exchangeBaseUrl", "https://exchange.demo.taler.net/") put("merchantBaseUrl", "https://backend.demo.taler.net/") put("merchantAuthToken", "secret-token:sandbox") @@ -211,6 +244,14 @@ class MainViewModel( } } + fun applyDevExperiment(uri: String, onError: (error: TalerErrorInfo) -> Unit) { + viewModelScope.launch { + api.request<Unit>("applyDevExperiment") { + put("devExperimentUri", uri) + }.onError(onError) + } + } + } sealed class AmountResult { |