Skip to content

Commit c1aa803

Browse files
committed
feat(multisig): Implement inbox pages for sign requests
- Add AllAccountsInboxFragment with unified inbox view - Add sign request notification items to inbox - Implement JointAccountInvitationDetailFragment - Add inbox preview mappers for different notification types - Handle inbox item click navigation - Add badge indicators for pending requests
1 parent 9fa3c15 commit c1aa803

29 files changed

+2494
-48
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,6 @@ render.experimental.xml
4646

4747
# Claude
4848
.claude/
49+
50+
# Backend Feedback
51+
BACKEND_FEEDBACK.md

app/src/main/kotlin/com/algorand/android/core/transaction/TransactionSignManager.kt

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -270,10 +270,6 @@ class TransactionSignManager @Inject constructor(
270270
is TransactionSigner.SignerNotFound -> {
271271
postResult(Defined(AnnotatedString(stringResId = R.string.the_signing_account_has)))
272272
}
273-
274-
is TransactionSigner.Joint -> {
275-
TODO("Handle Joint Account")
276-
}
277273
}
278274
}
279275

app/src/main/kotlin/com/algorand/android/modules/accountcore/ui/usecase/GetAccountIconDrawablePreviewUseCase.kt

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,6 @@ internal class GetAccountIconDrawablePreviewUseCase @Inject constructor(
6565
AccountType.RekeyedAuth -> getRekeyedAuthDrawable(address, rekeyAuthAddress)
6666
AccountType.Joint -> AccountIconDrawablePreviews.getJointDrawable()
6767
null -> AccountIconDrawablePreviews.getDefaultIconDrawablePreview()
68-
AccountType.Joint -> {
69-
TODO("Handle Joint Account")
70-
AccountIconDrawablePreviews.getDefaultIconDrawablePreview()
71-
}
7268
}
7369
}
7470

app/src/main/kotlin/com/algorand/android/modules/accountcore/ui/usecase/GetAccountOriginalStateIconDrawablePreviewUseCase.kt

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,6 @@ internal class GetAccountOriginalStateIconDrawablePreviewUseCase @Inject constru
5858
R.color.layer_gray_lighter
5959
}
6060
}
61-
62-
AccountType.Joint -> {
63-
TODO("Handle Joint Account")
64-
STANDARD.backgroundColorResId
65-
}
6661
}
6762
}
6863

@@ -76,11 +71,6 @@ internal class GetAccountOriginalStateIconDrawablePreviewUseCase @Inject constru
7671
AccountType.RekeyedAuth, AccountType.Rekeyed, null -> {
7772
if (accountType?.canSignTransaction() == true) STANDARD.iconTintResId else R.color.text_gray_lighter
7873
}
79-
80-
AccountType.Joint -> {
81-
TODO("Handle Joint Account")
82-
STANDARD.iconTintResId
83-
}
8474
}
8575
}
8676

@@ -94,11 +84,6 @@ internal class GetAccountOriginalStateIconDrawablePreviewUseCase @Inject constru
9484
AccountType.RekeyedAuth, AccountType.Rekeyed, null -> {
9585
if (accountType?.canSignTransaction() == true) STANDARD.iconResId else R.drawable.ic_question
9686
}
97-
98-
AccountType.Joint -> {
99-
TODO("Handle Joint Account")
100-
STANDARD.iconResId
101-
}
10287
}
10388
}
10489
}

