From 17e69838a22564135ce796493d4137cbb0be0690 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Tue, 2 Apr 2024 14:30:34 -0700 Subject: [PATCH] feat(coinjoin): group mixing transactions on home screen (#1272) * feat: add grouping of CoinJoin transactions * feat: create multiple groups * fix: add method to obtain wrappers --- .../dash/wallet/common/WalletDataProvider.kt | 3 +- .../transactions/TransactionWrapperFactory.kt | 25 +++++++++ .../crowdnode/api/CrowdNodeBlockchainApi.kt | 2 +- .../FullCrowdNodeSignUpTxSetFactory.kt | 17 ++++++ .../res/drawable/ic_coinjoin_mixing_group.xml | 13 +++++ wallet/res/values/strings.xml | 2 + .../schildbach/wallet/WalletApplication.java | 5 +- .../wallet/service/CoinJoinService.kt | 1 - .../transactions/TransactionWrapperHelper.kt | 20 ++++--- .../coinjoin/CoinJoinMixingFilter.kt | 39 +++++++++++++ .../coinjoin/CoinJoinMixingTxSet.kt | 53 ++++++++++++++++++ .../coinjoin/CoinJoinTxResourceMapper.kt | 53 ++++++++++++++++++ .../coinjoin/CoinJoinTxWrapperFactory.kt | 55 +++++++++++++++++++ .../wallet/ui/main/MainViewModel.kt | 6 +- .../TransactionGroupDetailsFragment.kt | 19 +++++-- .../transactions/TransactionGroupViewModel.kt | 10 ++-- .../ui/transactions/TransactionRowView.kt | 34 +++++++++--- .../TransactionWrapperHelperTest.kt | 6 +- 18 files changed, 331 insertions(+), 32 deletions(-) create mode 100644 common/src/main/java/org/dash/wallet/common/transactions/TransactionWrapperFactory.kt create mode 100644 integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/FullCrowdNodeSignUpTxSetFactory.kt create mode 100644 wallet/res/drawable/ic_coinjoin_mixing_group.xml create mode 100644 wallet/src/de/schildbach/wallet/transactions/coinjoin/CoinJoinMixingFilter.kt create mode 100644 wallet/src/de/schildbach/wallet/transactions/coinjoin/CoinJoinMixingTxSet.kt create mode 100644 wallet/src/de/schildbach/wallet/transactions/coinjoin/CoinJoinTxResourceMapper.kt create mode 100644 wallet/src/de/schildbach/wallet/transactions/coinjoin/CoinJoinTxWrapperFactory.kt diff --git a/common/src/main/java/org/dash/wallet/common/WalletDataProvider.kt b/common/src/main/java/org/dash/wallet/common/WalletDataProvider.kt index fd1ab32b2b..e5c53bdabd 100644 --- a/common/src/main/java/org/dash/wallet/common/WalletDataProvider.kt +++ b/common/src/main/java/org/dash/wallet/common/WalletDataProvider.kt @@ -26,6 +26,7 @@ import org.bitcoinj.wallet.authentication.AuthenticationGroupExtension import org.bitcoinj.wallet.authentication.AuthenticationKeyUsage import org.dash.wallet.common.services.LeftoverBalanceException import org.dash.wallet.common.transactions.TransactionWrapper +import org.dash.wallet.common.transactions.TransactionWrapperFactory import org.dash.wallet.common.transactions.filters.TransactionFilter import kotlin.jvm.Throws @@ -59,7 +60,7 @@ interface WalletDataProvider { fun getTransactions(vararg filters: TransactionFilter): Collection - fun wrapAllTransactions(vararg wrappers: TransactionWrapper): Collection + fun wrapAllTransactions(vararg wrappers: TransactionWrapperFactory): Collection fun attachOnWalletWipedListener(listener: () -> Unit) diff --git a/common/src/main/java/org/dash/wallet/common/transactions/TransactionWrapperFactory.kt b/common/src/main/java/org/dash/wallet/common/transactions/TransactionWrapperFactory.kt new file mode 100644 index 0000000000..6fa6e90baf --- /dev/null +++ b/common/src/main/java/org/dash/wallet/common/transactions/TransactionWrapperFactory.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2024 Dash Core Group. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.dash.wallet.common.transactions + +import org.bitcoinj.core.Transaction + +interface TransactionWrapperFactory { + fun tryInclude(tx: Transaction): Pair + val wrappers: List +} diff --git a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/api/CrowdNodeBlockchainApi.kt b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/api/CrowdNodeBlockchainApi.kt index 83bb1f1224..c6a1bfe16a 100644 --- a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/api/CrowdNodeBlockchainApi.kt +++ b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/api/CrowdNodeBlockchainApi.kt @@ -218,7 +218,7 @@ open class CrowdNodeBlockchainApi @Inject constructor( open fun getFullSignUpTxSet(): FullCrowdNodeSignUpTxSet? { val wrappedTransactions = walletData.wrapAllTransactions( - FullCrowdNodeSignUpTxSet(params, walletData.transactionBag) + FullCrowdNodeSignUpTxSetFactory(params, walletData.transactionBag) ) return wrappedTransactions.firstOrNull { it is FullCrowdNodeSignUpTxSet } as? FullCrowdNodeSignUpTxSet } diff --git a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/FullCrowdNodeSignUpTxSetFactory.kt b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/FullCrowdNodeSignUpTxSetFactory.kt new file mode 100644 index 0000000000..0183231c34 --- /dev/null +++ b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/FullCrowdNodeSignUpTxSetFactory.kt @@ -0,0 +1,17 @@ +package org.dash.wallet.integrations.crowdnode.transactions + +import org.bitcoinj.core.NetworkParameters +import org.bitcoinj.core.Transaction +import org.bitcoinj.core.TransactionBag +import org.dash.wallet.common.transactions.TransactionWrapper +import org.dash.wallet.common.transactions.TransactionWrapperFactory + +class FullCrowdNodeSignUpTxSetFactory(params: NetworkParameters, transactionBag: TransactionBag) : + TransactionWrapperFactory { + private val wrapper = FullCrowdNodeSignUpTxSet(params, transactionBag) + override val wrappers = listOf(wrapper) + + override fun tryInclude(tx: Transaction): Pair { + return Pair(wrapper.tryInclude(tx), wrapper) + } +} diff --git a/wallet/res/drawable/ic_coinjoin_mixing_group.xml b/wallet/res/drawable/ic_coinjoin_mixing_group.xml new file mode 100644 index 0000000000..a9e00835ae --- /dev/null +++ b/wallet/res/drawable/ic_coinjoin_mixing_group.xml @@ -0,0 +1,13 @@ + + + + diff --git a/wallet/res/values/strings.xml b/wallet/res/values/strings.xml index 0fc083b268..5bbc947dd9 100644 --- a/wallet/res/values/strings.xml +++ b/wallet/res/values/strings.xml @@ -508,6 +508,8 @@ Turning this feature on will result a higher battery usage Select mixing level You can change or stop the mixing level at any time + Mixing Transactions + These are mixing related transactions. Intermediate Advanced Advanced users who have a very high level of technical expertise can determine your transaction history diff --git a/wallet/src/de/schildbach/wallet/WalletApplication.java b/wallet/src/de/schildbach/wallet/WalletApplication.java index cb66d34789..07a88542db 100644 --- a/wallet/src/de/schildbach/wallet/WalletApplication.java +++ b/wallet/src/de/schildbach/wallet/WalletApplication.java @@ -80,6 +80,7 @@ import org.dash.wallet.common.services.LeftoverBalanceException; import org.dash.wallet.common.services.TransactionMetadataProvider; import org.dash.wallet.common.services.analytics.AnalyticsService; +import org.dash.wallet.common.transactions.TransactionWrapperFactory; import org.dash.wallet.common.transactions.filters.TransactionFilter; import org.dash.wallet.common.transactions.TransactionWrapper; import org.dash.wallet.features.exploredash.ExploreSyncWorker; @@ -1142,11 +1143,11 @@ public Collection getTransactions(@NonNull TransactionFilter... fil @NonNull @Override - public Collection wrapAllTransactions(@NonNull TransactionWrapper... wrappers) { + public Collection wrapAllTransactions(@NonNull TransactionWrapperFactory... wrapperFactories) { org.bitcoinj.core.Context.propagate(Constants.CONTEXT); return TransactionWrapperHelper.INSTANCE.wrapTransactions( wallet.getTransactions(true), - wrappers + wrapperFactories ); } diff --git a/wallet/src/de/schildbach/wallet/service/CoinJoinService.kt b/wallet/src/de/schildbach/wallet/service/CoinJoinService.kt index 4a85a907d2..21a20ab941 100644 --- a/wallet/src/de/schildbach/wallet/service/CoinJoinService.kt +++ b/wallet/src/de/schildbach/wallet/service/CoinJoinService.kt @@ -322,7 +322,6 @@ class CoinJoinMixingService @Inject constructor( log.info( "coinjoin-state: $mode, $timeSkew ms, $hasAnonymizableBalance, $networkStatus, synced: ${blockchainState.isSynced()}, ${blockChain != null}" ) - // log.info("coinjoin-Current timeskew: ${getCurrentTimeSkew()}") this.networkStatus = networkStatus this.hasAnonymizableBalance = hasAnonymizableBalance this.blockchainState = blockchainState diff --git a/wallet/src/de/schildbach/wallet/transactions/TransactionWrapperHelper.kt b/wallet/src/de/schildbach/wallet/transactions/TransactionWrapperHelper.kt index 9755fdaa2a..006ab58a8c 100644 --- a/wallet/src/de/schildbach/wallet/transactions/TransactionWrapperHelper.kt +++ b/wallet/src/de/schildbach/wallet/transactions/TransactionWrapperHelper.kt @@ -20,14 +20,16 @@ package de.schildbach.wallet.transactions import org.bitcoinj.core.Transaction import org.bitcoinj.core.TransactionBag import org.dash.wallet.common.transactions.TransactionWrapper +import org.dash.wallet.common.transactions.TransactionWrapperFactory +import org.slf4j.LoggerFactory object TransactionWrapperHelper { + private val log = LoggerFactory.getLogger(TransactionWrapperHelper::class.java) fun wrapTransactions( transactions: Set, - vararg wrappers: TransactionWrapper + vararg wrapperFactories: TransactionWrapperFactory ): Collection { val wrappedTransactions = ArrayList() - for (transaction in transactions) { if (transaction == null) { continue @@ -39,14 +41,18 @@ object TransactionWrapperHelper { override fun getValue(bag: TransactionBag) = transaction.getValue(bag) } - if (wrappers.isNotEmpty()) { - for (wrapper in wrappers) { - if (wrapper.tryInclude(transaction)) { + if (wrapperFactories.isNotEmpty()) { + var added = false + for (wrapperFactory in wrapperFactories) { + val (included, wrapper) = wrapperFactory.tryInclude(transaction) + if (included && wrapper != null) { if (!wrappedTransactions.contains(wrapper)) { wrappedTransactions.add(wrapper) } - break + added = true } + } + if (!added) { wrappedTransactions.add(anonWrapper) } } else { @@ -56,4 +62,4 @@ object TransactionWrapperHelper { return wrappedTransactions } -} \ No newline at end of file +} diff --git a/wallet/src/de/schildbach/wallet/transactions/coinjoin/CoinJoinMixingFilter.kt b/wallet/src/de/schildbach/wallet/transactions/coinjoin/CoinJoinMixingFilter.kt new file mode 100644 index 0000000000..a4a3f9cd36 --- /dev/null +++ b/wallet/src/de/schildbach/wallet/transactions/coinjoin/CoinJoinMixingFilter.kt @@ -0,0 +1,39 @@ +/* + * Copyright 2024 Dash Core Group. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package de.schildbach.wallet.transactions.coinjoin + +import org.bitcoinj.coinjoin.utils.CoinJoinTransactionType +import org.bitcoinj.core.Transaction +import org.bitcoinj.script.Script +import org.bitcoinj.script.ScriptPattern +import org.bitcoinj.wallet.WalletEx +import org.dash.wallet.common.transactions.filters.TransactionFilter + +open class CoinJoinTxFilter(private val wallet: WalletEx, val type: CoinJoinTransactionType) : TransactionFilter { + override fun matches(tx: Transaction): Boolean { + return CoinJoinTransactionType.fromTx(tx, wallet) == type + } +} + +class CreateDenominationTxFilter(wallet: WalletEx) : CoinJoinTxFilter( + wallet, + CoinJoinTransactionType.CreateDenomination +) +class MakeCollateralTxFilter(wallet: WalletEx) : CoinJoinTxFilter(wallet, CoinJoinTransactionType.MakeCollateralInputs) +class MixingFeeTxFilter(wallet: WalletEx) : CoinJoinTxFilter(wallet, CoinJoinTransactionType.MixingFee) +class MixingTxFilter(wallet: WalletEx) : CoinJoinTxFilter(wallet, CoinJoinTransactionType.Mixing) diff --git a/wallet/src/de/schildbach/wallet/transactions/coinjoin/CoinJoinMixingTxSet.kt b/wallet/src/de/schildbach/wallet/transactions/coinjoin/CoinJoinMixingTxSet.kt new file mode 100644 index 0000000000..8c8d1cd715 --- /dev/null +++ b/wallet/src/de/schildbach/wallet/transactions/coinjoin/CoinJoinMixingTxSet.kt @@ -0,0 +1,53 @@ +package de.schildbach.wallet.transactions.coinjoin + +import org.bitcoinj.core.* +import org.bitcoinj.wallet.WalletEx +import org.dash.wallet.common.transactions.TransactionComparator +import org.dash.wallet.common.transactions.TransactionWrapper +import org.dash.wallet.common.transactions.filters.TransactionFilter +import org.slf4j.LoggerFactory + +open class CoinJoinMixingTxSet( + private val networkParams: NetworkParameters, + private val wallet: WalletEx +) : TransactionWrapper { + private val log = LoggerFactory.getLogger(CoinJoinMixingTxSet::class.java) + private var isFinished = false + + private val coinjoinTxFilters = mutableListOf( + CreateDenominationTxFilter(wallet), + MakeCollateralTxFilter(wallet), + MixingFeeTxFilter(wallet), + MixingTxFilter(wallet) + ) + + private val matchedFilters = mutableListOf() + override val transactions = sortedSetOf(TransactionComparator()) + + override fun tryInclude(tx: Transaction): Boolean { + if (isFinished || transactions.any { it.txId == tx.txId }) { + return false + } + + val matchedFilter = coinjoinTxFilters.firstOrNull { it.matches(tx) } + + if (matchedFilter != null) { + transactions.add(tx) + matchedFilters.add(matchedFilter) + return true + } + + return false + } + + override fun getValue(bag: TransactionBag): Coin { + var result = Coin.ZERO + + for (tx in transactions) { + val value = tx.getValue(bag) + result = result.add(value) + } + + return result + } +} diff --git a/wallet/src/de/schildbach/wallet/transactions/coinjoin/CoinJoinTxResourceMapper.kt b/wallet/src/de/schildbach/wallet/transactions/coinjoin/CoinJoinTxResourceMapper.kt new file mode 100644 index 0000000000..de29aa4b15 --- /dev/null +++ b/wallet/src/de/schildbach/wallet/transactions/coinjoin/CoinJoinTxResourceMapper.kt @@ -0,0 +1,53 @@ +/* + * Copyright 2024 Dash Core Group. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package de.schildbach.wallet.transactions.coinjoin + +import de.schildbach.wallet.ui.transactions.TxResourceMapper + +import android.text.format.DateUtils +import androidx.annotation.StringRes +import de.schildbach.wallet_test.R +import org.bitcoinj.coinjoin.utils.CoinJoinTransactionType +import org.bitcoinj.core.Transaction +import org.bitcoinj.core.TransactionBag +import org.bitcoinj.wallet.WalletEx + +class CoinJoinTxResourceMapper: TxResourceMapper() { + @StringRes + override fun getTransactionTypeName(tx: Transaction, bag: TransactionBag): Int { + if ((tx.type != Transaction.Type.TRANSACTION_NORMAL && + tx.type != Transaction.Type.TRANSACTION_UNKNOWN) || + tx.confidence.hasErrors() || + tx.isCoinBase + ) { + return super.getTransactionTypeName(tx, bag) + } + + return when (CoinJoinTransactionType.fromTx(tx, bag as WalletEx)) { + CoinJoinTransactionType.CreateDenomination -> R.string.transaction_row_status_coinjoin_create_denominations + CoinJoinTransactionType.Mixing -> R.string.transaction_row_status_coinjoin_mixing + CoinJoinTransactionType.MixingFee -> R.string.transaction_row_status_coinjoin_mixing_fee + CoinJoinTransactionType.MakeCollateralInputs -> R.string.transaction_row_status_coinjoin_make_collateral + else -> super.getTransactionTypeName(tx, bag) + } + } + + override fun getDateTimeFormat(): Int { + return DateUtils.FORMAT_SHOW_TIME + } +} \ No newline at end of file diff --git a/wallet/src/de/schildbach/wallet/transactions/coinjoin/CoinJoinTxWrapperFactory.kt b/wallet/src/de/schildbach/wallet/transactions/coinjoin/CoinJoinTxWrapperFactory.kt new file mode 100644 index 0000000000..a0f6668b8a --- /dev/null +++ b/wallet/src/de/schildbach/wallet/transactions/coinjoin/CoinJoinTxWrapperFactory.kt @@ -0,0 +1,55 @@ +/* + * Copyright 2024 Dash Core Group. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package de.schildbach.wallet.transactions.coinjoin + +import org.bitcoinj.coinjoin.utils.CoinJoinTransactionType +import org.bitcoinj.core.NetworkParameters +import org.bitcoinj.core.Transaction +import org.bitcoinj.wallet.WalletEx +import org.dash.wallet.common.transactions.TransactionWrapper +import org.dash.wallet.common.transactions.TransactionWrapperFactory +import java.time.Instant +import java.time.LocalDateTime +import java.time.ZoneId + +class CoinJoinTxWrapperFactory(val params: NetworkParameters, val wallet: WalletEx) : TransactionWrapperFactory { + private val wrapperMap = hashMapOf() + override val wrappers: List + get() = wrapperMap.values.toList() + + override fun tryInclude(tx: Transaction): Pair { + return when (CoinJoinTransactionType.fromTx(tx, wallet)) { + CoinJoinTransactionType.None, CoinJoinTransactionType.Send -> { Pair(false, null) } + else -> { + val instant = Instant.ofEpochMilli(tx.updateTime.time) + val localDateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault()) + val startOfDay = localDateTime.toLocalDate().atStartOfDay(ZoneId.systemDefault()) + val startOfDayTimestamp = startOfDay.toInstant().toEpochMilli() + val wrapper = wrapperMap[startOfDayTimestamp] + if (wrapper != null) { + Pair(wrapper.tryInclude(tx), wrapper) + } else { + val newWrapper = CoinJoinMixingTxSet(params, wallet) + val included = newWrapper.tryInclude(tx) + wrapperMap[startOfDayTimestamp] = newWrapper + Pair(included, newWrapper) + } + } + } + } +} diff --git a/wallet/src/de/schildbach/wallet/ui/main/MainViewModel.kt b/wallet/src/de/schildbach/wallet/ui/main/MainViewModel.kt index 80e6e961ae..c761f07cf1 100644 --- a/wallet/src/de/schildbach/wallet/ui/main/MainViewModel.kt +++ b/wallet/src/de/schildbach/wallet/ui/main/MainViewModel.kt @@ -52,6 +52,7 @@ import de.schildbach.wallet.service.platform.PlatformService import de.schildbach.wallet.service.platform.PlatformSyncService import de.schildbach.wallet.transactions.TxDirectionFilter import de.schildbach.wallet.transactions.TxFilterType +import de.schildbach.wallet.transactions.coinjoin.CoinJoinTxWrapperFactory import de.schildbach.wallet.ui.dashpay.BaseProfileViewModel import de.schildbach.wallet.ui.dashpay.NotificationCountLiveData import de.schildbach.wallet.ui.dashpay.PlatformRepo @@ -100,7 +101,7 @@ import org.dash.wallet.common.transactions.TransactionUtils.isEntirelySelf import org.dash.wallet.common.transactions.TransactionWrapper import org.dash.wallet.common.transactions.TransactionWrapperComparator import org.dash.wallet.common.util.toBigDecimal -import org.dash.wallet.integrations.crowdnode.transactions.FullCrowdNodeSignUpTxSet +import org.dash.wallet.integrations.crowdnode.transactions.FullCrowdNodeSignUpTxSetFactory import org.slf4j.LoggerFactory import kotlin.math.abs import java.text.DecimalFormat @@ -474,7 +475,8 @@ class MainViewModel @Inject constructor( } val transactionViews = walletData.wrapAllTransactions( - FullCrowdNodeSignUpTxSet(walletData.networkParameters, wallet) + FullCrowdNodeSignUpTxSetFactory(walletData.networkParameters, wallet), + CoinJoinTxWrapperFactory(walletData.networkParameters, wallet as WalletEx) ).filter { it.passesFilter(filter, metadata) } .sortedWith(TransactionWrapperComparator()) .map { diff --git a/wallet/src/de/schildbach/wallet/ui/transactions/TransactionGroupDetailsFragment.kt b/wallet/src/de/schildbach/wallet/ui/transactions/TransactionGroupDetailsFragment.kt index 351df2c89f..b815cd7a48 100644 --- a/wallet/src/de/schildbach/wallet/ui/transactions/TransactionGroupDetailsFragment.kt +++ b/wallet/src/de/schildbach/wallet/ui/transactions/TransactionGroupDetailsFragment.kt @@ -23,6 +23,7 @@ import androidx.core.view.isVisible import androidx.fragment.app.viewModels import dagger.hilt.android.AndroidEntryPoint import de.schildbach.wallet.Constants +import de.schildbach.wallet.transactions.coinjoin.CoinJoinMixingTxSet import de.schildbach.wallet.ui.main.TransactionAdapter import de.schildbach.wallet_test.R import de.schildbach.wallet_test.databinding.TransactionGroupDetailsBinding @@ -52,11 +53,19 @@ class TransactionGroupDetailsFragment() : OffsetDialogFragment(R.layout.transact binding.dashAmount.setFormat(viewModel.dashFormat) - if (transactionWrapper is FullCrowdNodeSignUpTxSet) { - binding.groupTitle.text = getString(R.string.crowdnode_account) - binding.icon.setImageResource(R.drawable.ic_crowdnode_logo) - binding.detailsTitle.text = getString(R.string.crowdnode_tx_set_title) - binding.detailsMessage.text = getString(R.string.crowdnode_tx_set_explainer) + when (transactionWrapper) { + is FullCrowdNodeSignUpTxSet -> { + binding.groupTitle.text = getString(R.string.crowdnode_account) + binding.icon.setImageResource(R.drawable.ic_crowdnode_logo) + binding.detailsTitle.text = getString(R.string.crowdnode_tx_set_title) + binding.detailsMessage.text = getString(R.string.crowdnode_tx_set_explainer) + } + is CoinJoinMixingTxSet -> { + binding.groupTitle.text = getString(R.string.coinjoin_mixing_transactions) + binding.icon.setImageResource(R.drawable.ic_coinjoin) + binding.detailsTitle.text = getString(R.string.crowdnode_tx_set_title) + binding.detailsMessage.text = getString(R.string.coinjoin_transaction_group) + } } val adapter = TransactionAdapter(viewModel.dashFormat, resources) { item, _, _ -> diff --git a/wallet/src/de/schildbach/wallet/ui/transactions/TransactionGroupViewModel.kt b/wallet/src/de/schildbach/wallet/ui/transactions/TransactionGroupViewModel.kt index 000e7edb88..890b2a1643 100644 --- a/wallet/src/de/schildbach/wallet/ui/transactions/TransactionGroupViewModel.kt +++ b/wallet/src/de/schildbach/wallet/ui/transactions/TransactionGroupViewModel.kt @@ -22,6 +22,8 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel +import de.schildbach.wallet.transactions.coinjoin.CoinJoinMixingTxSet +import de.schildbach.wallet.transactions.coinjoin.CoinJoinTxResourceMapper import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.flatMapLatest import org.bitcoinj.core.Sha256Hash @@ -87,10 +89,10 @@ class TransactionGroupViewModel @Inject constructor( transactionWrapper: TransactionWrapper, metadata: Map ) { - val resourceMapper = if (transactionWrapper is FullCrowdNodeSignUpTxSet) { - CrowdNodeTxResourceMapper() - } else { - TxResourceMapper() + val resourceMapper = when (transactionWrapper) { + is FullCrowdNodeSignUpTxSet -> CrowdNodeTxResourceMapper() + is CoinJoinMixingTxSet -> CoinJoinTxResourceMapper() + else -> TxResourceMapper() } _transactions.value = transactionWrapper.transactions.map { diff --git a/wallet/src/de/schildbach/wallet/ui/transactions/TransactionRowView.kt b/wallet/src/de/schildbach/wallet/ui/transactions/TransactionRowView.kt index fa53725144..b476edee6b 100644 --- a/wallet/src/de/schildbach/wallet/ui/transactions/TransactionRowView.kt +++ b/wallet/src/de/schildbach/wallet/ui/transactions/TransactionRowView.kt @@ -22,6 +22,7 @@ import androidx.annotation.DrawableRes import androidx.annotation.StringRes import androidx.annotation.StyleRes import de.schildbach.wallet.database.entity.DashPayProfile +import de.schildbach.wallet.transactions.coinjoin.CoinJoinMixingTxSet import de.schildbach.wallet.ui.main.HistoryRowView import de.schildbach.wallet_test.R import org.bitcoinj.core.* @@ -61,14 +62,33 @@ data class TransactionRowView( ): TransactionRowView { val lastTx = txWrapper.transactions.last() - return if (txWrapper is FullCrowdNodeSignUpTxSet) { - TransactionRowView( - ResourceString(R.string.crowdnode_account), + return when (txWrapper) { + is FullCrowdNodeSignUpTxSet -> TransactionRowView( + ResourceString(R.string.crowdnode_account), + lastTx.txId, + txWrapper.getValue(bag), + lastTx.exchangeRate, + null, + R.drawable.ic_crowdnode_logo, + null, + R.style.TxNoBackground, + -1, + metadata?.memo ?: "", + txWrapper.transactions.size, + lastTx.updateTime.time, + TxResourceMapper().dateTimeFormat, + false, + ServiceName.CrowdNode, + txWrapper + ) + + is CoinJoinMixingTxSet -> TransactionRowView( + ResourceString(R.string.coinjoin_mixing_transactions), lastTx.txId, txWrapper.getValue(bag), lastTx.exchangeRate, null, - R.drawable.ic_crowdnode_logo, + R.drawable.ic_coinjoin_mixing_group, null, R.style.TxNoBackground, -1, @@ -77,11 +97,11 @@ data class TransactionRowView( lastTx.updateTime.time, TxResourceMapper().dateTimeFormat, false, - ServiceName.CrowdNode, + ServiceName.Unknown, txWrapper ) - } else { - fromTransaction(lastTx, bag, context, metadata, contact) + + else -> fromTransaction(lastTx, bag, context, metadata, contact) } } diff --git a/wallet/test/de/schildbach/wallet/util/transactions/TransactionWrapperHelperTest.kt b/wallet/test/de/schildbach/wallet/util/transactions/TransactionWrapperHelperTest.kt index 88446353f9..3ce3f3e58c 100644 --- a/wallet/test/de/schildbach/wallet/util/transactions/TransactionWrapperHelperTest.kt +++ b/wallet/test/de/schildbach/wallet/util/transactions/TransactionWrapperHelperTest.kt @@ -29,6 +29,7 @@ import org.bitcoinj.wallet.Wallet import org.bitcoinj.wallet.WalletTransaction import org.dash.wallet.integrations.crowdnode.transactions.FullCrowdNodeSignUpTxSet import org.bitcoinj.core.Utils +import org.dash.wallet.integrations.crowdnode.transactions.FullCrowdNodeSignUpTxSetFactory import org.junit.Before import org.junit.Test @@ -110,10 +111,11 @@ class TransactionWrapperHelperTest { every { bagMock.getTransactionPool(WalletTransaction.Pool.PENDING)} returns mapOf() every { bagMock.getTransactionPool(WalletTransaction.Pool.SPENT)} returns allTransactions.associateBy({it.txId}, {it}) - val crowdNodeWrapper = FullCrowdNodeSignUpTxSet(networkParams, bagMock) + val crowdNodeWrapperFactory = FullCrowdNodeSignUpTxSetFactory(networkParams, bagMock) + val crowdNodeWrapper = crowdNodeWrapperFactory.wrappers.first()!! val wrappedTransactions = TransactionWrapperHelper.wrapTransactions( allTransactions, - crowdNodeWrapper + crowdNodeWrapperFactory ) assertEquals("Must have CrowdNode wrapper and 2 anon wrappers:", 3, wrappedTransactions.size)