diff --git a/app/src/main/java/one/mixin/android/ui/wallet/TransactionInterface.kt b/app/src/main/java/one/mixin/android/ui/wallet/TransactionInterface.kt index 7ed0852e30..04d840bddb 100644 --- a/app/src/main/java/one/mixin/android/ui/wallet/TransactionInterface.kt +++ b/app/src/main/java/one/mixin/android/ui/wallet/TransactionInterface.kt @@ -34,6 +34,7 @@ import one.mixin.android.ui.home.inscription.InscriptionActivity import one.mixin.android.vo.Fiats import one.mixin.android.vo.SnapshotItem import one.mixin.android.vo.Ticker +import one.mixin.android.vo.safe.RawTransaction import one.mixin.android.vo.safe.SafeSnapshotType import one.mixin.android.vo.safe.TokenItem import one.mixin.android.widget.linktext.RoundBackgroundColorSpan @@ -61,7 +62,8 @@ interface TransactionInterface { contentBinding.avatarVa.setOnClickListener { clickAvatar(fragment, asset, snapshot.inscriptionHash) } - updateUI(fragment, contentBinding, asset, snapshot) + val rawTransaction = snapshot.traceId?.let { walletViewModel.findRawTransaction(it) } + updateUI(fragment, contentBinding, asset, snapshot, rawTransaction) fetchThatTimePrice( fragment, lifecycleScope, @@ -77,6 +79,7 @@ interface TransactionInterface { walletViewModel, snapshot, asset, + rawTransaction, ) } } @@ -87,23 +90,27 @@ interface TransactionInterface { contentBinding.avatarVa.setOnClickListener { clickAvatar(fragment, tokenItem, snapshotItem.inscriptionHash) } - updateUI(fragment, contentBinding, tokenItem, snapshotItem) - fetchThatTimePrice( - fragment, - lifecycleScope, - walletViewModel, - contentBinding, - tokenItem.assetId, - snapshotItem, - ) - refreshIncompleteSnapshot( - fragment, - contentBinding, - lifecycleScope, - walletViewModel, - snapshotItem, - tokenItem, - ) + lifecycleScope.launch { + val rawTransaction = snapshotItem.traceId?.let { walletViewModel.findRawTransaction(it) } + updateUI(fragment, contentBinding, tokenItem, snapshotItem, rawTransaction) + fetchThatTimePrice( + fragment, + lifecycleScope, + walletViewModel, + contentBinding, + tokenItem.assetId, + snapshotItem, + ) + refreshIncompleteSnapshot( + fragment, + contentBinding, + lifecycleScope, + walletViewModel, + snapshotItem, + tokenItem, + rawTransaction, + ) + } } } @@ -286,12 +293,14 @@ interface TransactionInterface { contentBinding: FragmentTransactionBinding, asset: TokenItem, snapshot: SnapshotItem, + rawTransaction: RawTransaction? = null, ) { if (checkDestroyed(fragment)) return contentBinding.apply { val amountVal = snapshot.amount.toFloatOrNull() val isPositive = if (amountVal == null) false else amountVal > 0 + val showPendingHash = snapshot.shouldShowPendingHash(rawTransaction) if (snapshot.inscriptionHash.isNullOrEmpty()) { avatarVa.displayedChild = 0 contentBinding.avatar.loadToken(asset) @@ -326,7 +335,7 @@ interface TransactionInterface { val amountColor = fragment.resources.getColor( when { - snapshot.type == SafeSnapshotType.pending.name -> { + snapshot.type == SafeSnapshotType.pending.name || showPendingHash -> { R.color.wallet_text_gray } isPositive -> { @@ -352,6 +361,7 @@ interface TransactionInterface { transactionIdTv.text = snapshot.snapshotId transactionHashLayout.isVisible = !snapshot.transactionHash.isNullOrBlank() transactionHashTv.text = snapshot.transactionHash + hashPendingPb.isVisible = false dateTv.text = snapshot.createdAt.fullDate() memoLl.isVisible = snapshot.formatMemo != null memoTv.text = snapshot.formatMemo?.utf ?: snapshot.formatMemo?.hex @@ -425,6 +435,7 @@ interface TransactionInterface { if (snapshot.withdrawal != null) { hashLl.isVisible = true hashTitle.text = fragment.getString(R.string.withdrawal_hash) + hashPendingPb.isVisible = showPendingHash if (snapshot.withdrawal.withdrawalHash.isBlank()) { hashTv.text = fragment.getString(R.string.withdrawal_pending) } else { @@ -463,12 +474,13 @@ interface TransactionInterface { walletViewModel: WalletViewModel, snapshot: SnapshotItem, asset: TokenItem, + rawTransaction: RawTransaction? = null, ) { if (snapshot.isDataIncomplete()) { lifecycleScope.launch { walletViewModel.refreshSnapshot(snapshot.snapshotId)?.let { it.label = snapshot.label // Saving temporary variables - updateUI(fragment, contentBinding, asset, it) + updateUI(fragment, contentBinding, asset, it, rawTransaction) } } } diff --git a/app/src/main/java/one/mixin/android/ui/wallet/WalletViewModel.kt b/app/src/main/java/one/mixin/android/ui/wallet/WalletViewModel.kt index 96832c1a90..24b1f5670d 100644 --- a/app/src/main/java/one/mixin/android/ui/wallet/WalletViewModel.kt +++ b/app/src/main/java/one/mixin/android/ui/wallet/WalletViewModel.kt @@ -57,6 +57,7 @@ import one.mixin.android.vo.UtxoItem import one.mixin.android.vo.market.Market import one.mixin.android.vo.market.MarketItem import one.mixin.android.vo.safe.Output +import one.mixin.android.vo.safe.RawTransaction import one.mixin.android.vo.safe.SafeSnapshot import one.mixin.android.vo.safe.TokenItem import one.mixin.android.vo.sumsub.ProfileResponse @@ -319,6 +320,11 @@ internal constructor( suspend fun findSnapshot(snapshotId: String): SnapshotItem? = tokenRepository.findSnapshotById(snapshotId) + suspend fun findRawTransaction(traceId: String): RawTransaction? = + withContext(Dispatchers.IO) { + tokenRepository.findRawTransaction(traceId) + } + suspend fun profile(): MixinResponse = tokenRepository.profile() suspend fun fetchSessionsSuspend(ids: List) = userRepository.fetchSessionsSuspend(ids) diff --git a/app/src/main/java/one/mixin/android/vo/SnapshotItem.kt b/app/src/main/java/one/mixin/android/vo/SnapshotItem.kt index d7996d72da..0588b498f3 100644 --- a/app/src/main/java/one/mixin/android/vo/SnapshotItem.kt +++ b/app/src/main/java/one/mixin/android/vo/SnapshotItem.kt @@ -12,6 +12,8 @@ import kotlinx.serialization.SerialName import one.mixin.android.extension.hexString import one.mixin.android.extension.isByteArrayValidUtf8 import one.mixin.android.extension.isValidHex +import one.mixin.android.vo.safe.OutputState +import one.mixin.android.vo.safe.RawTransaction import one.mixin.android.vo.safe.SafeDeposit import one.mixin.android.vo.safe.SafeSnapshotType import one.mixin.android.vo.safe.SafeWithdrawal @@ -159,6 +161,8 @@ data class SnapshotItem( } fun isPendingWithdrawal() = withdrawal != null && withdrawal.withdrawalHash.isNullOrBlank() + + fun shouldShowPendingHash(rawTransaction: RawTransaction?) = rawTransaction?.state == OutputState.unspent } @Parcelize diff --git a/app/src/main/res/layout/fragment_transaction.xml b/app/src/main/res/layout/fragment_transaction.xml index a3dc632e0c..3cdfd0f2f6 100644 --- a/app/src/main/res/layout/fragment_transaction.xml +++ b/app/src/main/res/layout/fragment_transaction.xml @@ -290,12 +290,38 @@ android:layout_height="wrap_content" android:text="@string/deposit_hash" /> - + android:gravity="center_vertical" + android:layout_marginStart="20dp" + android:layout_marginEnd="20dp" + android:layout_marginTop="5dp" + android:layout_marginBottom="12dp" + android:orientation="horizontal"> + + + + + - \ No newline at end of file + diff --git a/app/src/test/java/one/mixin/android/vo/SnapshotItemTest.kt b/app/src/test/java/one/mixin/android/vo/SnapshotItemTest.kt new file mode 100644 index 0000000000..a1445efba0 --- /dev/null +++ b/app/src/test/java/one/mixin/android/vo/SnapshotItemTest.kt @@ -0,0 +1,78 @@ +package one.mixin.android.vo + +import kotlin.test.Test +import kotlin.test.assertEquals +import one.mixin.android.vo.safe.OutputState +import one.mixin.android.vo.safe.RawTransaction +import one.mixin.android.vo.safe.RawTransactionType +import one.mixin.android.vo.safe.SafeSnapshotType + +class SnapshotItemTest { + @Test + fun `pending hash is shown when raw transaction is unspent`() { + val snapshot = snapshotItem(type = SafeSnapshotType.snapshot.name, traceId = "trace-id") + val rawTransaction = rawTransaction("trace-id", OutputState.unspent) + + assertEquals(true, snapshot.shouldShowPendingHash(rawTransaction)) + } + + @Test + fun `pending hash is hidden when raw transaction was sent`() { + val snapshot = snapshotItem(type = SafeSnapshotType.snapshot.name, traceId = "trace-id") + val rawTransaction = rawTransaction("trace-id", OutputState.signed) + + assertEquals(false, snapshot.shouldShowPendingHash(rawTransaction)) + } + + @Test + fun `pending hash is hidden when raw transaction is missing`() { + val snapshot = snapshotItem(type = SafeSnapshotType.withdrawal.name, traceId = "trace-id") + + assertEquals(false, snapshot.shouldShowPendingHash(null)) + } + + private fun rawTransaction( + requestId: String, + state: OutputState, + ) = RawTransaction( + requestId = requestId, + rawTransaction = "raw", + receiverId = "", + type = RawTransactionType.TRANSFER, + state = state, + createdAt = "2026-06-03T00:00:00Z", + inscriptionHash = null, + ) + + private fun snapshotItem( + type: String, + traceId: String?, + ) = SnapshotItem( + snapshotId = "snapshot-id", + type = type, + assetId = "asset-id", + amount = "-1", + createdAt = "2026-06-03T00:00:00Z", + opponentId = "opponent-id", + opponentFullName = null, + transactionHash = "hash", + memo = null, + assetSymbol = "XIN", + confirmations = null, + avatarUrl = null, + assetConfirmations = 0, + traceId = traceId, + openingBalance = null, + closingBalance = null, + deposit = null, + withdrawal = null, + label = null, + inscriptionHash = null, + collectionHash = null, + name = null, + sequence = null, + contentType = null, + contentUrl = null, + iconUrl = null, + ) +}