app/src/main/kotlin/com/algorand/android/modules/accounts/ui/viewmodel/AccountsPreviewUseCase.kt

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,15 @@ import com.algorand.android.modules.accounts.lite.domain.model.AccountLiteCacheS
2121
import com.algorand.android.modules.accounts.lite.domain.model.AccountLiteCacheStatus.Loading
2222
import com.algorand.android.modules.accounts.lite.domain.usecase.GetAccountLiteCacheFlow
2323
import com.algorand.android.modules.accounts.ui.model.AccountPreview
24+
import com.algorand.android.modules.inbox.allaccounts.ui.usecase.InboxPreviewUseCase
2425
import com.algorand.android.modules.parity.domain.model.SelectedCurrencyDetail
2526
import com.algorand.android.modules.peraconnectivitymanager.ui.PeraConnectivityManager
2627
import com.algorand.android.utils.CacheResult
2728
import com.algorand.wallet.banner.domain.usecase.GetBannerFlow
29+
import com.algorand.wallet.inbox.asset.domain.usecase.GetAssetInboxRequestCountFlow
2830
import com.algorand.wallet.privacy.domain.usecase.GetPrivacyModeFlow
31+
import com.algorand.wallet.remoteconfig.domain.model.FeatureToggle
32+
import com.algorand.wallet.remoteconfig.domain.usecase.IsFeatureToggleEnabled
2933
import com.algorand.wallet.spotbanner.domain.model.SpotBannerFlowData
3034
import com.algorand.wallet.spotbanner.domain.usecase.GetSpotBannersFlow
3135
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -42,10 +46,13 @@ class AccountsPreviewUseCase @Inject constructor(
4246
private val portfolioValueItemMapper: PortfolioValueItemMapper,
4347
private val peraConnectivityManager: PeraConnectivityManager,
4448
private val accountPreviewProcessor: AccountPreviewProcessor,
49+
private val getAssetInboxRequestCountFlow: GetAssetInboxRequestCountFlow,
4550
private val getAccountLiteCacheFlow: GetAccountLiteCacheFlow,
4651
private val getPrivacyModeFlow: GetPrivacyModeFlow,
4752
private val getBannerFlow: GetBannerFlow,
4853
private val getSpotBannersFlow: GetSpotBannersFlow,
54+
private val inboxPreviewUseCase: InboxPreviewUseCase,
55+
private val isFeatureToggleEnabled: IsFeatureToggleEnabled
4956
) {
5057

5158
suspend fun getInitialAccountPreview(): AccountPreview {
@@ -81,19 +88,40 @@ class AccountsPreviewUseCase @Inject constructor(
8188
return combine(
8289
getBannerFlow(),
8390
getSpotBannersFlow(getSpotBannerFlowData(accountLiteCacheData)),
91+
getTotalInboxCountFlow(),
8492
getPrivacyModeFlow()
85-
) { banner, spotBanners, privacyMode ->
93+
) { banner, spotBanners, totalInboxCount, privacyMode ->
8694
accountPreviewProcessor.prepareAccountPreview(
8795
accountLiteCacheData.localAccounts,
8896
accountLiteCacheData.accountLites,
8997
banner,
90-
0,
98+
totalInboxCount,
9199
privacyMode,
92100
spotBanners
93101
)
94102
}
95103
}
96104

105+
@OptIn(
106+
kotlinx.coroutines.FlowPreview::class,
107+
kotlinx.coroutines.ExperimentalCoroutinesApi::class
108+
)
109+
private suspend fun getTotalInboxCountFlow(): Flow<Int> {
110+
val isJointAccountEnabled = isFeatureToggleEnabled(FeatureToggle.JOINT_ACCOUNT.key)
111+
return getAssetInboxRequestCountFlow().flatMapLatest { asaInboxCount ->
112+
inboxPreviewUseCase.getInboxPreview()
113+
.mapLatest { inboxPreview ->
114+
if (isJointAccountEnabled) {
115+
val jointAccountSignRequestsCount = inboxPreview.signatureRequestList.size
116+
val jointAccountImportRequestsCount = inboxPreview.jointAccountInvitationList.size
117+
asaInboxCount + jointAccountSignRequestsCount + jointAccountImportRequestsCount
118+
} else {
119+
asaInboxCount
120+
}
121+
}
122+
}
123+
}
124+
97125
private fun getSpotBannerFlowData(accountLiteCacheData: Data): List<SpotBannerFlowData> {
98126
return accountLiteCacheData.accountLites.values.map { lite ->
99127
with(lite) {

app/src/main/kotlin/com/algorand/android/modules/assetinbox/detail/receivedetail/ui/Arc59ReceiveDetailFragment.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,10 @@ class Arc59ReceiveDetailFragment : BaseFragment(R.layout.fragment_arc59_receive_
9797
initObservers()
9898
viewModel.initializePreview()
9999
arc59ClaimRejectTransactionSignManager.setup(viewLifecycleOwner.lifecycle)
100+
initSavedStateListener()
100101
}
101102

102-
override fun onResume() {
103-
super.onResume()
103+
private fun initSavedStateListener() {
104104
startSavedStateListener(R.id.arc59ReceiveDetailFragment) {
105105
useSavedStateValue<ConfirmationBottomSheetResult>(RESULT_KEY) {
106106
if (it.isAccepted) viewModel.rejectTransaction()

app/src/main/kotlin/com/algorand/android/modules/assetinbox/info/ui/AssetInboxInfoBottomSheet.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import com.google.android.material.button.MaterialButton
2020
class AssetInboxInfoBottomSheet : BaseInformationBottomSheet() {
2121

2222
override fun initTitleTextView(titleTextView: TextView) {
23-
titleTextView.setText(R.string.asset_transfer_requests)
23+
titleTextView.setText(R.string.inbox)
2424
}
2525

2626
override fun initDescriptionTextView(descriptionTextView: TextView) {

app/src/main/kotlin/com/algorand/android/modules/assetinbox/send/summary/ui/Arc59SendSummaryFragment.kt

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -137,10 +137,6 @@ class Arc59SendSummaryFragment : BaseFragment(R.layout.fragment_arc59_send_summa
137137
initObservers()
138138
arc59SendSummaryViewModel.initializePreview()
139139
arc59SendTransactionSignManager.setup(viewLifecycleOwner.lifecycle)
140-
}
141-
142-
override fun onResume() {
143-
super.onResume()
144140
initSavedStateListener()
145141
}
146142

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright 2022-2025 Pera Wallet, LDA
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
* Unless required by applicable law or agreed to in writing, software
7+
* distributed under the License is distributed on an "AS IS" BASIS,
8+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9+
* See the License for the specific language governing permissions and
10+
* limitations under the License
11+
*/
12+
13+
package com.algorand.android.modules.inbox.allaccounts.di
14+
15+
import com.algorand.android.modules.inbox.allaccounts.ui.mapper.InboxPreviewMapper
16+
import com.algorand.android.modules.inbox.allaccounts.ui.mapper.InboxPreviewMapperImpl
17+
import dagger.Module
18+
import dagger.Provides
19+
import dagger.hilt.InstallIn
20+
import dagger.hilt.components.SingletonComponent
21+
import javax.inject.Singleton
22+
23+
@Module
24+
@InstallIn(SingletonComponent::class)
25+
object InboxRepositoryModule {
26+
27+
@Provides
28+
@Singleton
29+
fun provideInboxPreviewMapper(
30+
inboxPreviewMapperImpl: InboxPreviewMapperImpl
31+
): InboxPreviewMapper = inboxPreviewMapperImpl
32+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2022-2025 Pera Wallet, LDA
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
* Unless required by applicable law or agreed to in writing, software
7+
* distributed under the License is distributed on an "AS IS" BASIS,
8+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9+
* See the License for the specific language governing permissions and
10+
* limitations under the License
11+
*/
12+
13+
package com.algorand.android.modules.inbox.allaccounts.domain.model
14+
15+
import android.os.Parcelable
16+
import com.algorand.android.models.RecyclerListItem
17+
import com.algorand.android.modules.accountcore.ui.model.AccountDisplayName
18+
import com.algorand.android.modules.accounticon.ui.model.AccountIconDrawablePreview
19+
import kotlinx.parcelize.Parcelize
20+
21+
@Parcelize
22+
data class InboxWithAccount(
23+
val address: String,
24+
val requestCount: Int,
25+
val accountDisplayName: AccountDisplayName,
26+
val accountAddress: String,
27+
val accountIconDrawablePreview: AccountIconDrawablePreview
28+
) : Parcelable, RecyclerListItem {
29+
override fun areItemsTheSame(other: RecyclerListItem): Boolean {
30+
return other is InboxWithAccount && accountAddress == other.accountAddress
31+
}
32+
33+
override fun areContentsTheSame(other: RecyclerListItem): Boolean {
34+
return other is InboxWithAccount && this == other
35+
}
36+
}

0 commit comments

Comments
 (0)