From f82d305c7b47d42f022393eb7d0fc00225fc5dd0 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 4 Mar 2024 08:15:00 +0600 Subject: [PATCH 01/38] Updated on 2026-08-14 --- app/build.gradle.kts | 8 ++++ .../tangem/helpers/base/BaseAutoTestCase.kt | 27 +++++++++++ .../com/tangem/screens/StoriesScreen.kt | 32 +++++++++++++ .../kotlin/com/tangem/tests/StoriesTest.kt | 45 +++++++++++++++++++ .../tangem/tap/common/compose/resources/C.kt | 9 ++++ .../features/home/compose/StoriesScreen.kt | 4 +- .../home/compose/views/HomeButtons.kt | 10 ++++- gradle/dependencies.toml | 14 +++++- 8 files changed, 144 insertions(+), 5 deletions(-) create mode 100644 app/src/androidTest/kotlin/com/tangem/helpers/base/BaseAutoTestCase.kt create mode 100644 app/src/androidTest/kotlin/com/tangem/screens/StoriesScreen.kt create mode 100644 app/src/androidTest/kotlin/com/tangem/tests/StoriesTest.kt create mode 100644 app/src/main/java/com/tangem/tap/common/compose/resources/C.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 553088615c..ee0bc5020b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -11,6 +11,9 @@ plugins { android { namespace = "com.tangem.wallet" + testOptions { + animationsDisabled = true + } } configurations.all { @@ -207,6 +210,11 @@ dependencies { testImplementation(deps.test.truth) androidTestImplementation(deps.test.junit.android) androidTestImplementation(deps.test.espresso) + androidTestImplementation(deps.test.espresso.intents) + androidTestImplementation(deps.test.kaspresso) + androidTestImplementation(deps.test.kaspresso.compose) + androidTestImplementation(deps.test.compose.junit) + androidTestImplementation(deps.test.hamcrest) /** Chucker */ debugImplementation(deps.chucker) diff --git a/app/src/androidTest/kotlin/com/tangem/helpers/base/BaseAutoTestCase.kt b/app/src/androidTest/kotlin/com/tangem/helpers/base/BaseAutoTestCase.kt new file mode 100644 index 0000000000..6e0bfc19b2 --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/helpers/base/BaseAutoTestCase.kt @@ -0,0 +1,27 @@ +package com.tangem.helpers.base + +import android.Manifest +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.rule.GrantPermissionRule +import com.kaspersky.components.composesupport.config.withComposeSupport +import com.kaspersky.kaspresso.kaspresso.Kaspresso +import com.kaspersky.kaspresso.testcases.api.testcase.TestCase +import com.tangem.tap.MainActivity +import org.junit.Rule +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +open class BaseAutoTestCase : TestCase( + kaspressoBuilder = Kaspresso.Builder.withComposeSupport() +) { + + @get:Rule + open val composeTestRule = createAndroidComposeRule() + + @get: Rule + val grantPermissionRule: GrantPermissionRule = GrantPermissionRule.grant( + Manifest.permission.POST_NOTIFICATIONS, + Manifest.permission.CAMERA + ) +} \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/screens/StoriesScreen.kt b/app/src/androidTest/kotlin/com/tangem/screens/StoriesScreen.kt new file mode 100644 index 0000000000..38bdbce846 --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/screens/StoriesScreen.kt @@ -0,0 +1,32 @@ +package com.tangem.screens + +import androidx.compose.ui.test.SemanticsNodeInteractionsProvider +import com.tangem.tap.common.compose.resources.C +import com.tangem.wallet.R +import io.github.kakaocup.compose.node.element.ComposeScreen +import io.github.kakaocup.compose.node.element.KNode +import io.github.kakaocup.kakao.common.views.KView +import io.github.kakaocup.kakao.text.KButton + +class StoriesScreen(semanticsProvider: SemanticsNodeInteractionsProvider) : + ComposeScreen( + semanticsProvider = semanticsProvider, + viewBuilderAction = { hasTestTag(C.Tag.STORIES_SCREEN) } + ) { + + val scanButton: KNode = child { + hasTestTag(C.Tag.STORIES_SCREEN_SCAN_BUTTON) + } + + val orderButton: KNode = child { + hasTestTag(C.Tag.STORIES_SCREEN_ORDER_BUTTON) + } + + val enableNFCAlert: KView = KView { + withId(R.id.alertTitle) + } + + val cancelButton: KButton = KButton { + withId(android.R.id.button2) + } +} \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/tests/StoriesTest.kt b/app/src/androidTest/kotlin/com/tangem/tests/StoriesTest.kt new file mode 100644 index 0000000000..ce58b4a1be --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/tests/StoriesTest.kt @@ -0,0 +1,45 @@ +package com.tangem.tests + +import android.content.Intent.ACTION_VIEW +import androidx.test.espresso.intent.Intents +import com.tangem.helpers.base.BaseAutoTestCase +import com.tangem.screens.StoriesScreen +import com.tangem.tap.features.home.redux.HomeMiddleware.NEW_BUY_WALLET_URL +import io.github.kakaocup.compose.node.element.ComposeScreen +import io.github.kakaocup.kakao.intent.KIntent +import org.junit.Test + +class StoriesTest : BaseAutoTestCase() { + + @Test + fun clickOnButtons() = before { + Intents.init() + }.after { + Intents.release() + }.run { + ComposeScreen.onComposeScreen(composeTestRule) { + step("Click on \"Scan\" button") { + scanButton { + assertIsDisplayed() + performClick() + } + } + step("Assert: \"Scan card\" popup opened") { + enableNFCAlert.isDisplayed() + cancelButton.click() + device.uiDevice.pressBack() + } + step("Click on \"Order\" button") { + orderButton.performClick() + } + step("Assert: browser opened") { + val expectedIntent = KIntent { + hasAction(ACTION_VIEW) + hasData(NEW_BUY_WALLET_URL) + } + expectedIntent.intended() + device.uiDevice.pressBack() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/compose/resources/C.kt b/app/src/main/java/com/tangem/tap/common/compose/resources/C.kt new file mode 100644 index 0000000000..ca77ba62d2 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/compose/resources/C.kt @@ -0,0 +1,9 @@ +package com.tangem.tap.common.compose.resources + +object C { + object Tag { + const val STORIES_SCREEN = "STORIES_SCREEN_CONTAINER" + const val STORIES_SCREEN_SCAN_BUTTON = "STORIES_SCREEN_SCAN_BUTTON" + const val STORIES_SCREEN_ORDER_BUTTON = "STORIES_SCREEN_ORDER_BUTTON" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreen.kt b/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreen.kt index 73a454fe10..bd3f2a5dde 100644 --- a/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreen.kt @@ -15,11 +15,13 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import com.tangem.core.ui.res.TangemTheme +import com.tangem.tap.common.compose.resources.C import com.tangem.tap.features.home.compose.content.* import com.tangem.tap.features.home.compose.views.HomeButtons import com.tangem.tap.features.home.compose.views.SearchCurrenciesButton @@ -55,7 +57,7 @@ fun StoriesScreen( } StoriesScreenContent( - modifier = Modifier.fillMaxSize(), + modifier = Modifier.fillMaxSize().testTag(C.Tag.STORIES_SCREEN), config = StoriesScreenContentConfig( storiesSize = state.stories.lastIndex, currentStoryIndex = currentStoryIndex, diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtons.kt b/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtons.kt index 0533961886..75d308e2b7 100644 --- a/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtons.kt +++ b/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtons.kt @@ -8,6 +8,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter @@ -15,6 +16,7 @@ import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameter import com.tangem.core.ui.components.SpacerW12 import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition import com.tangem.core.ui.res.TangemTheme +import com.tangem.tap.common.compose.resources.C import com.tangem.wallet.R @Composable @@ -29,13 +31,17 @@ internal fun HomeButtons( modifier = modifier, ) { ScanCardButton( - modifier = Modifier.weight(weight = 1f), + modifier = Modifier + .weight(weight = 1f) + .testTag(C.Tag.STORIES_SCREEN_SCAN_BUTTON), showProgress = btnScanStateInProgress, onClick = onScanButtonClick, ) SpacerW12() OrderCardButton( - modifier = Modifier.weight(weight = 1f), + modifier = Modifier + .weight(weight = 1f) + .testTag(C.Tag.STORIES_SCREEN_ORDER_BUTTON), onClick = onShopButtonClick, ) } diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 47d3487f7f..4f440160e3 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -97,11 +97,16 @@ detekt = "1.22.0" # endregion Tools # region Testing -espresso = "3.4.0" +espresso = "3.5.1" +espresso-intents = "3.5.1" junit = "4.13.2" -junitAndroidExt = "1.1.3" +junitAndroidExt = "1.1.5" mockk = "1.13.4" truth = "1.1.3" +kaspresso = "1.5.4" +kaspresso-compose = "1.5.4" +compose-junit = "1.6.2" +hamcrest = "2.2" # endregion Testing [plugins] @@ -183,10 +188,15 @@ detekt-formatting = { module = "io.gitlab.arturbosch.detekt:detekt-formatting", # region Test test-coroutine = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutine" } test-espresso = { module = "androidx.test.espresso:espresso-core", version.ref = "espresso" } +test-espresso-intents = { module = "androidx.test.espresso:espresso-intents", version.ref = "espresso-intents" } test-junit = { module = "junit:junit", version.ref = "junit" } test-junit-android = { module = "androidx.test.ext:junit", version.ref = "junitAndroidExt" } test-truth = { module = "com.google.truth:truth", version.ref = "truth" } test-mockk = { module = "io.mockk:mockk", version.ref = "mockk" } +test-kaspresso = { module = "com.kaspersky.android-components:kaspresso", version.ref = "kaspresso" } +test-kaspresso-compose = { module = "com.kaspersky.android-components:kaspresso-compose-support", version.ref = "kaspresso-compose"} +test-compose-junit = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "compose-junit" } +test-hamcrest = { module = "org.hamcrest:hamcrest", version.ref = "hamcrest" } # endregion Test # region Other From a784d120fc05daa24464349be4d71f08ffa6c2ff Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 4 Mar 2024 08:15:00 +0600 Subject: [PATCH 02/38] Updated on 2026-08-14 --- app/build.gradle.kts | 8 ++++ .../tangem/helpers/base/BaseAutoTestCase.kt | 27 +++++++++++ .../com/tangem/screens/StoriesScreen.kt | 32 +++++++++++++ .../kotlin/com/tangem/tests/StoriesTest.kt | 45 +++++++++++++++++++ .../tangem/tap/common/compose/resources/C.kt | 9 ++++ .../features/home/compose/StoriesScreen.kt | 4 +- .../home/compose/views/HomeButtons.kt | 10 ++++- gradle/dependencies.toml | 14 +++++- 8 files changed, 144 insertions(+), 5 deletions(-) create mode 100644 app/src/androidTest/kotlin/com/tangem/helpers/base/BaseAutoTestCase.kt create mode 100644 app/src/androidTest/kotlin/com/tangem/screens/StoriesScreen.kt create mode 100644 app/src/androidTest/kotlin/com/tangem/tests/StoriesTest.kt create mode 100644 app/src/main/java/com/tangem/tap/common/compose/resources/C.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 553088615c..ee0bc5020b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -11,6 +11,9 @@ plugins { android { namespace = "com.tangem.wallet" + testOptions { + animationsDisabled = true + } } configurations.all { @@ -207,6 +210,11 @@ dependencies { testImplementation(deps.test.truth) androidTestImplementation(deps.test.junit.android) androidTestImplementation(deps.test.espresso) + androidTestImplementation(deps.test.espresso.intents) + androidTestImplementation(deps.test.kaspresso) + androidTestImplementation(deps.test.kaspresso.compose) + androidTestImplementation(deps.test.compose.junit) + androidTestImplementation(deps.test.hamcrest) /** Chucker */ debugImplementation(deps.chucker) diff --git a/app/src/androidTest/kotlin/com/tangem/helpers/base/BaseAutoTestCase.kt b/app/src/androidTest/kotlin/com/tangem/helpers/base/BaseAutoTestCase.kt new file mode 100644 index 0000000000..6e0bfc19b2 --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/helpers/base/BaseAutoTestCase.kt @@ -0,0 +1,27 @@ +package com.tangem.helpers.base + +import android.Manifest +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.rule.GrantPermissionRule +import com.kaspersky.components.composesupport.config.withComposeSupport +import com.kaspersky.kaspresso.kaspresso.Kaspresso +import com.kaspersky.kaspresso.testcases.api.testcase.TestCase +import com.tangem.tap.MainActivity +import org.junit.Rule +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +open class BaseAutoTestCase : TestCase( + kaspressoBuilder = Kaspresso.Builder.withComposeSupport() +) { + + @get:Rule + open val composeTestRule = createAndroidComposeRule() + + @get: Rule + val grantPermissionRule: GrantPermissionRule = GrantPermissionRule.grant( + Manifest.permission.POST_NOTIFICATIONS, + Manifest.permission.CAMERA + ) +} \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/screens/StoriesScreen.kt b/app/src/androidTest/kotlin/com/tangem/screens/StoriesScreen.kt new file mode 100644 index 0000000000..38bdbce846 --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/screens/StoriesScreen.kt @@ -0,0 +1,32 @@ +package com.tangem.screens + +import androidx.compose.ui.test.SemanticsNodeInteractionsProvider +import com.tangem.tap.common.compose.resources.C +import com.tangem.wallet.R +import io.github.kakaocup.compose.node.element.ComposeScreen +import io.github.kakaocup.compose.node.element.KNode +import io.github.kakaocup.kakao.common.views.KView +import io.github.kakaocup.kakao.text.KButton + +class StoriesScreen(semanticsProvider: SemanticsNodeInteractionsProvider) : + ComposeScreen( + semanticsProvider = semanticsProvider, + viewBuilderAction = { hasTestTag(C.Tag.STORIES_SCREEN) } + ) { + + val scanButton: KNode = child { + hasTestTag(C.Tag.STORIES_SCREEN_SCAN_BUTTON) + } + + val orderButton: KNode = child { + hasTestTag(C.Tag.STORIES_SCREEN_ORDER_BUTTON) + } + + val enableNFCAlert: KView = KView { + withId(R.id.alertTitle) + } + + val cancelButton: KButton = KButton { + withId(android.R.id.button2) + } +} \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/tests/StoriesTest.kt b/app/src/androidTest/kotlin/com/tangem/tests/StoriesTest.kt new file mode 100644 index 0000000000..ce58b4a1be --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/tests/StoriesTest.kt @@ -0,0 +1,45 @@ +package com.tangem.tests + +import android.content.Intent.ACTION_VIEW +import androidx.test.espresso.intent.Intents +import com.tangem.helpers.base.BaseAutoTestCase +import com.tangem.screens.StoriesScreen +import com.tangem.tap.features.home.redux.HomeMiddleware.NEW_BUY_WALLET_URL +import io.github.kakaocup.compose.node.element.ComposeScreen +import io.github.kakaocup.kakao.intent.KIntent +import org.junit.Test + +class StoriesTest : BaseAutoTestCase() { + + @Test + fun clickOnButtons() = before { + Intents.init() + }.after { + Intents.release() + }.run { + ComposeScreen.onComposeScreen(composeTestRule) { + step("Click on \"Scan\" button") { + scanButton { + assertIsDisplayed() + performClick() + } + } + step("Assert: \"Scan card\" popup opened") { + enableNFCAlert.isDisplayed() + cancelButton.click() + device.uiDevice.pressBack() + } + step("Click on \"Order\" button") { + orderButton.performClick() + } + step("Assert: browser opened") { + val expectedIntent = KIntent { + hasAction(ACTION_VIEW) + hasData(NEW_BUY_WALLET_URL) + } + expectedIntent.intended() + device.uiDevice.pressBack() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/compose/resources/C.kt b/app/src/main/java/com/tangem/tap/common/compose/resources/C.kt new file mode 100644 index 0000000000..ca77ba62d2 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/compose/resources/C.kt @@ -0,0 +1,9 @@ +package com.tangem.tap.common.compose.resources + +object C { + object Tag { + const val STORIES_SCREEN = "STORIES_SCREEN_CONTAINER" + const val STORIES_SCREEN_SCAN_BUTTON = "STORIES_SCREEN_SCAN_BUTTON" + const val STORIES_SCREEN_ORDER_BUTTON = "STORIES_SCREEN_ORDER_BUTTON" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreen.kt b/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreen.kt index 73a454fe10..bd3f2a5dde 100644 --- a/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreen.kt @@ -15,11 +15,13 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import com.tangem.core.ui.res.TangemTheme +import com.tangem.tap.common.compose.resources.C import com.tangem.tap.features.home.compose.content.* import com.tangem.tap.features.home.compose.views.HomeButtons import com.tangem.tap.features.home.compose.views.SearchCurrenciesButton @@ -55,7 +57,7 @@ fun StoriesScreen( } StoriesScreenContent( - modifier = Modifier.fillMaxSize(), + modifier = Modifier.fillMaxSize().testTag(C.Tag.STORIES_SCREEN), config = StoriesScreenContentConfig( storiesSize = state.stories.lastIndex, currentStoryIndex = currentStoryIndex, diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtons.kt b/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtons.kt index 0533961886..75d308e2b7 100644 --- a/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtons.kt +++ b/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtons.kt @@ -8,6 +8,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter @@ -15,6 +16,7 @@ import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameter import com.tangem.core.ui.components.SpacerW12 import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition import com.tangem.core.ui.res.TangemTheme +import com.tangem.tap.common.compose.resources.C import com.tangem.wallet.R @Composable @@ -29,13 +31,17 @@ internal fun HomeButtons( modifier = modifier, ) { ScanCardButton( - modifier = Modifier.weight(weight = 1f), + modifier = Modifier + .weight(weight = 1f) + .testTag(C.Tag.STORIES_SCREEN_SCAN_BUTTON), showProgress = btnScanStateInProgress, onClick = onScanButtonClick, ) SpacerW12() OrderCardButton( - modifier = Modifier.weight(weight = 1f), + modifier = Modifier + .weight(weight = 1f) + .testTag(C.Tag.STORIES_SCREEN_ORDER_BUTTON), onClick = onShopButtonClick, ) } diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 4086422ee6..30a5d4ce98 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -97,11 +97,16 @@ detekt = "1.22.0" # endregion Tools # region Testing -espresso = "3.4.0" +espresso = "3.5.1" +espresso-intents = "3.5.1" junit = "4.13.2" -junitAndroidExt = "1.1.3" +junitAndroidExt = "1.1.5" mockk = "1.13.4" truth = "1.1.3" +kaspresso = "1.5.4" +kaspresso-compose = "1.5.4" +compose-junit = "1.6.2" +hamcrest = "2.2" # endregion Testing [plugins] @@ -183,10 +188,15 @@ detekt-formatting = { module = "io.gitlab.arturbosch.detekt:detekt-formatting", # region Test test-coroutine = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutine" } test-espresso = { module = "androidx.test.espresso:espresso-core", version.ref = "espresso" } +test-espresso-intents = { module = "androidx.test.espresso:espresso-intents", version.ref = "espresso-intents" } test-junit = { module = "junit:junit", version.ref = "junit" } test-junit-android = { module = "androidx.test.ext:junit", version.ref = "junitAndroidExt" } test-truth = { module = "com.google.truth:truth", version.ref = "truth" } test-mockk = { module = "io.mockk:mockk", version.ref = "mockk" } +test-kaspresso = { module = "com.kaspersky.android-components:kaspresso", version.ref = "kaspresso" } +test-kaspresso-compose = { module = "com.kaspersky.android-components:kaspresso-compose-support", version.ref = "kaspresso-compose"} +test-compose-junit = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "compose-junit" } +test-hamcrest = { module = "org.hamcrest:hamcrest", version.ref = "hamcrest" } # endregion Test # region Other From dec5075374c2df694fbff0650aa934063d921ba5 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 5 Mar 2024 12:14:56 +0000 Subject: [PATCH 03/38] Updated on 2026-08-14 --- gradle/dependencies.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 4b9302bf6b..ad38fa4c24 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -85,9 +85,9 @@ web3j = "4.10.1" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "develop-514" +tangemBlockchainSdk = "release-app_5.8-515" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds -tangemCardSdk = "develop-332" +tangemCardSdk = "release-app_5.8-333" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ # endregion Tangem From 91323fc0159c1819c67d9ab7e3795fd658160124 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 6 Mar 2024 09:07:36 +0000 Subject: [PATCH 04/38] Updated on 2026-08-14 --- .../wallet/subscribers/MultiWalletTokenListSubscriber.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt index 1c53ebee84..9185370945 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt @@ -36,9 +36,11 @@ internal class MultiWalletTokenListSubscriber( override fun tokenListFlow(): MaybeTokenListFlow = getTokenListUseCase(userWallet.walletId) override suspend fun onTokenListReceived(maybeTokenList: Either) { - updateSortingIfNeeded(maybeTokenList) + // TODO disabled for 5.7.2 because of potential critical + // updateSortingIfNeeded(maybeTokenList) } + @Suppress("UnusedPrivateMember") private suspend fun updateSortingIfNeeded(maybeTokenList: Either) { val tokenList = maybeTokenList.getOrElse { return } if (!checkNeedSorting(tokenList)) return From cfce6c0ef878ca5797bd81d14cdb182590c47460 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 6 Mar 2024 20:46:21 +0400 Subject: [PATCH 05/38] Updated on 2026-08-14 --- gradle/dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 08469f5c44..35111945c2 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -85,7 +85,7 @@ web3j = "4.10.1" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.7-509" +tangemBlockchainSdk = "release-app_5.7-517" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_5.7-330" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From fbc8c82590279a80026c4073500c55f013413634 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 7 Mar 2024 21:50:16 +0400 Subject: [PATCH 06/38] Updated on 2026-08-14 --- gradle/dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 35111945c2..ef80c04882 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -85,7 +85,7 @@ web3j = "4.10.1" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.7-517" +tangemBlockchainSdk = "release-app_5.7-519" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_5.7-330" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From 528a8c1ed0f7b4625f185c60819dcd4845421e6c Mon Sep 17 00:00:00 2001 From: Tangem Date: Sat, 9 Mar 2024 20:25:10 +0000 Subject: [PATCH 07/38] Updated on 2026-08-14 --- gradle/dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index ef80c04882..7d506e01f4 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -85,7 +85,7 @@ web3j = "4.10.1" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.7-519" +tangemBlockchainSdk = "release-app_5.7-520" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_5.7-330" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From 2c7eedd2263ddb86981459140e15ec8faba00d47 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sat, 9 Mar 2024 21:15:12 +0000 Subject: [PATCH 08/38] Updated on 2026-08-14 --- .../core/analytics/models/AnalyticsParam.kt | 6 +++ .../analytics/WalletScreenAnalyticsEvent.kt | 8 ++++ .../utils/TokenListAnalyticsSender.kt | 48 +++++++++++++++++++ 3 files changed, 62 insertions(+) diff --git a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt index a684154112..434d51d967 100644 --- a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt +++ b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt @@ -11,6 +11,11 @@ sealed class AnalyticsParam { companion object } + sealed class TokenBalanceState(val value: String) { + data object Empty : TokenBalanceState("Empty") + data object Full : TokenBalanceState("Full") + } + sealed class RateApp(val value: String) { object Liked : RateApp("Liked") object Disliked : RateApp("Disliked") @@ -124,6 +129,7 @@ sealed class AnalyticsParam { const val TOKEN = "Token" const val SOURCE = "Source" const val BALANCE = "Balance" + const val STATE = "State" const val BATCH = "Batch" const val TYPE = "Type" const val FEE_TYPE = "Fee Type" diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt index 2717ca2dfa..088622ed27 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt @@ -38,6 +38,14 @@ sealed class WalletScreenAnalyticsEvent { AnalyticsParam.BALANCE to balance.value, ), ) + + class TokenBalance(balance: AnalyticsParam.TokenBalanceState, token: String) : Basic( + event = "Token Balance", + params = mapOf( + AnalyticsParam.STATE to balance.value, + AnalyticsParam.TOKEN to token, + ), + ) } sealed class MainScreen( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/TokenListAnalyticsSender.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/TokenListAnalyticsSender.kt index 563ab70f1e..901116d2d9 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/TokenListAnalyticsSender.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/TokenListAnalyticsSender.kt @@ -1,10 +1,13 @@ package com.tangem.feature.wallet.presentation.wallet.analytics.utils import arrow.core.getOrElse +import com.tangem.blockchain.common.Blockchain +import com.tangem.common.extensions.isZero import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.domain.analytics.CheckIsWalletToppedUpUseCase import com.tangem.domain.analytics.model.WalletBalanceState +import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.NetworkGroup import com.tangem.domain.tokens.model.TokenList @@ -24,6 +27,8 @@ internal class TokenListAnalyticsSender @Inject constructor( private val screenLifecycleProvider: ScreenLifecycleProvider, ) { + private val balanceWasSentMap = mutableMapOf() + suspend fun send(displayedUiState: WalletState?, userWallet: UserWallet, tokenList: TokenList) { if (screenLifecycleProvider.isBackground) return if (displayedUiState == null || displayedUiState.pullToRefreshConfig.isRefreshing) return @@ -34,6 +39,7 @@ internal class TokenListAnalyticsSender @Inject constructor( sendBalanceLoadedEventIfNeeded(tokenList.totalFiatBalance, currenciesStatuses) sendToppedUpEventIfNeeded(userWallet, tokenList.totalFiatBalance, currenciesStatuses) sendUnreachableNetworksEventIfNeeded(currenciesStatuses) + sendTokenBalancesIfNeeded(currenciesStatuses) } private fun getCurrenciesStatuses(tokenList: TokenList): List = when (tokenList) { @@ -72,6 +78,48 @@ internal class TokenListAnalyticsSender @Inject constructor( } } + private fun sendTokenBalancesIfNeeded(currenciesStatuses: List) { + currenciesStatuses.forEach { + val status = it.value + if (status is CryptoCurrencyStatus.Loaded) { + sendTokenBalancesForSpecificBlockchains(it, status) + } + } + } + + // TODO hotfix/5.7.4 send event for log if tokens from polkadot ecosystem have balance + private fun sendTokenBalancesForSpecificBlockchains( + currencyStatus: CryptoCurrencyStatus, + balanceStatus: CryptoCurrencyStatus.Loaded, + ) { + // for now send only for Polkadot ecosystem blockchains + // later dependency on Blockchain will be removed and use token name + when (val blockchain = Blockchain.fromNetworkId(currencyStatus.currency.network.backendId)) { + Blockchain.Polkadot, + Blockchain.AlephZero, + Blockchain.Kusama, + -> { + if (balanceWasSentMap[blockchain.currency] != true) { + val tokenBalance = if (balanceStatus.amount.isZero()) { + AnalyticsParam.TokenBalanceState.Empty + } else { + AnalyticsParam.TokenBalanceState.Full + } + analyticsEventHandler.send( + Basic.TokenBalance( + balance = tokenBalance, + token = blockchain.currency, + ), + ) + balanceWasSentMap[blockchain.currency] = true + } + } + else -> { + /* no-op */ + } + } + } + private fun getCardBalanceState(fiatBalance: TokenList.FiatBalance.Loaded): AnalyticsParam.CardBalanceState { return if (fiatBalance.amount > BigDecimal.ZERO) { AnalyticsParam.CardBalanceState.Full From 9dc9f19da894edfccbcedade732cae4fc8cb03fb Mon Sep 17 00:00:00 2001 From: Tangem Date: Sat, 9 Mar 2024 21:15:12 +0000 Subject: [PATCH 09/38] Updated on 2026-08-14 --- .../java/com/tangem/core/analytics/models/AnalyticsParam.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt index 434d51d967..5f53ab40d7 100644 --- a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt +++ b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt @@ -12,8 +12,8 @@ sealed class AnalyticsParam { } sealed class TokenBalanceState(val value: String) { - data object Empty : TokenBalanceState("Empty") - data object Full : TokenBalanceState("Full") + object Empty : TokenBalanceState("Empty") + object Full : TokenBalanceState("Full") } sealed class RateApp(val value: String) { From e9c01dd3956c4eba5ab2d315e7abdd76cb184f07 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sun, 10 Mar 2024 08:08:17 +0000 Subject: [PATCH 10/38] Updated on 2026-08-14 --- .../analytics/utils/TokenListAnalyticsSender.kt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/TokenListAnalyticsSender.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/TokenListAnalyticsSender.kt index 901116d2d9..655a49a92b 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/TokenListAnalyticsSender.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/TokenListAnalyticsSender.kt @@ -17,6 +17,8 @@ import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnaly import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState import com.tangem.feature.wallet.presentation.wallet.utils.ScreenLifecycleProvider import dagger.hilt.android.scopes.ViewModelScoped +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import java.math.BigDecimal import javax.inject.Inject @@ -28,6 +30,7 @@ internal class TokenListAnalyticsSender @Inject constructor( ) { private val balanceWasSentMap = mutableMapOf() + private val mutex = Mutex() suspend fun send(displayedUiState: WalletState?, userWallet: UserWallet, tokenList: TokenList) { if (screenLifecycleProvider.isBackground) return @@ -78,7 +81,7 @@ internal class TokenListAnalyticsSender @Inject constructor( } } - private fun sendTokenBalancesIfNeeded(currenciesStatuses: List) { + private suspend fun sendTokenBalancesIfNeeded(currenciesStatuses: List) { currenciesStatuses.forEach { val status = it.value if (status is CryptoCurrencyStatus.Loaded) { @@ -88,7 +91,7 @@ internal class TokenListAnalyticsSender @Inject constructor( } // TODO hotfix/5.7.4 send event for log if tokens from polkadot ecosystem have balance - private fun sendTokenBalancesForSpecificBlockchains( + private suspend fun sendTokenBalancesForSpecificBlockchains( currencyStatus: CryptoCurrencyStatus, balanceStatus: CryptoCurrencyStatus.Loaded, ) { @@ -111,7 +114,9 @@ internal class TokenListAnalyticsSender @Inject constructor( token = blockchain.currency, ), ) - balanceWasSentMap[blockchain.currency] = true + mutex.withLock { + balanceWasSentMap[blockchain.currency] = true + } } } else -> { From 78ab92dc3ce4f6266cc3334cf0ebd696fa9a811c Mon Sep 17 00:00:00 2001 From: Tangem Date: Sun, 10 Mar 2024 20:04:14 +0000 Subject: [PATCH 11/38] Updated on 2026-08-14 --- gradle/dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 7d506e01f4..8174779a4e 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -85,7 +85,7 @@ web3j = "4.10.1" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.7-520" +tangemBlockchainSdk = "release-app_5.7-521" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_5.7-330" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From c54b0b4781d64d7cf1bb21f0361b6127cbd9c1af Mon Sep 17 00:00:00 2001 From: Tangem Date: Sun, 10 Mar 2024 20:40:07 +0000 Subject: [PATCH 12/38] Updated on 2026-08-14 --- gradle/dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 8174779a4e..8d3e50ca24 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -85,7 +85,7 @@ web3j = "4.10.1" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.7-521" +tangemBlockchainSdk = "release-app_5.7-522" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_5.7-330" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From 2f2dec3b67a278dec20fc8ed9ded19501a7199db Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 7 Mar 2024 13:27:35 +0400 Subject: [PATCH 13/38] Updated on 2026-08-14 --- app/src/main/assets/testnet_tokens.json | 2 +- .../data/tokens/utils/QuotesUnsupportedCurrenciesIdAdapter.kt | 1 + .../main/java/com/tangem/domain/common/extensions/Blockchain.kt | 2 +- gradle/dependencies.toml | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/src/main/assets/testnet_tokens.json b/app/src/main/assets/testnet_tokens.json index 14327b84be..b638128b34 100644 --- a/app/src/main/assets/testnet_tokens.json +++ b/app/src/main/assets/testnet_tokens.json @@ -610,7 +610,7 @@ ] }, { - "id": "aurora-near", + "id": "aurora-ethereum", "name": "Aurora Testnet", "symbol": "ETH", "networks": [ diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/QuotesUnsupportedCurrenciesIdAdapter.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/QuotesUnsupportedCurrenciesIdAdapter.kt index a038b0be78..e6d573b353 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/QuotesUnsupportedCurrenciesIdAdapter.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/QuotesUnsupportedCurrenciesIdAdapter.kt @@ -51,6 +51,7 @@ internal class QuotesUnsupportedCurrenciesIdAdapter { private val UNSUPPORTED_IDS_WITH_REPLACEMENTS = mapOf( "optimistic-ethereum" to "ethereum", "arbitrum-one" to "ethereum", + "aurora-ethereum" to "ethereum", ) } } \ No newline at end of file diff --git a/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt b/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt index 03841cc507..ce72fcde55 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt @@ -246,7 +246,7 @@ fun Blockchain.toCoinId(): String { Blockchain.Unknown -> "unknown" Blockchain.Hedera -> "hedera-hashgraph" Blockchain.HederaTestnet -> "hedera-hashgraph/test" - Blockchain.Aurora, Blockchain.AuroraTestnet -> "aurora-near" + Blockchain.Aurora, Blockchain.AuroraTestnet -> "aurora-ethereum" Blockchain.Areon, Blockchain.AreonTestnet -> "areon-network" Blockchain.PulseChain, Blockchain.PulseChainTestnet -> "pulsechain" } diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index ad38fa4c24..f148ddcaf0 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -85,7 +85,7 @@ web3j = "4.10.1" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.8-515" +tangemBlockchainSdk = "release-app_5.8-518" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_5.8-333" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From 42e55156eb57ccdeba13fab9622292498e22f18e Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 8 Mar 2024 09:18:07 +0800 Subject: [PATCH 14/38] Updated on 2026-08-14 --- .../domain/GetMultiWalletWarningsFactory.kt | 21 +++++++------------ .../domain/GetSingleWalletWarningsFactory.kt | 14 +------------ .../implementors/MultiWalletContentLoader.kt | 2 +- .../implementors/SingleWalletContentLoader.kt | 2 +- .../SingleWalletWithTokenContentLoader.kt | 2 +- .../wallet/state/model/WalletNotification.kt | 10 ++++----- .../MultiWalletWarningsSubscriber.kt | 10 ++++----- .../SingleWalletNotificationsSubscriber.kt | 10 ++++----- .../wallet/viewmodels/WalletViewModel.kt | 12 +++++------ 9 files changed, 32 insertions(+), 51 deletions(-) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt index 7de96e0c75..54bfd1bb5a 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt @@ -14,22 +14,23 @@ import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.NetworkGroup import com.tangem.domain.tokens.model.TokenList import com.tangem.domain.tokens.repository.PromoRepository -import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase +import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.usecase.IsNeedToBackupUseCase import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents import dagger.hilt.android.scopes.ViewModelScoped import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList -import kotlinx.coroutines.flow.* -import timber.log.Timber +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.conflate +import kotlinx.coroutines.flow.flow import javax.inject.Inject +import kotlin.collections.count @Suppress("LongParameterList") @ViewModelScoped internal class GetMultiWalletWarningsFactory @Inject constructor( - private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase, private val getTokenListUseCase: GetTokenListUseCase, private val isDemoCardUseCase: IsDemoCardUseCase, private val isReadyToShowRateAppUseCase: IsReadyToShowRateAppUseCase, @@ -40,15 +41,7 @@ internal class GetMultiWalletWarningsFactory @Inject constructor( private var readyForRateAppNotification = false - fun create(clickIntents: WalletClickIntents): Flow> { - val userWallet = getSelectedWalletSyncUseCase().fold( - ifLeft = { - Timber.e("Failed to get selected wallet $it") - return flowOf(value = persistentListOf()) - }, - ifRight = { it }, - ) - + fun create(userWallet: UserWallet, clickIntents: WalletClickIntents): Flow> { val cardTypesResolver = userWallet.scanResponse.cardTypesResolver val promoFlow = flow { emit(promoRepository.getChangellyPromoBanner()) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetSingleWalletWarningsFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetSingleWalletWarningsFactory.kt index edcbb820d8..c502e65890 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetSingleWalletWarningsFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetSingleWalletWarningsFactory.kt @@ -9,21 +9,17 @@ import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase import com.tangem.domain.tokens.error.CurrencyStatusError import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.wallets.models.UserWallet -import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase import com.tangem.domain.wallets.usecase.IsNeedToBackupUseCase import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents import dagger.hilt.android.scopes.ViewModelScoped import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.flow.* -import timber.log.Timber import javax.inject.Inject @ViewModelScoped internal class GetSingleWalletWarningsFactory @Inject constructor( - private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase, private val getPrimaryCurrencyStatusUpdatesUseCase: GetPrimaryCurrencyStatusUpdatesUseCase, private val isDemoCardUseCase: IsDemoCardUseCase, private val isReadyToShowRateAppUseCase: IsReadyToShowRateAppUseCase, @@ -33,15 +29,7 @@ internal class GetSingleWalletWarningsFactory @Inject constructor( private var readyForRateAppNotification = false - fun create(clickIntents: WalletClickIntents): Flow> { - val userWallet = getSelectedWalletSyncUseCase().fold( - ifLeft = { - Timber.e("Failed to get selected wallet $it") - return flowOf(value = persistentListOf()) - }, - ifRight = { it }, - ) - + fun create(userWallet: UserWallet, clickIntents: WalletClickIntents): Flow> { val cardTypesResolver = userWallet.scanResponse.cardTypesResolver return combine( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt index b12083df95..ba746cd830 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt @@ -44,7 +44,7 @@ internal class MultiWalletContentLoader( applyTokenListSortingUseCase = applyTokenListSortingUseCase, ), MultiWalletWarningsSubscriber( - userWalletId = userWallet.walletId, + userWallet = userWallet, stateHolder = stateHolder, clickIntents = clickIntents, getMultiWalletWarningsFactory = getMultiWalletWarningsFactory, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletContentLoader.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletContentLoader.kt index 45b3b831a5..b4c0f3252e 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletContentLoader.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletContentLoader.kt @@ -49,7 +49,7 @@ internal class SingleWalletContentLoader( getCryptoCurrencyActionsUseCase = getCryptoCurrencyActionsUseCase, ), SingleWalletNotificationsSubscriber( - userWalletId = userWallet.walletId, + userWallet = userWallet, stateHolder = stateHolder, clickIntents = clickIntents, getSingleWalletWarningsFactory = getSingleWalletWarningsFactory, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoader.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoader.kt index 42e26f47f2..525f4952ae 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoader.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoader.kt @@ -38,7 +38,7 @@ internal class SingleWalletWithTokenContentLoader( getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase, ), MultiWalletWarningsSubscriber( - userWalletId = userWallet.walletId, + userWallet = userWallet, stateHolder = stateHolder, clickIntents = clickIntents, getMultiWalletWarningsFactory = getMultiWalletWarningsFactory, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNotification.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNotification.kt index e7c5e885c4..e7d25584d5 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNotification.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNotification.kt @@ -27,12 +27,12 @@ sealed class WalletNotification(val config: NotificationConfig) { ), ) { - object DevCard : Critical( + data object DevCard : Critical( title = resourceReference(id = R.string.warning_developer_card_title), subtitle = resourceReference(id = R.string.warning_developer_card_message), ) - object FailedCardValidation : Critical( + data object FailedCardValidation : Critical( title = resourceReference(id = R.string.warning_failed_to_verify_card_title), subtitle = resourceReference(id = R.string.warning_failed_to_verify_card_message), ) @@ -64,12 +64,12 @@ sealed class WalletNotification(val config: NotificationConfig) { ), ) - object NetworksUnreachable : Warning( + data object NetworksUnreachable : Warning( title = resourceReference(id = R.string.warning_network_unreachable_title), subtitle = resourceReference(id = R.string.warning_network_unreachable_message), ) - object SomeNetworksUnreachable : Warning( + data object SomeNetworksUnreachable : Warning( title = resourceReference(id = R.string.warning_some_networks_unreachable_title), subtitle = resourceReference(id = R.string.warning_some_networks_unreachable_message), ) @@ -80,7 +80,7 @@ sealed class WalletNotification(val config: NotificationConfig) { onCloseClick = onCloseClick, ) - object TestNetCard : Warning( + data object TestNetCard : Warning( title = resourceReference(id = R.string.warning_testnet_card_title), subtitle = resourceReference(id = R.string.warning_testnet_card_message), ) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletWarningsSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletWarningsSubscriber.kt index d9029393c1..cf18d05d6d 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletWarningsSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletWarningsSubscriber.kt @@ -1,6 +1,6 @@ package com.tangem.feature.wallet.presentation.wallet.subscribers -import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.domain.GetMultiWalletWarningsFactory import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController @@ -15,7 +15,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.onEach internal class MultiWalletWarningsSubscriber( - private val userWalletId: UserWalletId, + private val userWallet: UserWallet, private val stateHolder: WalletStateController, private val clickIntents: WalletClickIntents, private val getMultiWalletWarningsFactory: GetMultiWalletWarningsFactory, @@ -23,13 +23,13 @@ internal class MultiWalletWarningsSubscriber( ) : WalletSubscriber() { override fun create(coroutineScope: CoroutineScope): Flow> { - return getMultiWalletWarningsFactory.create(clickIntents) + return getMultiWalletWarningsFactory.create(userWallet, clickIntents) .conflate() .distinctUntilChanged() .onEach { warnings -> - val displayedState = stateHolder.getWalletState(userWalletId) + val displayedState = stateHolder.getWalletState(userWallet.walletId) - stateHolder.update(SetWarningsTransformer(userWalletId, warnings)) + stateHolder.update(SetWarningsTransformer(userWallet.walletId, warnings)) walletWarningsAnalyticsSender.send(displayedState, warnings) } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletNotificationsSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletNotificationsSubscriber.kt index f266b681fd..a1ef1d4ee6 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletNotificationsSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletNotificationsSubscriber.kt @@ -1,6 +1,6 @@ package com.tangem.feature.wallet.presentation.wallet.subscribers -import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.domain.GetSingleWalletWarningsFactory import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController @@ -18,7 +18,7 @@ import kotlinx.coroutines.flow.onEach [REDACTED_AUTHOR] */ internal class SingleWalletNotificationsSubscriber( - private val userWalletId: UserWalletId, + private val userWallet: UserWallet, private val stateHolder: WalletStateController, private val getSingleWalletWarningsFactory: GetSingleWalletWarningsFactory, private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender, @@ -26,13 +26,13 @@ internal class SingleWalletNotificationsSubscriber( ) : WalletSubscriber() { override fun create(coroutineScope: CoroutineScope): Flow> { - return getSingleWalletWarningsFactory.create(clickIntents) + return getSingleWalletWarningsFactory.create(userWallet, clickIntents) .conflate() .distinctUntilChanged() .onEach { warnings -> - val displayedState = stateHolder.getWalletState(userWalletId) + val displayedState = stateHolder.getWalletState(userWallet.walletId) - stateHolder.update(SetWarningsTransformer(userWalletId, warnings)) + stateHolder.update(SetWarningsTransformer(userWallet.walletId, warnings)) walletWarningsAnalyticsSender.send(displayedState, warnings) } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt index 62d6a03cfd..4c3c0b6cff 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt @@ -249,6 +249,12 @@ internal class WalletViewModel @Inject constructor( } private suspend fun addWallet(action: WalletsUpdateActionResolver.Action.AddWallet) { + walletScreenContentLoader.load( + userWallet = action.selectedWallet, + clickIntents = clickIntents, + coroutineScope = viewModelScope, + ) + stateHolder.update( AddWalletTransformer( userWallet = action.selectedWallet, @@ -256,12 +262,6 @@ internal class WalletViewModel @Inject constructor( ), ) - walletScreenContentLoader.load( - userWallet = action.selectedWallet, - clickIntents = clickIntents, - coroutineScope = viewModelScope, - ) - withContext(dispatchers.io) { delay(timeMillis = 700) } scrollToWallet(index = action.selectedWalletIndex) From 48a7b84bb4589f4361daf3f9ff0fa8716b7c2447 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 12 Mar 2024 09:40:09 +0000 Subject: [PATCH 15/38] Updated on 2026-08-14 --- gradle/dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index f148ddcaf0..0065774dfd 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -85,7 +85,7 @@ web3j = "4.10.1" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.8-518" +tangemBlockchainSdk = "release-app_5.8-525" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_5.8-333" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From c668c24d279098c5cf26191273be24c9b5342f8a Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 11 Mar 2024 21:29:08 +0800 Subject: [PATCH 16/38] Updated on 2026-08-14 --- .../transformers/ScrollToWalletTransformer.kt | 3 ++ .../presentation/wallet/ui/WalletScreen.kt | 2 +- .../wallet/ui/utils/LazyListStateExt.kt | 9 +++- .../wallet/viewmodels/WalletViewModel.kt | 43 +++++++++++-------- .../viewmodels/WalletsUpdateActionResolver.kt | 40 +++++++++++------ .../intents/WalletCardClickIntents.kt | 17 -------- 6 files changed, 65 insertions(+), 49 deletions(-) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/ScrollToWalletTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/ScrollToWalletTransformer.kt index 6381c98d9d..d7135f0fdf 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/ScrollToWalletTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/ScrollToWalletTransformer.kt @@ -10,6 +10,7 @@ internal class ScrollToWalletTransformer( private val index: Int, private val currentStateProvider: Provider, private val stateUpdater: (WalletScreenState) -> Unit, + private val onConsume: () -> Unit = {}, ) : WalletScreenStateTransformer { override fun transform(prevState: WalletScreenState): WalletScreenState { @@ -23,6 +24,8 @@ internal class ScrollToWalletTransformer( event = consumedEvent(), ), ) + + onConsume() }, ), ) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt index 9d6dc72199..9819f09790 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt @@ -113,7 +113,7 @@ private fun WalletContent( alertConfig: WalletAlertState?, ) { var selectedWalletIndex by remember(state.selectedWalletIndex) { mutableIntStateOf(state.selectedWalletIndex) } - val selectedWallet = state.wallets[selectedWalletIndex] + val selectedWallet = state.wallets.getOrElse(selectedWalletIndex) { state.wallets[state.selectedWalletIndex] } val scaffoldContent: @Composable () -> Unit = { val movableItemModifier = Modifier.changeWalletAnimator(walletsListState) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/utils/LazyListStateExt.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/utils/LazyListStateExt.kt index a9ccd192d4..d1b702c3b9 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/utils/LazyListStateExt.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/utils/LazyListStateExt.kt @@ -20,5 +20,12 @@ internal suspend fun LazyListState.animateScrollByIndex(prevIndex: Int, newIndex } private fun calculateOffset(layoutInfo: LazyListLayoutInfo, prevIndex: Int, newIndex: Int): Float { - return layoutInfo.viewportSize.width.times(other = newIndex - prevIndex).toFloat() + val indexDifference = newIndex - prevIndex + val coefficient = if (indexDifference == 0) 1 else indexDifference + + return layoutInfo.getItemSizeWithSpacing().times(other = coefficient).toFloat() +} + +private fun LazyListLayoutInfo.getItemSizeWithSpacing(): Int { + return viewportSize.width - afterContentPadding - beforeContentPadding + mainAxisItemSpacing } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt index 4c3c0b6cff..1581b36984 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt @@ -5,6 +5,7 @@ import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.core.navigation.AppScreen import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.settings.CanUseBiometryUseCase @@ -199,6 +200,14 @@ internal class WalletViewModel @Inject constructor( is WalletsUpdateActionResolver.Action.UpdateWalletName -> { stateHolder.update(transformer = RenameWalletTransformer(action.selectedWalletId, action.name)) } + is WalletsUpdateActionResolver.Action.NoAccessibleWallets -> { + stateHolder.clear() + router.popBackStack(screen = AppScreen.Welcome) + } + is WalletsUpdateActionResolver.Action.NoWallets -> { + stateHolder.clear() + router.popBackStack(screen = AppScreen.Home) + } is WalletsUpdateActionResolver.Action.Unknown -> Unit } } @@ -274,23 +283,22 @@ internal class WalletViewModel @Inject constructor( coroutineScope = viewModelScope, ) - if (action.selectedWalletIndex != 0) { - /* - * If card is reset to factory settings, then Compose need some time to draw the WalletScreen. - * Otherwise, scroll isn't happened - */ - withContext(dispatchers.io) { delay(timeMillis = 700) } + /* + * If card is reset to factory settings, then Compose need some time to draw the WalletScreen. + * Otherwise, scroll isn't happened + */ + withContext(dispatchers.io) { delay(timeMillis = 1000) } - scrollToWallet(index = action.selectedWalletIndex) - - withContext(dispatchers.io) { delay(timeMillis = 1000) } - } - - stateHolder.update( - DeleteWalletTransformer( - selectedWalletIndex = action.selectedWalletIndex, - deletedWalletId = action.deletedWalletId, - ), + scrollToWallet( + index = action.selectedWalletIndex, + onConsume = { + stateHolder.update( + DeleteWalletTransformer( + selectedWalletIndex = action.selectedWalletIndex, + deletedWalletId = action.deletedWalletId, + ), + ) + }, ) } @@ -311,12 +319,13 @@ internal class WalletViewModel @Inject constructor( ) } - private fun scrollToWallet(index: Int) { + private fun scrollToWallet(index: Int, onConsume: () -> Unit = {}) { stateHolder.update( ScrollToWalletTransformer( index = index, currentStateProvider = Provider(action = stateHolder::value), stateUpdater = { newState -> stateHolder.update { newState } }, + onConsume = onConsume, ), ) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletsUpdateActionResolver.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletsUpdateActionResolver.kt index bdfd27558d..5e221221de 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletsUpdateActionResolver.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletsUpdateActionResolver.kt @@ -26,17 +26,21 @@ internal class WalletsUpdateActionResolver @Inject constructor( private var canSaveWallets: Boolean = false fun resolve(wallets: List, currentState: WalletScreenState, canSaveWallets: Boolean): Action { - val selectedWallet = wallets.getSelectedWallet() ?: return Action.Unknown + val selectedWallet = wallets.getSelectedWallet() - val action = when { - isFirstInitialization(currentState) -> { - createInitializeWalletsAction(wallets, selectedWallet, canSaveWallets) + val action = if (selectedWallet == null) { + createNoSelectedWalletAction(wallets) + } else { + when { + isFirstInitialization(currentState) -> { + createInitializeWalletsAction(wallets, selectedWallet, canSaveWallets) + } + isReinitialization(canSaveWallets) -> { + this.canSaveWallets = canSaveWallets + Action.ReinitializeWallets(selectedWallet = selectedWallet) + } + else -> getUpdateContentAction(currentState, wallets, selectedWallet) } - isReinitialization(canSaveWallets) -> { - this.canSaveWallets = canSaveWallets - Action.ReinitializeWallets(selectedWallet = selectedWallet) - } - else -> getUpdateContentAction(currentState, wallets, selectedWallet) } Timber.d("Resolved action: $action") @@ -47,11 +51,19 @@ internal class WalletsUpdateActionResolver @Inject constructor( private fun List.getSelectedWallet(): UserWallet? { return when { isEmpty() -> null - size == 1 -> first() + size == 1 -> if (first().isLocked) null else first() else -> getSelectedWalletSyncUseCase().fold(ifLeft = { null }, ifRight = { it }) } } + private fun createNoSelectedWalletAction(wallets: List): Action { + return when { + wallets.isEmpty() -> Action.NoWallets + wallets.all(UserWallet::isLocked) -> Action.NoAccessibleWallets + else -> Action.Unknown + } + } + private fun isFirstInitialization(state: WalletScreenState): Boolean { return state.selectedWalletIndex == NOT_INITIALIZED_WALLET_INDEX } @@ -305,8 +317,10 @@ internal class WalletsUpdateActionResolver @Inject constructor( } } - object Unknown : Action() { - override fun toString(): String = "Unknown" - } + data object NoAccessibleWallets : Action() + + data object NoWallets : Action() + + data object Unknown : Action() } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCardClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCardClickIntents.kt index fcbbdab810..7e47a2a490 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCardClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCardClickIntents.kt @@ -1,7 +1,6 @@ package com.tangem.feature.wallet.presentation.wallet.viewmodels.intents import com.tangem.core.analytics.api.AnalyticsEventHandler -import com.tangem.core.navigation.AppScreen import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.usecase.DeleteWalletUseCase import com.tangem.domain.wallets.usecase.UpdateWalletUseCase @@ -9,9 +8,7 @@ import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnaly import com.tangem.feature.wallet.presentation.wallet.loaders.WalletScreenContentLoader import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController import com.tangem.feature.wallet.presentation.wallet.state.model.WalletAlertState -import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState import com.tangem.feature.wallet.presentation.wallet.state.model.WalletEvent -import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSender import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.launch @@ -76,21 +73,7 @@ internal class WalletCardClickIntentsImplementor @Inject constructor( viewModelScope.launch(dispatchers.main) { walletScreenContentLoader.cancel(userWalletId) deleteWalletUseCase(userWalletId) - .onRight { popBackIfAllWalletsIsLocked() } .onLeft { Timber.e(it.toString()) } } } - - private fun popBackIfAllWalletsIsLocked() { - val wallets = stateHolder.value.wallets.map(WalletState::walletCardState) - val unlockedWallet = wallets.count { it !is WalletCardState.LockedContent } - - if (unlockedWallet == 1) { - stateHolder.clear() - - router.popBackStack( - screen = if (wallets.size > 1) AppScreen.Welcome else AppScreen.Home, - ) - } - } } \ No newline at end of file From 080cbdd5c75670840a3bae1c560038958dd550f8 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 5 Mar 2024 14:53:30 +0400 Subject: [PATCH 17/38] Updated on 2026-08-14 --- .../com/tangem/tap/domain/TangemSdkManager.kt | 4 ++-- .../di/UserWalletsListManagerModule.kt | 2 +- .../di/UserWalletsListManagerProvider.kt | 2 +- .../repository/DelegatedKeystoreManager.kt | 17 ++++++++++------- .../BiometricUserWalletsKeysRepository.kt | 2 +- gradle/dependencies.toml | 2 +- 6 files changed, 16 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt index d9e620497a..94b72fcf1e 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -6,13 +6,14 @@ import androidx.annotation.StringRes import com.tangem.Message import com.tangem.TangemSdk import com.tangem.common.* -import com.tangem.common.authentication.KeystoreManager +import com.tangem.common.authentication.keystore.KeystoreManager import com.tangem.common.card.FirmwareVersion import com.tangem.common.core.* import com.tangem.common.extensions.ByteArrayKey import com.tangem.common.services.secure.SecureStorage import com.tangem.common.usersCode.UserCodeRepository import com.tangem.core.analytics.Analytics +import com.tangem.core.analytics.models.Basic import com.tangem.crypto.bip39.DefaultMnemonic import com.tangem.crypto.hdWallet.DerivationPath import com.tangem.domain.card.repository.CardSdkConfigRepository @@ -25,7 +26,6 @@ import com.tangem.operations.derivation.DerivationTaskResponse import com.tangem.operations.derivation.DeriveMultipleWalletPublicKeysTask import com.tangem.operations.pins.SetUserCodeCommand import com.tangem.operations.usersetttings.SetUserCodeRecoveryAllowedTask -import com.tangem.core.analytics.models.Basic import com.tangem.tap.derivationsFinder import com.tangem.tap.domain.tasks.product.CreateProductWalletTask import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerModule.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerModule.kt index 1a162862e6..f2e03181e1 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerModule.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerModule.kt @@ -3,7 +3,7 @@ package com.tangem.tap.domain.userWalletList.di import android.content.Context import com.squareup.moshi.Moshi import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory -import com.tangem.common.authentication.AuthenticatedStorage +import com.tangem.common.authentication.storage.AuthenticatedStorage import com.tangem.common.json.TangemSdkAdapter import com.tangem.common.services.secure.SecureStorage import com.tangem.datasource.local.preferences.AppPreferencesStore diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerProvider.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerProvider.kt index efb44252c2..51bdd56ae6 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerProvider.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerProvider.kt @@ -3,7 +3,7 @@ package com.tangem.tap.domain.userWalletList.di import android.content.Context import com.squareup.moshi.Moshi import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory -import com.tangem.common.authentication.AuthenticatedStorage +import com.tangem.common.authentication.storage.AuthenticatedStorage import com.tangem.common.json.TangemSdkAdapter import com.tangem.common.services.secure.SecureStorage import com.tangem.domain.wallets.legacy.UserWalletsListManager diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DelegatedKeystoreManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DelegatedKeystoreManager.kt index 3c548bd255..f61d6df0d0 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DelegatedKeystoreManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DelegatedKeystoreManager.kt @@ -1,6 +1,6 @@ package com.tangem.tap.domain.userWalletList.repository -import com.tangem.common.authentication.KeystoreManager +import com.tangem.common.authentication.keystore.KeystoreManager import com.tangem.utils.Provider import javax.crypto.SecretKey @@ -8,15 +8,18 @@ internal class DelegatedKeystoreManager( private val keystoreManagerProvider: Provider, ) : KeystoreManager { - override suspend fun get(keyAlias: String): SecretKey? { - return keystoreManagerProvider().get(keyAlias) + override suspend fun get(masterKeyConfig: KeystoreManager.MasterKeyConfig, keyAlias: String): SecretKey? { + return keystoreManagerProvider().get(masterKeyConfig, keyAlias) } - override suspend fun get(keyAliases: Collection): Map { - return keystoreManagerProvider().get(keyAliases) + override suspend fun get( + masterKeyConfig: KeystoreManager.MasterKeyConfig, + keyAliases: Collection, + ): Map { + return keystoreManagerProvider().get(masterKeyConfig, keyAliases) } - override suspend fun store(keyAlias: String, key: SecretKey) { - return keystoreManagerProvider().store(keyAlias, key) + override suspend fun store(masterKeyConfig: KeystoreManager.MasterKeyConfig, keyAlias: String, key: SecretKey) { + keystoreManagerProvider().store(masterKeyConfig, keyAlias, key) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt index 0b06c9565f..d04e0122b7 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt @@ -4,7 +4,7 @@ import com.squareup.moshi.JsonAdapter import com.squareup.moshi.Moshi import com.squareup.moshi.Types import com.tangem.common.* -import com.tangem.common.authentication.AuthenticatedStorage +import com.tangem.common.authentication.storage.AuthenticatedStorage import com.tangem.common.core.TangemSdkError import com.tangem.common.services.secure.SecureStorage import com.tangem.domain.wallets.legacy.UserWalletsListError diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 0065774dfd..46c976be0f 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -87,7 +87,7 @@ web3j = "4.10.1" # region Tangem tangemBlockchainSdk = "release-app_5.8-525" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds -tangemCardSdk = "release-app_5.8-333" +tangemCardSdk = "release-app_5.8-335" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ # endregion Tangem From a1ba30971d99f1fdf6773c3e1353680b754eb634 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 12 Mar 2024 17:39:41 +0400 Subject: [PATCH 18/38] Updated on 2026-08-14 --- .../presentation/states/TokensListStateHolder.kt | 1 + .../tokens/impl/presentation/ui/TokensListScreen.kt | 12 +++++++++--- .../presentation/viewmodels/TokensListMigration.kt | 8 ++++---- .../presentation/viewmodels/TokensListViewModel.kt | 11 +++++++++++ 4 files changed, 25 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/states/TokensListStateHolder.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/states/TokensListStateHolder.kt index 101cdc8f52..bf48a93269 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/states/TokensListStateHolder.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/states/TokensListStateHolder.kt @@ -96,5 +96,6 @@ internal sealed interface TokensListStateHolder { override val tokens: Flow>, override val onTokensLoadStateChanged: (LoadState) -> Unit, val onSaveButtonClick: () -> Unit, + val isSavingInProgress: Boolean, ) : TokensListStateHolder } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/TokensListScreen.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/TokensListScreen.kt index 3dff613b36..70335b9609 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/TokensListScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/TokensListScreen.kt @@ -29,7 +29,10 @@ import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.TextUnitType import androidx.compose.ui.unit.dp import androidx.paging.PagingData -import androidx.paging.compose.* +import androidx.paging.compose.LazyPagingItems +import androidx.paging.compose.collectAsLazyPagingItems +import androidx.paging.compose.itemContentType +import androidx.paging.compose.itemKey import com.tangem.core.ui.components.PrimaryButton import com.tangem.core.ui.res.TangemTheme import com.tangem.tap.features.tokens.impl.presentation.states.TokenItemState @@ -63,10 +66,11 @@ internal fun TokensListScreen(stateHolder: TokensListStateHolder, modifier: Modi val verticalPadding = TangemTheme.dimens.spacing32 SaveChangesButton( - onClick = stateHolder.onSaveButtonClick, modifier = Modifier.onSizeChanged { with(density) { floatingButtonHeight = it.height.toDp() + verticalPadding } }, + showProgress = stateHolder.isSavingInProgress, + onClick = stateHolder.onSaveButtonClick, ) } }, @@ -176,13 +180,14 @@ private fun DifferentAddressesWarning() { } @Composable -private fun SaveChangesButton(onClick: () -> Unit, modifier: Modifier = Modifier) { +private fun SaveChangesButton(showProgress: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier) { PrimaryButton( modifier = modifier .imePadding() .padding(horizontal = TangemTheme.dimens.spacing16) .fillMaxWidth(), text = stringResource(id = R.string.common_save_changes), + showProgress = showProgress, onClick = onClick, ) } @@ -237,6 +242,7 @@ private class TokensListScreenProvider : CollectionPreviewParameterProvider) { diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListViewModel.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListViewModel.kt index 0853b72b8f..2464c82a17 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListViewModel.kt @@ -11,6 +11,8 @@ import androidx.lifecycle.viewModelScope import androidx.paging.* import com.tangem.blockchain.common.Blockchain import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.core.navigation.AppScreen +import com.tangem.core.navigation.NavigationAction import com.tangem.core.ui.extensions.getActiveIconRes import com.tangem.core.ui.extensions.getGreyedOutIconRes import com.tangem.domain.card.DerivePublicKeysUseCase @@ -24,6 +26,7 @@ import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase import com.tangem.domain.tokens.GetCryptoCurrenciesUseCase import com.tangem.domain.tokens.TokenWithBlockchain import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase +import com.tangem.tap.common.extensions.dispatchWithMain import com.tangem.tap.common.extensions.fullNameWithoutTestnet import com.tangem.tap.common.extensions.getNetworkName import com.tangem.tap.features.tokens.impl.domain.TokensListInteractor @@ -122,6 +125,7 @@ internal class TokensListViewModel @Inject constructor( isDifferentAddressesBlockVisible = isDifferentAddressesBlockVisible(), tokens = getInitialTokensList(), onTokensLoadStateChanged = actionsHandler::onTokensLoadStateChanged, + isSavingInProgress = false, onSaveButtonClick = actionsHandler::onSaveButtonClick, ) } else { @@ -308,13 +312,20 @@ internal class TokensListViewModel @Inject constructor( } fun onSaveButtonClick() { + val state = uiState as? TokensListStateHolder.ManageContent ?: return + analyticsSender.sendWhenSaveButtonClicked() viewModelScope.launch(dispatchers.main) { + uiState = state.copy(isSavingInProgress = true) + tokensListMigration.onSaveButtonClick( changedTokensList = changedTokensList, changedBlockchainList = changedBlockchainList, ) + + uiState = state.copy(isSavingInProgress = false) + store.dispatchWithMain(NavigationAction.PopBackTo(screen = AppScreen.Wallet)) } } From 567cc9541d1156ec297e8f175682a7a554fa22c3 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 13 Mar 2024 16:30:25 +0000 Subject: [PATCH 19/38] Updated on 2026-08-14 --- .../com/tangem/core/ui/components/buttons/actions/Actions.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/Actions.kt b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/Actions.kt index 9b56a22f57..3d0b87e26a 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/Actions.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/Actions.kt @@ -11,6 +11,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextOverflow @@ -86,7 +87,8 @@ private fun Button( Row( modifier = modifier .heightIn(min = TangemTheme.dimens.size36) - .background(color = backgroundColor, shape = shape) + .clip(shape) + .background(color = backgroundColor) .clickable(enabled = config.enabled, onClick = config.onClick) .padding(start = TangemTheme.dimens.spacing16, end = TangemTheme.dimens.spacing24) .padding(vertical = TangemTheme.dimens.spacing8), From 1da2ca6d1f98d555f792e0d989a1d4e907bd659d Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 12 Mar 2024 16:18:53 +0000 Subject: [PATCH 20/38] Updated on 2026-08-14 --- .../local/datastore/FileDataStore.kt | 17 +++++-- .../data/common/cache/DefaultCacheRegistry.kt | 47 ++++++++++++------- .../repository/DefaultNetworksRepository.kt | 42 +++++++++++------ .../repository/DefaultQuotesRepository.kt | 25 ++++++---- 4 files changed, 87 insertions(+), 44 deletions(-) diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/datastore/FileDataStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/datastore/FileDataStore.kt index e9b2317f35..f67f25584f 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/datastore/FileDataStore.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/datastore/FileDataStore.kt @@ -8,6 +8,8 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.map +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import timber.log.Timber @Deprecated("Use shared preferences data store instead") @@ -16,6 +18,7 @@ internal class FileDataStore( private val adapter: JsonAdapter, ) : StringKeyDataStore { + private val mutex = Mutex() private val writeTrigger = Trigger() override suspend fun isEmpty(): Boolean { val e = NotImplementedError("`isEmpty()` function not implemented for `FileDataStore`") @@ -28,7 +31,11 @@ internal class FileDataStore( override fun get(key: String): Flow { return writeTrigger - .map { getInternal(key) } + .map { + mutex.withLock { + getInternal(key) + } + } .filterNotNull() .distinctUntilChanged() } @@ -53,10 +60,12 @@ internal class FileDataStore( override suspend fun store(key: String, value: Value) { try { - val json = adapter.toJson(value) + mutex.withLock { + val json = adapter.toJson(value) - fileReader.rewriteFile(json, key) - writeTrigger.trigger() + fileReader.rewriteFile(json, key) + writeTrigger.trigger() + } } catch (e: Throwable) { Timber.e(e, "Unable to write file: $key") } diff --git a/data/common/src/main/kotlin/com/tangem/data/common/cache/DefaultCacheRegistry.kt b/data/common/src/main/kotlin/com/tangem/data/common/cache/DefaultCacheRegistry.kt index 6d6f19cd0c..d0938c6718 100644 --- a/data/common/src/main/kotlin/com/tangem/data/common/cache/DefaultCacheRegistry.kt +++ b/data/common/src/main/kotlin/com/tangem/data/common/cache/DefaultCacheRegistry.kt @@ -3,15 +3,21 @@ package com.tangem.data.common.cache import com.tangem.datasource.local.cache.CacheKeysStore import com.tangem.datasource.local.cache.model.CacheKey import kotlinx.coroutines.NonCancellable +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext import org.joda.time.Duration import org.joda.time.LocalDateTime import timber.log.Timber +import java.util.concurrent.ConcurrentHashMap internal class DefaultCacheRegistry( private val cacheKeysStore: CacheKeysStore, ) : CacheRegistry { + private val mutex = Mutex() + private val mutexes = ConcurrentHashMap() + override suspend fun isExpired(key: String): Boolean { val cacheKey = cacheKeysStore.getSyncOrNull(key) ?: return true @@ -41,27 +47,36 @@ internal class DefaultCacheRegistry( expireIn: Duration, block: suspend () -> Unit, ) { - val isExpired = isExpired(key) || skipCache - if (!isExpired) return + // use a separate mutexForKey for each key to avoid multiple calls block() to the same key + // also used mutex to safe create mutexForKey, otherwise it can lead to multiple calls for the same key + val mutexForKey = mutex.withLock { + mutexes.getOrPut(key) { Mutex() } + } + mutexForKey.withLock { + val isExpired = isExpired(key) || skipCache + if (!isExpired) { + return + } - try { - Timber.d("Invoke the action associated with the cache key: $key") + try { + Timber.d("Invoke the action associated with the cache key: $key") - cacheKeysStore.store( - key = CacheKey( - id = key, - updatedAt = LocalDateTime.now(), - expiresIn = expireIn, - ), - ) + cacheKeysStore.store( + key = CacheKey( + id = key, + updatedAt = LocalDateTime.now(), + expiresIn = expireIn, + ), + ) - block() - } catch (e: Throwable) { - Timber.e(e, "The action related to the cache key has failed: $key") + block() + } catch (e: Throwable) { + Timber.e(e, "The action related to the cache key has failed: $key") - invalidate(key) + invalidate(key) - throw e + throw e + } } } } \ No newline at end of file diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt index fc4296fdb8..44096bdcd7 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt @@ -56,8 +56,9 @@ internal class DefaultNetworksRepository( .cancellable() override suspend fun fetchNetworkPendingTransactions(userWalletId: UserWalletId, networks: Set) { + val currencies = getCurrencies(userWalletId, networks) withContext(dispatchers.io) { - fetchNetworksPendingTransactions(userWalletId, networks) + fetchNetworksPendingTransactions(userWalletId, networks, currencies) } } @@ -80,23 +81,28 @@ internal class DefaultNetworksRepository( networks: Set, refresh: Boolean, ) { + val currencies = getCurrencies(userWalletId, networks) coroutineScope { networks .map { network -> async { - fetchNetworkStatusIfCacheExpired(userWalletId, network, refresh) + fetchNetworkStatusIfCacheExpired(userWalletId, network, currencies, refresh) } } .awaitAll() } } - private suspend fun fetchNetworksPendingTransactions(userWalletId: UserWalletId, networks: Set) { + private suspend fun fetchNetworksPendingTransactions( + userWalletId: UserWalletId, + networks: Set, + currencies: Sequence, + ) { coroutineScope { networks .map { network -> async { - fetchNetworkPendingTransactions(userWalletId, network) + fetchNetworkPendingTransactions(userWalletId, network, currencies) } } .awaitAll() @@ -106,22 +112,28 @@ internal class DefaultNetworksRepository( private suspend fun fetchNetworkStatusIfCacheExpired( userWalletId: UserWalletId, network: Network, + currencies: Sequence, refresh: Boolean, ) { cacheRegistry.invokeOnExpire( key = getNetworksStatusesCacheKey(userWalletId, network), skipCache = refresh, - block = { fetchNetworkStatus(userWalletId, network) }, + block = { fetchNetworkStatus(userWalletId, network, currencies) }, ) } - private suspend fun fetchNetworkStatus(userWalletId: UserWalletId, network: Network) { - val currencies = getCurrencies(userWalletId, network) - + private suspend fun fetchNetworkStatus( + userWalletId: UserWalletId, + network: Network, + currencies: Sequence, + ) { val result = walletManagersFacade.update( userWalletId = userWalletId, network = network, - extraTokens = currencies.filterIsInstance().toSet(), + extraTokens = currencies + .filterIsInstance() + .filter { it.network == network } + .toSet(), ) withContext(NonCancellable) { @@ -137,9 +149,11 @@ internal class DefaultNetworksRepository( networksStatusesStore.store(userWalletId, networkStatus) } - private suspend fun fetchNetworkPendingTransactions(userWalletId: UserWalletId, network: Network) { - val currencies = getCurrencies(userWalletId, network) - + private suspend fun fetchNetworkPendingTransactions( + userWalletId: UserWalletId, + network: Network, + currencies: Sequence, + ) { val result = walletManagersFacade.updatePendingTransactions( userWalletId = userWalletId, network = network, @@ -158,7 +172,7 @@ internal class DefaultNetworksRepository( networksStatusesStore.store(userWalletId, networkStatus) } - private suspend fun getCurrencies(userWalletId: UserWalletId, network: Network): Sequence { + private suspend fun getCurrencies(userWalletId: UserWalletId, networks: Set): Sequence { val userWallet = requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) { "Unable to find user wallet with provided ID: $userWalletId" } @@ -180,7 +194,7 @@ internal class DefaultNetworksRepository( } } - return currencies.filter { it.network == network } + return currencies.filter { networks.contains(it.network) } } private suspend fun invalidateCacheKeyIfNeeded( diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultQuotesRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultQuotesRepository.kt index bd834c5450..d362189832 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultQuotesRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultQuotesRepository.kt @@ -17,6 +17,8 @@ import com.tangem.domain.tokens.repository.QuotesRepository import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.* +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext internal class DefaultQuotesRepository( @@ -32,6 +34,7 @@ internal class DefaultQuotesRepository( @Volatile private var quotesFetchedForAppCurrency: String? = null + private val mutex = Mutex() @OptIn(ExperimentalCoroutinesApi::class) override fun getQuotesUpdates(currenciesIds: Set): Flow> { @@ -42,7 +45,6 @@ internal class DefaultQuotesRepository( .filterNotNull() .flatMapLatest { appCurrency -> fetchExpiredQuotes(currenciesIds, appCurrency.id, refresh = false) - quotesStore.get(currenciesIds).map(quotesConverter::convertSet) } .cancellable() @@ -79,15 +81,19 @@ internal class DefaultQuotesRepository( appCurrencyId: String, refresh: Boolean, ) { - val expiredCurrenciesIds = filterExpiredCurrenciesIds( - currenciesIds = currenciesIds, - refresh = refresh || quotesFetchedForAppCurrency != appCurrencyId, - ) - if (expiredCurrenciesIds.isEmpty()) return + // TODO("[REDACTED_JIRA]") need refactor working with quotesFetchedForAppCurrency, + // it changes after filterExpiredCurrenciesIds + // calls with different coroutines and lead to fetchQuotes + mutex.withLock { + val expiredCurrenciesIds = filterExpiredCurrenciesIds( + currenciesIds = currenciesIds, + refresh = refresh || quotesFetchedForAppCurrency != appCurrencyId, + ) + if (expiredCurrenciesIds.isEmpty()) return - quotesFetchedForAppCurrency = appCurrencyId - - fetchQuotes(expiredCurrenciesIds, appCurrencyId) + quotesFetchedForAppCurrency = appCurrencyId + fetchQuotes(expiredCurrenciesIds, appCurrencyId) + } } private suspend fun fetchQuotes(rawCurrenciesIds: Set, appCurrencyId: String) { @@ -118,7 +124,6 @@ internal class DefaultQuotesRepository( ): Set { return currenciesIds.fold(hashSetOf()) { acc, currencyId -> val rawCurrencyId = currencyId.rawCurrencyId - if (rawCurrencyId != null && rawCurrencyId !in acc) { cacheRegistry.invokeOnExpire( key = getQuoteCacheKey(rawCurrencyId), From 364d064faf14be674a2ad37d37f38f8fdb0fd2df Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 14 Mar 2024 19:04:04 +0800 Subject: [PATCH 21/38] Updated on 2026-08-14 --- .../GeneralUserWalletsListManager.kt | 13 +++++- .../configs/feature_toggles_config.json | 2 +- .../wallet/viewmodels/WalletViewModel.kt | 29 ++----------- .../viewmodels/WalletsUpdateActionResolver.kt | 43 +++---------------- 4 files changed, 21 insertions(+), 66 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt index cf7497ea51..6fccee5d59 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt @@ -131,16 +131,25 @@ internal class GeneralUserWalletsListManager( Timber.d("Switch to ${manager::class.java.simpleName}") implementation.value = manager + + clearOldManager(manager) } .flowOn(dispatchers.io) .launchIn(applicationScope) } - /** Copy data from [old] manager and clean it */ + /** Copy data from [old] manager */ private suspend fun UserWalletsListManager.copyFrom(old: UserWalletsListManager): UserWalletsListManager { old.selectedUserWalletSync?.let { this.save(it) } - old.clear() return this } + + private suspend fun clearOldManager(current: UserWalletsListManager) { + if (current == biometricUserWalletsListManager) { + runtimeUserWalletsListManager.clear() + } else { + biometricUserWalletsListManager.clear() + } + } } \ No newline at end of file diff --git a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json index 1b7b643cea..639da533da 100644 --- a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json @@ -17,7 +17,7 @@ }, { "name": "GENERAL_USER_WALLETS_LIST_MANAGER_ENABLED", - "version": "5.7.0" + "version": "5.8.0" }, { "name": "LOCAL_USER_LOGS_ENABLED", diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt index 1581b36984..467f87f0fa 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt @@ -14,7 +14,6 @@ import com.tangem.domain.settings.ShouldShowSaveWalletScreenUseCase import com.tangem.domain.walletconnect.WalletConnectActions import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase import com.tangem.domain.wallets.usecase.GetWalletsUseCase -import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase import com.tangem.feature.wallet.presentation.deeplink.WalletDeepLinksHandler import com.tangem.feature.wallet.presentation.router.InnerWalletRouter import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent @@ -53,7 +52,6 @@ internal class WalletViewModel @Inject constructor( private val getWalletsUseCase: GetWalletsUseCase, private val shouldShowSaveWalletScreenUseCase: ShouldShowSaveWalletScreenUseCase, private val canUseBiometryUseCase: CanUseBiometryUseCase, - private val shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase, private val isWalletsScrollPreviewEnabled: IsWalletsScrollPreviewEnabled, private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, analyticsEventsHandler: AnalyticsEventHandler, @@ -74,7 +72,7 @@ internal class WalletViewModel @Inject constructor( suggestToEnableBiometrics() - subscribeOnWalletsUpdateFlow() + subscribeToUserWalletsUpdates() subscribeOnBalanceHiding() subscribeOnSelectedWalletFlow() } @@ -112,25 +110,12 @@ internal class WalletViewModel @Inject constructor( return router.isWalletLastScreen() && shouldShowSaveWalletScreenUseCase() && canUseBiometryUseCase() } - private fun subscribeOnWalletsUpdateFlow() { - viewModelScope.launch(dispatchers.main) { - shouldSaveUserWalletsUseCase() - .conflate() - .distinctUntilChanged() - .collectLatest(::subscribeToUserWalletsUpdates) - } - } - - private fun subscribeToUserWalletsUpdates(shouldSaveUserWallet: Boolean) { + private fun subscribeToUserWalletsUpdates() { getWalletsUseCase() .conflate() .distinctUntilChanged() .map { - walletsUpdateActionResolver.resolve( - wallets = it, - currentState = stateHolder.value, - canSaveWallets = shouldSaveUserWallet, - ) + walletsUpdateActionResolver.resolve(wallets = it, currentState = stateHolder.value) } .onEach(::updateWallets) .flowOn(dispatchers.main) @@ -182,14 +167,6 @@ internal class WalletViewModel @Inject constructor( private suspend fun updateWallets(action: WalletsUpdateActionResolver.Action) { when (action) { is WalletsUpdateActionResolver.Action.InitializeWallets -> initializeWallets(action) - is WalletsUpdateActionResolver.Action.ReinitializeWallets -> { - walletScreenContentLoader.load( - userWallet = action.selectedWallet, - clickIntents = clickIntents, - isRefresh = true, - coroutineScope = viewModelScope, - ) - } is WalletsUpdateActionResolver.Action.ReinitializeWallet -> reinitializeWallet(action) is WalletsUpdateActionResolver.Action.AddWallet -> addWallet(action) is WalletsUpdateActionResolver.Action.DeleteWallet -> deleteWallet(action) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletsUpdateActionResolver.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletsUpdateActionResolver.kt index 5e221221de..012aee200d 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletsUpdateActionResolver.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletsUpdateActionResolver.kt @@ -22,24 +22,16 @@ internal class WalletsUpdateActionResolver @Inject constructor( private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase, ) { - private var isInitialized: Boolean = false - private var canSaveWallets: Boolean = false - - fun resolve(wallets: List, currentState: WalletScreenState, canSaveWallets: Boolean): Action { + fun resolve(wallets: List, currentState: WalletScreenState): Action { val selectedWallet = wallets.getSelectedWallet() val action = if (selectedWallet == null) { createNoSelectedWalletAction(wallets) } else { - when { - isFirstInitialization(currentState) -> { - createInitializeWalletsAction(wallets, selectedWallet, canSaveWallets) - } - isReinitialization(canSaveWallets) -> { - this.canSaveWallets = canSaveWallets - Action.ReinitializeWallets(selectedWallet = selectedWallet) - } - else -> getUpdateContentAction(currentState, wallets, selectedWallet) + if (isFirstInitialization(currentState)) { + createInitializeWalletsAction(wallets, selectedWallet) + } else { + getUpdateContentAction(currentState, wallets, selectedWallet) } } @@ -68,14 +60,7 @@ internal class WalletsUpdateActionResolver @Inject constructor( return state.selectedWalletIndex == NOT_INITIALIZED_WALLET_INDEX } - private fun createInitializeWalletsAction( - wallets: List, - selectedWallet: UserWallet, - canSaveWallets: Boolean, - ): Action { - this.isInitialized = true - this.canSaveWallets = canSaveWallets - + private fun createInitializeWalletsAction(wallets: List, selectedWallet: UserWallet): Action { return Action.InitializeWallets( selectedWalletIndex = wallets.indexOfWallet(selectedWallet.walletId), selectedWallet = selectedWallet, @@ -83,10 +68,6 @@ internal class WalletsUpdateActionResolver @Inject constructor( ) } - private fun isReinitialization(canSaveWallets: Boolean): Boolean { - return isInitialized && this.canSaveWallets != canSaveWallets - } - private fun getUpdateContentAction( state: WalletScreenState, wallets: List, @@ -232,18 +213,6 @@ internal class WalletsUpdateActionResolver @Inject constructor( } } - /** - * Reinitialize wallets. Example, if user turned on wallets saving - * - * @property selectedWallet selected wallet - */ - data class ReinitializeWallets(val selectedWallet: UserWallet) : Action() { - - override fun toString(): String { - return "ReinitializeWallets(selectedWallet = ${selectedWallet.walletId})" - } - } - /** * Reinitialize selected wallet. Example, scanning a new card if wallets saving is turned off * From dfe0afb66da341be62d47334b1055ff3e8316a5c Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 15 Mar 2024 10:15:51 +0000 Subject: [PATCH 22/38] Updated on 2026-08-14 --- gradle/dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 46c976be0f..49cdba9902 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -85,7 +85,7 @@ web3j = "4.10.1" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.8-525" +tangemBlockchainSdk = "release-app_5.8-529" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_5.8-335" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From bee7b16accf3bad0f9de2cbd55c87afce6c69f92 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 12 Mar 2024 19:34:15 +0400 Subject: [PATCH 23/38] Updated on 2026-08-14 --- .../wallets/usecase/GetUserWalletUseCase.kt | 2 +- .../wallet/state/model/WalletState.kt | 30 ++------- .../model/holder/LockedWalletStateHolder.kt | 15 +---- .../CloseBottomSheetTransformer.kt | 32 ++++++---- .../InitializeWalletsTransformer.kt | 20 ++---- .../OpenBottomSheetTransformer.kt | 51 ++++++--------- .../transformers/UnlockWalletTransformer.kt | 6 -- .../MultiWalletCurrencyActionsConverter.kt | 4 +- .../wallet/viewmodels/WalletViewModel.kt | 1 - .../viewmodels/intents/WalletClickIntents.kt | 2 +- .../intents/WalletContentClickIntents.kt | 55 ++++++++++++++-- .../intents/WalletWarningsClickIntents.kt | 64 ++++++++++++------- 12 files changed, 148 insertions(+), 134 deletions(-) diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetUserWalletUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetUserWalletUseCase.kt index ae2ace600a..2b9c40ae27 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetUserWalletUseCase.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetUserWalletUseCase.kt @@ -12,7 +12,7 @@ import kotlinx.coroutines.flow.firstOrNull class GetUserWalletUseCase(private val walletsStateHolder: WalletsStateHolder) { - suspend operator fun invoke(userWalletId: UserWalletId): Either = either { + suspend operator fun invoke(userWalletId: UserWalletId): Either = either { val userWalletsListManager = ensureUserWalletListManagerNotNull( walletsStateHolder = walletsStateHolder, raise = GetUserWalletError::DataError, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletState.kt index 0792d8f25a..81c9017dee 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletState.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletState.kt @@ -32,19 +32,13 @@ internal sealed interface WalletState : WalletStateHolder { data class Locked( override val walletCardState: WalletCardState, + override val bottomSheetConfig: TangemBottomSheetConfig?, val onUnlockNotificationClick: () -> Unit, - val isBottomSheetShow: Boolean = false, - val onBottomSheetDismiss: () -> Unit = {}, - val onUnlockClick: () -> Unit, - val onScanClick: () -> Unit, ) : MultiCurrency(), WalletStateHolder by LockedWalletStateHolder( walletCardState, + bottomSheetConfig, onUnlockNotificationClick, - isBottomSheetShow, - onBottomSheetDismiss, - onUnlockClick, - onScanClick, ) { override val tokensListState = WalletTokensListState.ContentState.Locked @@ -70,21 +64,15 @@ internal sealed interface WalletState : WalletStateHolder { data class Locked( override val walletCardState: WalletCardState, override val buttons: PersistentList, + override val bottomSheetConfig: TangemBottomSheetConfig?, val onUnlockNotificationClick: () -> Unit, - val isBottomSheetShow: Boolean = false, - val onBottomSheetDismiss: () -> Unit = {}, - val onUnlockClick: () -> Unit, - val onScanClick: () -> Unit, val onExploreClick: () -> Unit, ) : SingleCurrency(), TxHistoryStateHolder by LockedTxHistoryStateHolder(onExploreClick), WalletStateHolder by LockedWalletStateHolder( walletCardState, + bottomSheetConfig, onUnlockNotificationClick, - isBottomSheetShow, - onBottomSheetDismiss, - onUnlockClick, - onScanClick, ) { override val marketPriceBlockState: MarketPriceBlockState? = null @@ -108,20 +96,14 @@ internal sealed interface WalletState : WalletStateHolder { data class Locked( override val walletCardState: WalletCardState, val onUnlockNotificationClick: () -> Unit, - val isBottomSheetShow: Boolean = false, - val onBottomSheetDismiss: () -> Unit = {}, - val onUnlockClick: () -> Unit, - val onScanClick: () -> Unit, + override val bottomSheetConfig: TangemBottomSheetConfig?, val onExploreClick: () -> Unit, ) : Visa(), TxHistoryStateHolder by LockedTxHistoryStateHolder(onExploreClick), WalletStateHolder by LockedWalletStateHolder( walletCardState, + bottomSheetConfig, onUnlockNotificationClick, - isBottomSheetShow, - onBottomSheetDismiss, - onUnlockClick, - onScanClick, ) { override val balancesAndLimitBlockState: BalancesAndLimitsBlockState? = null diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/holder/LockedWalletStateHolder.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/holder/LockedWalletStateHolder.kt index 6c361d5da7..96bef56446 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/holder/LockedWalletStateHolder.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/holder/LockedWalletStateHolder.kt @@ -1,7 +1,6 @@ package com.tangem.feature.wallet.presentation.wallet.state.model.holder import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig -import com.tangem.feature.wallet.presentation.wallet.state.model.WalletBottomSheetConfig import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification import com.tangem.feature.wallet.presentation.wallet.state.model.WalletPullToRefreshConfig @@ -18,11 +17,8 @@ internal interface WalletStateHolder { internal class LockedWalletStateHolder( override val walletCardState: WalletCardState, + override val bottomSheetConfig: TangemBottomSheetConfig?, onUnlockNotificationClick: () -> Unit, - isBottomSheetShow: Boolean, - onBottomSheetDismiss: () -> Unit, - onUnlockClick: () -> Unit, - onScanClick: () -> Unit, ) : WalletStateHolder { override val pullToRefreshConfig: WalletPullToRefreshConfig @@ -31,13 +27,4 @@ internal class LockedWalletStateHolder( override val warnings: ImmutableList = persistentListOf( WalletNotification.UnlockWallets(onUnlockNotificationClick), ) - - override val bottomSheetConfig = TangemBottomSheetConfig( - isShow = isBottomSheetShow, - onDismissRequest = onBottomSheetDismiss, - content = WalletBottomSheetConfig.UnlockWallets( - onUnlockClick = onUnlockClick, - onScanClick = onScanClick, - ), - ) } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/CloseBottomSheetTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/CloseBottomSheetTransformer.kt index 81106b0db4..02dd3d93aa 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/CloseBottomSheetTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/CloseBottomSheetTransformer.kt @@ -7,18 +7,28 @@ internal class CloseBottomSheetTransformer(userWalletId: UserWalletId) : WalletS override fun transform(prevState: WalletState): WalletState { return when (prevState) { - is WalletState.MultiCurrency.Content -> { - prevState.copy(bottomSheetConfig = prevState.bottomSheetConfig?.copy(isShow = false)) - } - is WalletState.MultiCurrency.Locked -> prevState.copy(isBottomSheetShow = false) - is WalletState.SingleCurrency.Content -> { - prevState.copy(bottomSheetConfig = prevState.bottomSheetConfig?.copy(isShow = false)) - } - is WalletState.SingleCurrency.Locked -> prevState.copy(isBottomSheetShow = false) - is WalletState.Visa.Content -> prevState.copy( - bottomSheetConfig = prevState.bottomSheetConfig?.copy(isShow = false), + is WalletState.MultiCurrency.Content -> prevState.copy( + bottomSheetConfig = updateConfig(prevState), + ) + is WalletState.MultiCurrency.Locked -> prevState.copy( + bottomSheetConfig = updateConfig(prevState), + ) + is WalletState.SingleCurrency.Content -> prevState.copy( + bottomSheetConfig = updateConfig(prevState), + ) + is WalletState.SingleCurrency.Locked -> prevState.copy( + bottomSheetConfig = updateConfig(prevState), + ) + is WalletState.Visa.Content -> prevState.copy( + bottomSheetConfig = updateConfig(prevState), + ) + is WalletState.Visa.Locked -> prevState.copy( + bottomSheetConfig = updateConfig(prevState), ) - is WalletState.Visa.Locked -> prevState.copy(isBottomSheetShow = false) } } + + private fun updateConfig(prevState: WalletState) = prevState.bottomSheetConfig?.copy( + isShow = false, + ) } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/InitializeWalletsTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/InitializeWalletsTransformer.kt index 7b94f4d770..73065cb197 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/InitializeWalletsTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/InitializeWalletsTransformer.kt @@ -13,7 +13,6 @@ import kotlinx.collections.immutable.toImmutableList internal class InitializeWalletsTransformer( private val selectedWalletIndex: Int, - private val selectedWallet: UserWallet, private val wallets: List, private val clickIntents: WalletClickIntents, ) : WalletScreenStateTransformer { @@ -23,7 +22,7 @@ internal class InitializeWalletsTransformer( override fun transform(prevState: WalletScreenState): WalletScreenState { return prevState.copy( onBackClick = clickIntents::onBackClick, - topBarConfig = createTopBarConfig(userWallet = selectedWallet), + topBarConfig = createTopBarConfig(), selectedWalletIndex = selectedWalletIndex, wallets = wallets .map { userWallet -> @@ -38,13 +37,9 @@ internal class InitializeWalletsTransformer( ) } - private fun createTopBarConfig(userWallet: UserWallet): WalletTopBarConfig { + private fun createTopBarConfig(): WalletTopBarConfig { return WalletTopBarConfig( - onDetailsClick = if (userWallet.isLocked) { - clickIntents::onOpenUnlockWalletsBottomSheetClick - } else { - clickIntents::onDetailsClick - }, + onDetailsClick = clickIntents::onDetailsClick, ) } @@ -53,27 +48,24 @@ internal class InitializeWalletsTransformer( multiCurrencyCreator = { WalletState.MultiCurrency.Locked( walletCardState = userWallet.toLockedWalletCardState(), + bottomSheetConfig = null, onUnlockNotificationClick = clickIntents::onOpenUnlockWalletsBottomSheetClick, - onUnlockClick = clickIntents::onUnlockWalletClick, - onScanClick = clickIntents::onScanToUnlockWalletClick, ) }, singleCurrencyCreator = { WalletState.SingleCurrency.Locked( walletCardState = userWallet.toLockedWalletCardState(), + bottomSheetConfig = null, buttons = createDisabledButtons(), onUnlockNotificationClick = clickIntents::onOpenUnlockWalletsBottomSheetClick, - onUnlockClick = clickIntents::onUnlockWalletClick, - onScanClick = clickIntents::onScanToUnlockWalletClick, onExploreClick = clickIntents::onExploreClick, ) }, visaWalletCreator = { WalletState.Visa.Locked( walletCardState = userWallet.toLockedWalletCardState(), + bottomSheetConfig = null, onUnlockNotificationClick = clickIntents::onOpenUnlockWalletsBottomSheetClick, - onUnlockClick = clickIntents::onUnlockWalletClick, - onScanClick = clickIntents::onScanToUnlockWalletClick, onExploreClick = clickIntents::onExploreClick, ) }, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/OpenBottomSheetTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/OpenBottomSheetTransformer.kt index 141f4a57d0..69417c564c 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/OpenBottomSheetTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/OpenBottomSheetTransformer.kt @@ -13,41 +13,30 @@ internal class OpenBottomSheetTransformer( override fun transform(prevState: WalletState): WalletState { return when (prevState) { - is WalletState.MultiCurrency.Content -> { - prevState.copy( - bottomSheetConfig = TangemBottomSheetConfig( - isShow = true, - onDismissRequest = onDismissBottomSheet, - content = content, - ), - ) - } - is WalletState.MultiCurrency.Locked -> { - prevState.copy(isBottomSheetShow = true, onBottomSheetDismiss = onDismissBottomSheet) - } - is WalletState.SingleCurrency.Content -> { - prevState.copy( - bottomSheetConfig = TangemBottomSheetConfig( - isShow = true, - onDismissRequest = onDismissBottomSheet, - content = content, - ), - ) - } - is WalletState.SingleCurrency.Locked -> { - prevState.copy(isBottomSheetShow = true, onBottomSheetDismiss = onDismissBottomSheet) - } + is WalletState.MultiCurrency.Content -> prevState.copy( + bottomSheetConfig = updateConfig(), + ) + is WalletState.MultiCurrency.Locked -> prevState.copy( + bottomSheetConfig = updateConfig(), + ) + is WalletState.SingleCurrency.Content -> prevState.copy( + bottomSheetConfig = updateConfig(), + ) + is WalletState.SingleCurrency.Locked -> prevState.copy( + bottomSheetConfig = updateConfig(), + ) is WalletState.Visa.Content -> prevState.copy( - bottomSheetConfig = TangemBottomSheetConfig( - isShow = true, - onDismissRequest = onDismissBottomSheet, - content = content, - ), + bottomSheetConfig = updateConfig(), ) is WalletState.Visa.Locked -> prevState.copy( - isBottomSheetShow = true, - onBottomSheetDismiss = onDismissBottomSheet, + bottomSheetConfig = updateConfig(), ) } } + + private fun updateConfig() = TangemBottomSheetConfig( + isShow = true, + onDismissRequest = onDismissBottomSheet, + content = content, + ) } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/UnlockWalletTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/UnlockWalletTransformer.kt index f22dedbc22..be16388c83 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/UnlockWalletTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/UnlockWalletTransformer.kt @@ -4,7 +4,6 @@ import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState -import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTopBarConfig import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletLoadingStateFactory import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents import kotlinx.collections.immutable.toImmutableList @@ -19,7 +18,6 @@ internal class UnlockWalletTransformer( override fun transform(prevState: WalletScreenState): WalletScreenState { return prevState.copy( - topBarConfig = prevState.topBarConfig.toUnlockedState(), wallets = prevState.wallets .map { state -> val unlockedWallet = getUnlockedWallet(state.walletCardState.id) @@ -29,10 +27,6 @@ internal class UnlockWalletTransformer( ) } - private fun WalletTopBarConfig.toUnlockedState(): WalletTopBarConfig { - return copy(onDetailsClick = clickIntents::onDetailsClick) - } - private fun getUnlockedWallet(walletId: UserWalletId): UserWallet? { return unlockedWallets.firstOrNull { it.walletId == walletId } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/MultiWalletCurrencyActionsConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/MultiWalletCurrencyActionsConverter.kt index 16876a9396..0e76d03e34 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/MultiWalletCurrencyActionsConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/MultiWalletCurrencyActionsConverter.kt @@ -8,7 +8,7 @@ import com.tangem.domain.tokens.model.TokenActionsState import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.impl.R import com.tangem.feature.wallet.presentation.wallet.state.model.TokenActionButtonConfig -import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletCurrencyActionsClickIntentsImplementor +import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletCurrencyActionsClickIntents import com.tangem.utils.converter.Converter import com.tangem.utils.isNullOrZero import kotlinx.collections.immutable.ImmutableList @@ -16,7 +16,7 @@ import kotlinx.collections.immutable.toImmutableList internal class MultiWalletCurrencyActionsConverter( private val userWallet: UserWallet, - private val clickIntents: WalletCurrencyActionsClickIntentsImplementor, + private val clickIntents: WalletCurrencyActionsClickIntents, ) : Converter> { override fun convert(value: TokenActionsState): ImmutableList { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt index 467f87f0fa..dab3f9be92 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt @@ -199,7 +199,6 @@ internal class WalletViewModel @Inject constructor( stateHolder.update( transformer = InitializeWalletsTransformer( selectedWalletIndex = action.selectedWalletIndex, - selectedWallet = action.selectedWallet, wallets = action.wallets, clickIntents = clickIntents, ), diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletClickIntents.kt index c27ff4424e..3cf99441ab 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletClickIntents.kt @@ -26,7 +26,7 @@ import javax.inject.Inject @ViewModelScoped internal class WalletClickIntents @Inject constructor( private val walletCardClickIntentsImplementor: WalletCardClickIntentsImplementor, - private val warningsClickIntentsImplementer: WalletWarningsClickIntentsImplementer, + private val warningsClickIntentsImplementer: WalletWarningsClickIntentsImplementor, private val currencyActionsClickIntentsImplementor: WalletCurrencyActionsClickIntentsImplementor, private val contentClickIntentsImplementor: WalletContentClickIntentsImplementor, private val visaWalletIntentsImplementor: VisaWalletIntentsImplementor, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletContentClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletContentClickIntents.kt index 3ebb1b4830..a91eec17a0 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletContentClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletContentClickIntents.kt @@ -1,5 +1,6 @@ package com.tangem.feature.wallet.presentation.wallet.viewmodels.intents +import arrow.core.getOrElse import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.tokens.GetCryptoCurrencyActionsUseCase @@ -9,17 +10,19 @@ import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.TokenActionsState import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase import com.tangem.domain.wallets.models.UserWallet -import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase +import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.feature.wallet.presentation.wallet.analytics.PortfolioEvent import com.tangem.feature.wallet.presentation.wallet.domain.unwrap import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController import com.tangem.feature.wallet.presentation.wallet.state.model.ActionsBottomSheetConfig +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletBottomSheetConfig import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.MultiWalletCurrencyActionsConverter import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.hilt.android.scopes.ViewModelScoped import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.take import kotlinx.coroutines.launch +import timber.log.Timber import javax.inject.Inject internal interface WalletContentClickIntents { @@ -43,8 +46,9 @@ internal interface WalletContentClickIntents { @ViewModelScoped internal class WalletContentClickIntentsImplementor @Inject constructor( private val stateHolder: WalletStateController, - private val currencyActionsClickIntentsImplementor: WalletCurrencyActionsClickIntentsImplementor, - private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase, + private val currencyActionsClickIntents: WalletCurrencyActionsClickIntentsImplementor, + private val walletWarningsClickIntents: WalletWarningsClickIntentsImplementor, + private val getUserWalletUseCase: GetUserWalletUseCase, private val getPrimaryCurrencyStatusUpdatesUseCase: GetPrimaryCurrencyStatusUpdatesUseCase, private val getCryptoCurrencyActionsUseCase: GetCryptoCurrencyActionsUseCase, private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase, @@ -55,7 +59,33 @@ internal class WalletContentClickIntentsImplementor @Inject constructor( override fun onBackClick() = router.popBackStack() - override fun onDetailsClick() = router.openDetailsScreen() + override fun onDetailsClick() { + viewModelScope.launch(dispatchers.main) { + val userWalletId = stateHolder.getSelectedWalletId() + val userWallet = getUserWalletUseCase(userWalletId).getOrElse { + Timber.e( + """ + Unable to get user wallet + |- ID: $userWalletId + |- Exception: $it + """.trimIndent(), + ) + + return@launch + } + + if (userWallet.isLocked) { + stateHolder.showBottomSheet( + WalletBottomSheetConfig.UnlockWallets( + onUnlockClick = walletWarningsClickIntents::onUnlockWalletClick, + onScanClick = walletWarningsClickIntents::onScanToUnlockWalletClick, + ), + ) + } else { + router.openDetailsScreen() + } + } + } override fun onManageTokensClick() { analyticsEventHandler.send(PortfolioEvent.ButtonManageTokens) @@ -74,9 +104,20 @@ internal class WalletContentClickIntentsImplementor @Inject constructor( } override fun onTokenItemLongClick(cryptoCurrencyStatus: CryptoCurrencyStatus) { - val userWallet = getSelectedWalletSyncUseCase.unwrap() ?: return - viewModelScope.launch(dispatchers.main) { + val userWalletId = stateHolder.getSelectedWalletId() + val userWallet = getUserWalletUseCase(userWalletId).getOrElse { + Timber.e( + """ + Unable to get user wallet + |- ID: $userWalletId + |- Exception: $it + """.trimIndent(), + ) + + return@launch + } + getCryptoCurrencyActionsUseCase(userWallet = userWallet, cryptoCurrencyStatus = cryptoCurrencyStatus) .take(count = 1) .collectLatest { @@ -90,7 +131,7 @@ internal class WalletContentClickIntentsImplementor @Inject constructor( ActionsBottomSheetConfig( actions = MultiWalletCurrencyActionsConverter( userWallet = userWallet, - clickIntents = currencyActionsClickIntentsImplementor, + clickIntents = currencyActionsClickIntents, ).convert(tokenActionsState), ), userWallet.walletId, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt index 5f6133df1b..d44e4a04e5 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt @@ -1,5 +1,6 @@ package com.tangem.feature.wallet.presentation.wallet.viewmodels.intents +import arrow.core.getOrElse import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.ui.extensions.resourceReference @@ -13,16 +14,17 @@ import com.tangem.domain.settings.ShouldShowSwapPromoWalletUseCase import com.tangem.domain.tokens.FetchTokenListUseCase import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.wallets.models.UnlockWalletsError -import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase +import com.tangem.domain.wallets.models.UserWallet +import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.domain.wallets.usecase.UnlockWalletsUseCase import com.tangem.feature.wallet.impl.R import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.Basic import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.MainScreen import com.tangem.feature.wallet.presentation.wallet.domain.ScanCardToUnlockWalletClickHandler import com.tangem.feature.wallet.presentation.wallet.domain.ScanCardToUnlockWalletError -import com.tangem.feature.wallet.presentation.wallet.domain.unwrap import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController import com.tangem.feature.wallet.presentation.wallet.state.model.WalletAlertState +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletBottomSheetConfig import com.tangem.feature.wallet.presentation.wallet.state.model.WalletEvent import com.tangem.feature.wallet.presentation.wallet.state.transformers.CloseBottomSheetTransformer import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSender @@ -57,17 +59,17 @@ internal interface WalletWarningsClickIntents { @Suppress("LongParameterList") @ViewModelScoped -internal class WalletWarningsClickIntentsImplementer @Inject constructor( +internal class WalletWarningsClickIntentsImplementor @Inject constructor( private val stateHolder: WalletStateController, private val walletEventSender: WalletEventSender, - private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase, - private val unlockWalletsUseCase: UnlockWalletsUseCase, private val derivePublicKeysUseCase: DerivePublicKeysUseCase, - private val scanCardToUnlockWalletClickHandler: ScanCardToUnlockWalletClickHandler, private val fetchTokenListUseCase: FetchTokenListUseCase, private val setCardWasScannedUseCase: SetCardWasScannedUseCase, private val neverToSuggestRateAppUseCase: NeverToSuggestRateAppUseCase, private val remindToRateAppLaterUseCase: RemindToRateAppLaterUseCase, + private val getUserWalletUseCase: GetUserWalletUseCase, + private val scanCardToUnlockWalletClickHandler: ScanCardToUnlockWalletClickHandler, + private val unlockWalletsUseCase: UnlockWalletsUseCase, private val analyticsEventHandler: AnalyticsEventHandler, private val reduxStateHolder: ReduxStateHolder, private val dispatchers: CoroutineDispatcherProvider, @@ -82,31 +84,33 @@ internal class WalletWarningsClickIntentsImplementer @Inject constructor( } private fun prepareOnboardingProcess() { - getSelectedWalletSyncUseCase.unwrap()?.let { - reduxStateHolder.dispatch( - LegacyAction.StartOnboardingProcess( - scanResponse = it.scanResponse, - canSkipBackup = false, - ), - ) + viewModelScope.launch(dispatchers.main) { + getSelectedUserWallet()?.let { + reduxStateHolder.dispatch( + LegacyAction.StartOnboardingProcess( + scanResponse = it.scanResponse, + canSkipBackup = false, + ), + ) + } } } override fun onCloseAlreadySignedHashesWarningClick() { - val userWallet = getSelectedWalletSyncUseCase.unwrap() ?: return - viewModelScope.launch(dispatchers.main) { + val userWallet = getSelectedUserWallet() ?: return@launch + setCardWasScannedUseCase(cardId = userWallet.cardId) } } override fun onGenerateMissedAddressesClick(missedAddressCurrencies: List) { - val userWallet = getSelectedWalletSyncUseCase.unwrap() ?: return - analyticsEventHandler.send(Basic.CardWasScanned(AnalyticsParam.ScannedFrom.Main)) analyticsEventHandler.send(MainScreen.NoticeScanYourCardTapped) viewModelScope.launch(dispatchers.main) { + val userWallet = getSelectedUserWallet() ?: return@launch + derivePublicKeysUseCase( userWalletId = userWallet.walletId, currencies = missedAddressCurrencies, @@ -119,11 +123,12 @@ internal class WalletWarningsClickIntentsImplementer @Inject constructor( override fun onOpenUnlockWalletsBottomSheetClick() { analyticsEventHandler.send(MainScreen.WalletUnlockTapped) - val config = requireNotNull(stateHolder.getSelectedWallet().bottomSheetConfig) { - "Impossible to open unlock wallet bottom sheet if it's null" - } - - stateHolder.showBottomSheet(config.content) + stateHolder.showBottomSheet( + WalletBottomSheetConfig.UnlockWallets( + onUnlockClick = this::onUnlockWalletClick, + onScanClick = this::onScanToUnlockWalletClick, + ), + ) } override fun onUnlockWalletClick() { @@ -204,4 +209,19 @@ internal class WalletWarningsClickIntentsImplementer @Inject constructor( shouldShowSwapPromoWalletUseCase.neverToShow() } } + + private suspend fun getSelectedUserWallet(): UserWallet? { + val userWalletId = stateHolder.getSelectedWalletId() + return getUserWalletUseCase(userWalletId).getOrElse { + Timber.e( + """ + Unable to get user wallet + |- ID: $userWalletId + |- Exception: $it + """.trimIndent(), + ) + + null + } + } } \ No newline at end of file From 9d03d44bf7d5ce2ee545e5121ac0d23c1a10ea34 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 15 Mar 2024 17:36:00 +0000 Subject: [PATCH 24/38] Updated on 2026-08-14 --- .../swap/converters/ExchangeStatusConverter.kt | 1 + .../feature/swap/domain/SwapInteractorImpl.kt | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/ExchangeStatusConverter.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/ExchangeStatusConverter.kt index 30ea8ec4cd..e3c5321be7 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/ExchangeStatusConverter.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/ExchangeStatusConverter.kt @@ -14,6 +14,7 @@ internal class ExchangeStatusConverter : Converter - if (balance.value.minus(spendAmount.value) > fee.multiply(percentsToFeeIncrease)) { + val balanceToCheck = when (fromTokenStatus.currency) { + is CryptoCurrency.Token -> { + balance.value + } + is CryptoCurrency.Coin -> { + // need to check balance minus amount only if amount to swap in native token + balance.value.minus(spendAmount.value) + } + } + if (balanceToCheck > fee.multiply(percentsToFeeIncrease)) { SwapFeeState.Enough } else { val nativeToken = getNativeToken(fromTokenStatus.currency.network.backendId) @@ -1510,9 +1519,9 @@ internal class SwapInteractorImpl @Inject constructor( } else { val token = currenciesRepository .getMultiCurrencyWalletCurrenciesSync(userWalletId) + .filterIsInstance() .find { - it is CryptoCurrency.Token && - it.contractAddress.equals(feePaidCurrency.contractAddress, ignoreCase = true) && + it.contractAddress.equals(feePaidCurrency.contractAddress, ignoreCase = true) && it.network.derivationPath == fromTokenStatus.currency.network.derivationPath } SwapFeeState.NotEnough( From c42298315d1c8dded417cdd97940d5e88d8e98e5 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 18 Mar 2024 09:17:52 +0000 Subject: [PATCH 25/38] Updated on 2026-08-14 --- gradle/dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 49cdba9902..f0365d4039 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -85,7 +85,7 @@ web3j = "4.10.1" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.8-529" +tangemBlockchainSdk = "release-app_5.8-531" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_5.8-335" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From 7bcadd9f4c7de91387003aed49a6f4bbb9655bba Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 18 Mar 2024 15:14:41 +0500 Subject: [PATCH 26/38] Updated on 2026-08-14 --- gradle/dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index f0365d4039..a5b5769a89 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -85,7 +85,7 @@ web3j = "4.10.1" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.8-531" +tangemBlockchainSdk = "release-app_5.8-533" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_5.8-335" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From e1480e005ea99bc0fb8749844bcbbfa995d6679d Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 18 Mar 2024 16:20:25 +0300 Subject: [PATCH 27/38] Updated on 2026-08-14 --- .../BiometricUserWalletsListManager.kt | 174 +++++++++--------- .../GeneralUserWalletsListManager.kt | 4 +- .../repository/DelegatedKeystoreManager.kt | 2 +- .../BiometricUserWalletsKeysRepository.kt | 3 +- .../welcome/redux/WelcomeMiddleware.kt | 3 +- .../wallets/legacy/UserWalletsListManager.kt | 32 +++- .../UserWalletsListManagerExtensions.kt | 5 +- .../wallets/usecase/UnlockWalletsUseCase.kt | 42 ++--- .../intents/WalletWarningsClickIntents.kt | 3 +- gradle/dependencies.toml | 2 +- 10 files changed, 154 insertions(+), 116 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt index a27e52d22e..daf1ce408b 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt @@ -3,6 +3,7 @@ package com.tangem.tap.domain.userWalletList.implementation import com.tangem.common.* import com.tangem.domain.wallets.legacy.UserWalletsListError import com.tangem.domain.wallets.legacy.UserWalletsListManager +import com.tangem.domain.wallets.legacy.UserWalletsListManager.Lockable.UnlockType import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey @@ -56,8 +57,8 @@ internal class BiometricUserWalletsListManager( override val walletsCount: Int get() = state.value.userWallets.size - override suspend fun unlock(throwIfNotAllWalletsUnlocked: Boolean): CompletionResult { - return unlockWithBiometryInternal() + override suspend fun unlock(type: UnlockType): CompletionResult { + return unlockAndSetSelectedUserWallet(type) .mapFailure { error -> Timber.e(error, "Unable to unlock user wallets") if (error is UserWalletsListError) { @@ -66,16 +67,8 @@ internal class BiometricUserWalletsListManager( UserWalletsListError.UnableToUnlockUserWallets(error) } } - .map { - val userWallets = state.value.userWallets - - if (throwIfNotAllWalletsUnlocked && userWallets.any(UserWallet::isLocked)) { - Timber.e("Some user wallets remain locked") - throw UserWalletsListError.NotAllUserWalletsUnlocked - } - - val selectedUserWallet = selectedUserWalletSync - if (selectedUserWallet == null) { + .map { selectedUserWallet -> + if (selectedUserWallet == null || selectedUserWallet.isLocked) { Timber.e("Unable to find selected user wallet") throw UserWalletsListError.NoUserWalletSelected } else { @@ -186,99 +179,116 @@ internal class BiometricUserWalletsListManager( changeSelectedUserWallet: Boolean, canOverridePublicInfo: Boolean, ): CompletionResult { - return saveEncryptionKeyIfNotNull(userWallet) - .flatMap { sensitiveInformationRepository.save(userWallet, encryptionKey = it) } - .flatMap { publicInformationRepository.save(userWallet, canOverridePublicInfo) } - .map { - if (changeSelectedUserWallet) { - selectedUserWalletRepository.set(userWallet.walletId) - } - } - .flatMap { loadModels() } - .doOnSuccess { - state.update { prevState -> - prevState.copy( - selectedUserWalletId = if (changeSelectedUserWallet) { - userWallet.walletId - } else { - prevState.selectedUserWalletId - }, - isLocked = prevState.userWallets.any { it.isLocked }, - ) - } - } - } - - private suspend fun unlockWithBiometryInternal(): CompletionResult { - return keysRepository.getAll() - .map { keys -> - state.update { prevState -> - prevState.copy( - encryptionKeys = (keys + prevState.encryptionKeys).distinctBy { it.walletId }, - ) - } - } - .flatMap { loadModels() } - .map { - state.update { prevState -> - val hasLockedUserWallets = prevState.userWallets.any { it.isLocked } - prevState.copy(isLocked = hasLockedUserWallets) - } - } - } - - private suspend fun saveEncryptionKeyIfNotNull(userWallet: UserWallet): CompletionResult { val encryptionKey = userWallet.scanResponse.card.encryptionKey ?.let { UserWalletEncryptionKey(userWallet.walletId, it) } + ?: return CompletionResult.Success(Unit) // No encryption key, no need to save - return if (encryptionKey != null) { - keysRepository.save(encryptionKey) - .doOnSuccess { - state.update { prevState -> - prevState.copy( - encryptionKeys = prevState.encryptionKeys - .plus(encryptionKey) - .distinctBy { it.walletId }, - ) - } + return keysRepository.save(encryptionKey) + .flatMap { sensitiveInformationRepository.save(userWallet, encryptionKey = encryptionKey.encryptionKey) } + .flatMap { publicInformationRepository.save(userWallet, canOverridePublicInfo) } + .flatMap { + loadUserWallets( + encryptionKeys = state.value.encryptionKeys + .plus(encryptionKey) + .distinctBy(UserWalletEncryptionKey::walletId), + ) + } + .doOnSuccess { loadedState -> + if (changeSelectedUserWallet) { + selectedUserWalletRepository.set(userWallet.walletId) + + state.value = loadedState.copy( + selectedUserWalletId = userWallet.walletId, + ) + } else { + state.value = loadedState } - .map { encryptionKey.encryptionKey } - } else { - CompletionResult.Success(data = null) - } + } + .map { /* Type erasing */ } } - private suspend fun loadModels(): CompletionResult { - return getSavedUserWallets() - .map { userWallets -> - if (userWallets.isNotEmpty()) { - state.update { prevState -> - val wallets = (userWallets + prevState.userWallets).distinctBy { it.walletId } + private suspend fun unlockAndSetSelectedUserWallet(type: UnlockType): CompletionResult { + return keysRepository.getAll() + .flatMap { encryptionKeys -> + loadUserWallets( + encryptionKeys = state.value.encryptionKeys + .plus(encryptionKeys) + .distinctBy(UserWalletEncryptionKey::walletId), + ) + } + .map { loadedState -> + when (type) { + UnlockType.ALL -> { + if (loadedState.isLocked) { + Timber.e("Some user wallets remain locked") - prevState.copy( - userWallets = wallets, - selectedUserWalletId = findOrSetSelectedWalletId(prevState.selectedUserWalletId, wallets), + state.value = loadedState + + throw UserWalletsListError.NotAllUserWalletsUnlocked + } else { + val selectedWallet = findOrSetSelectedWallet( + state.value.selectedUserWalletId, + loadedState.userWallets, + ) + + state.value = loadedState.copy( + selectedUserWalletId = selectedWallet?.walletId, + ) + + selectedWallet + } + } + UnlockType.ANY -> { + val selectedWallet = findOrSetSelectedWallet( + state.value.selectedUserWalletId, + loadedState.userWallets, ) + + state.value = loadedState.copy( + selectedUserWalletId = selectedWallet?.walletId, + ) + + selectedWallet + } + UnlockType.ALL_WITHOUT_SELECT -> { + state.value = loadedState + + findSelectedUserWallet() } } } } - private suspend fun getSavedUserWallets(): CompletionResult> { + private suspend fun loadUserWallets(encryptionKeys: List): CompletionResult { return publicInformationRepository.getAll() .map { it.toUserWallets() } .flatMap { userWallets -> - sensitiveInformationRepository.getAll(state.value.encryptionKeys) + sensitiveInformationRepository.getAll(encryptionKeys) .map { walletIdToSensitiveInformation -> userWallets.updateWith(walletIdToSensitiveInformation) } } + .map { userWallets -> + val prevState = state.value + + if (userWallets.isNotEmpty()) { + val newUserWallets = (userWallets + prevState.userWallets) + .distinctBy(UserWallet::walletId) + + prevState.copy( + userWallets = newUserWallets, + isLocked = newUserWallets.any(UserWallet::isLocked), + ) + } else { + prevState + } + } } - private fun findOrSetSelectedWalletId( + private fun findOrSetSelectedWallet( prevSelectedWalletId: UserWalletId?, userWallets: List, - ): UserWalletId? { + ): UserWallet? { val selectedWalletId = prevSelectedWalletId ?: selectedUserWalletRepository.get() var possibleSelectedUserWallet = findSelectedUserWallet(userWallets, selectedWalletId) @@ -290,7 +300,7 @@ internal class BiometricUserWalletsListManager( } } - return possibleSelectedUserWallet?.walletId + return possibleSelectedUserWallet } private fun changeSelectedUserWalletIdIfNeeded(walletsIdsToRemove: List) { diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt index 6fccee5d59..e0e160a524 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt @@ -100,10 +100,10 @@ internal class GeneralUserWalletsListManager( return implementation.value.get(userWalletId) } - override suspend fun unlock(throwIfNotAllWalletsUnlocked: Boolean): CompletionResult { + override suspend fun unlock(type: UserWalletsListManager.Lockable.UnlockType): CompletionResult { val implementation = implementation.value return if (implementation is UserWalletsListManager.Lockable) { - implementation.unlock() + implementation.unlock(type) } else { error("RuntimeUserWalletsListManager is not lockable") } diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DelegatedKeystoreManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DelegatedKeystoreManager.kt index f61d6df0d0..7e7a781189 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DelegatedKeystoreManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DelegatedKeystoreManager.kt @@ -14,7 +14,7 @@ internal class DelegatedKeystoreManager( override suspend fun get( masterKeyConfig: KeystoreManager.MasterKeyConfig, - keyAliases: Collection, + keyAliases: Set, ): Map { return keystoreManagerProvider().get(masterKeyConfig, keyAliases) } diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt index d04e0122b7..6f7c82d446 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt @@ -89,7 +89,6 @@ internal class BiometricUserWalletsKeysRepository( .doOnFailure { error -> when (error) { is TangemSdkError.KeystoreInvalidated -> { - // If the biometric cryptography key was invalidated, then delete all encryption keys getUserWalletsIds().forEach { userWalletId -> deleteEncryptionKey(userWalletId) } @@ -120,7 +119,7 @@ internal class BiometricUserWalletsKeysRepository( } private fun deleteEncryptionKey(userWalletId: UserWalletId) { - return authenticatedStorage.delete(StorageKey.UserWalletEncryptionKey(userWalletId).name) + authenticatedStorage.delete(StorageKey.UserWalletEncryptionKey(userWalletId).name) } private suspend fun getUserWalletsIds(): List { diff --git a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt index 5e03eed1a5..ca7d00d461 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt @@ -14,6 +14,7 @@ import com.tangem.core.navigation.NavigationAction import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.userwallets.UserWalletBuilder +import com.tangem.domain.wallets.legacy.UserWalletsListManager.Lockable.UnlockType import com.tangem.domain.wallets.legacy.unlockIfLockable import com.tangem.tap.* import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter @@ -86,7 +87,7 @@ internal class WelcomeMiddleware { """.trimIndent(), ) - userWalletsListManager.unlockIfLockable() + userWalletsListManager.unlockIfLockable(type = UnlockType.ANY) .doOnFailure { error -> Timber.e(error, "Unable to unlock user wallets with biometrics") store.dispatchWithMain(WelcomeAction.ProceedWithBiometrics.Error(error)) diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManager.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManager.kt index 7211f73a66..8a63233bed 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManager.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManager.kt @@ -105,16 +105,42 @@ interface UserWalletsListManager { /** * Receive saved [UserWallet]s, populate [userWallets] flow with it and set [isLocked] as false. * - * @param throwIfNotAllWalletsUnlocked Indicates that the function must throw - * [UserWalletsListError.NotAllUserWalletsUnlocked] if not all user wallets are unlocked. + * @param type Defines the behavior of the operation. * * @return [CompletionResult] of operation, with selected [UserWallet] * or null if there is no selected [UserWallet] */ - suspend fun unlock(throwIfNotAllWalletsUnlocked: Boolean = false): CompletionResult + suspend fun unlock(type: UnlockType): CompletionResult /** Remove [UserWallet]s from [userWallets] and set [isLocked] as true */ fun lock() + + /** + * Defines the behavior of the [unlock] operation. + * */ + enum class UnlockType { + /** + * Ensures that all stored [UserWallet]s are unlocked, + * or throws [UserWalletsListError.NotAllUserWalletsUnlocked]. + * + * In this type [selectedUserWallet] is either a previously selected [UserWallet] or the first stored + * [UserWallet]. + * */ + ALL, + + /** + * Ensures that at least one stored [UserWallet] is unlocked, + * or throws [UserWalletsListError.NoUserWalletSelected]. + * + * In this type [selectedUserWallet] is the first stored and unlocked [UserWallet]. + * */ + ANY, + + /** + * Same as [ALL] type, but this type can not change [selectedUserWallet] while unlocking. + * */ + ALL_WITHOUT_SELECT, + } } // For provider diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManagerExtensions.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManagerExtensions.kt index aa65450d99..0003a1300d 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManagerExtensions.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManagerExtensions.kt @@ -1,6 +1,7 @@ package com.tangem.domain.wallets.legacy import com.tangem.common.CompletionResult +import com.tangem.domain.wallets.legacy.UserWalletsListManager.Lockable.UnlockType import com.tangem.domain.wallets.models.UserWallet import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf @@ -43,8 +44,8 @@ val UserWalletsListManager.isLockedSync: Boolean * * @see UserWalletsListManager.Lockable.unlock * */ -suspend fun UserWalletsListManager.unlockIfLockable(): CompletionResult { - return asLockable()?.unlock() ?: CompletionResult.Failure(UserWalletsListError.UnableToUnlockUserWallets()) +suspend fun UserWalletsListManager.unlockIfLockable(type: UnlockType = UnlockType.ANY): CompletionResult { + return asLockable()?.unlock(type) ?: CompletionResult.Failure(UserWalletsListError.UnableToUnlockUserWallets()) } /** diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/UnlockWalletsUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/UnlockWalletsUseCase.kt index 5ae878661a..9b6bdf4997 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/UnlockWalletsUseCase.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/UnlockWalletsUseCase.kt @@ -5,6 +5,7 @@ import arrow.core.raise.either import arrow.core.raise.ensureNotNull import com.tangem.common.doOnFailure import com.tangem.domain.wallets.legacy.UserWalletsListError +import com.tangem.domain.wallets.legacy.UserWalletsListManager.Lockable.UnlockType import com.tangem.domain.wallets.legacy.WalletsStateHolder import com.tangem.domain.wallets.legacy.asLockable import com.tangem.domain.wallets.models.UnlockWalletsError @@ -18,27 +19,26 @@ import com.tangem.domain.wallets.models.UnlockWalletsError */ class UnlockWalletsUseCase(private val walletsStateHolder: WalletsStateHolder) { - suspend operator fun invoke(throwIfNotAllWalletsUnlocked: Boolean = false): Either = - either { - val userWalletsListManager = ensureNotNull( - value = walletsStateHolder.userWalletsListManager?.asLockable(), - raise = { - UnlockWalletsError.DataError( - cause = IllegalStateException("The lockable user wallets list manager could not be found"), - ) - }, - ) + suspend operator fun invoke(type: UnlockType = UnlockType.ANY): Either = either { + val userWalletsListManager = ensureNotNull( + value = walletsStateHolder.userWalletsListManager?.asLockable(), + raise = { + UnlockWalletsError.DataError( + cause = IllegalStateException("The lockable user wallets list manager could not be found"), + ) + }, + ) - userWalletsListManager.unlock(throwIfNotAllWalletsUnlocked) - .doOnFailure { error -> - val e = when (error) { - is UserWalletsListError.NoUserWalletSelected -> UnlockWalletsError.NoUserWalletSelected - is UserWalletsListError.NotAllUserWalletsUnlocked -> - UnlockWalletsError.NotAllUserWalletsUnlocked - else -> UnlockWalletsError.UnableToUnlockWallets - } - - raise(e) + userWalletsListManager.unlock(type) + .doOnFailure { error -> + val e = when (error) { + is UserWalletsListError.NoUserWalletSelected -> UnlockWalletsError.NoUserWalletSelected + is UserWalletsListError.NotAllUserWalletsUnlocked -> + UnlockWalletsError.NotAllUserWalletsUnlocked + else -> UnlockWalletsError.UnableToUnlockWallets } - } + + raise(e) + } + } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt index d44e4a04e5..f16f13dda2 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt @@ -13,6 +13,7 @@ import com.tangem.domain.settings.RemindToRateAppLaterUseCase import com.tangem.domain.settings.ShouldShowSwapPromoWalletUseCase import com.tangem.domain.tokens.FetchTokenListUseCase import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.wallets.legacy.UserWalletsListManager.Lockable.UnlockType import com.tangem.domain.wallets.models.UnlockWalletsError import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.usecase.GetUserWalletUseCase @@ -135,7 +136,7 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor( analyticsEventHandler.send(MainScreen.UnlockAllWithBiometrics) viewModelScope.launch(dispatchers.main) { - unlockWalletsUseCase(throwIfNotAllWalletsUnlocked = true) + unlockWalletsUseCase(type = UnlockType.ALL_WITHOUT_SELECT) .onRight { stateHolder.update(CloseBottomSheetTransformer(stateHolder.getSelectedWalletId())) } .onLeft(::handleUnlockWalletsError) } diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index a5b5769a89..7e5893d613 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -87,7 +87,7 @@ web3j = "4.10.1" # region Tangem tangemBlockchainSdk = "release-app_5.8-533" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds -tangemCardSdk = "release-app_5.8-335" +tangemCardSdk = "release-app_5.8-336" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ # endregion Tangem From 691dc512df9252abc42b45051ac87c43cd4604db Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 18 Mar 2024 21:16:35 +0500 Subject: [PATCH 28/38] Updated on 2026-08-14 --- gradle/dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 7e5893d613..d0ec1ec7bd 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -85,7 +85,7 @@ web3j = "4.10.1" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.8-533" +tangemBlockchainSdk = "release-app_5.8-534" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_5.8-336" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From 0d279ad0860993b5bc76ab1e5b9bd71766bbdd13 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 18 Mar 2024 18:47:07 +0000 Subject: [PATCH 29/38] Updated on 2026-08-14 --- gradle/dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index d0ec1ec7bd..22742d3d83 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -85,7 +85,7 @@ web3j = "4.10.1" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.8-534" +tangemBlockchainSdk = "release-app_5.8-535" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_5.8-336" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From e878dcd7c31a554e7979243d1afb9c28e18638db Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 18 Mar 2024 17:31:02 +0000 Subject: [PATCH 30/38] Updated on 2026-08-14 --- .../tangem/tap/features/details/ui/details/DetailsViewModel.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt index 5d4f2b20df..12698bcde4 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt @@ -4,6 +4,7 @@ import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import com.tangem.common.extensions.guard import com.tangem.core.analytics.Analytics +import com.tangem.core.analytics.models.Basic import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction import com.tangem.core.ui.event.StateEvent @@ -137,7 +138,7 @@ internal class DetailsViewModel( } private fun sendFeedback() { - Analytics.send(Settings.ButtonSendFeedback()) + Analytics.send(Basic.ButtonSupport()) store.dispatchOnMain(GlobalAction.SendEmail(FeedbackEmail())) } From 299b55b54096a49a6844d124a5c72497343002e9 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 18 Mar 2024 19:19:58 +0000 Subject: [PATCH 31/38] Updated on 2026-08-14 --- .../tangem/tap/common/CustomTabsManager.kt | 1 + .../domain/txhistory/models/TxStatusError.kt | 7 ++++++ .../GetExplorerTransactionUrlUseCase.kt | 17 ++++++++++++-- .../presentation/state/SendStateFactory.kt | 2 +- .../viewmodels/TokenDetailsViewModel.kt | 22 ++++++------------- .../intents/WalletContentClickIntents.kt | 8 ++++--- 6 files changed, 36 insertions(+), 21 deletions(-) create mode 100644 domain/txhistory/models/src/main/kotlin/com/tangem/domain/txhistory/models/TxStatusError.kt diff --git a/app/src/main/java/com/tangem/tap/common/CustomTabsManager.kt b/app/src/main/java/com/tangem/tap/common/CustomTabsManager.kt index 08f62eee72..c56c238e57 100644 --- a/app/src/main/java/com/tangem/tap/common/CustomTabsManager.kt +++ b/app/src/main/java/com/tangem/tap/common/CustomTabsManager.kt @@ -14,6 +14,7 @@ import com.tangem.wallet.R class CustomTabsManager { fun openUrl(url: String, context: Context) { + if (url.isEmpty()) return val customTabsIntent = CustomTabsIntent.Builder() .setDefaultColorSchemeParams( CustomTabColorSchemeParams.Builder() diff --git a/domain/txhistory/models/src/main/kotlin/com/tangem/domain/txhistory/models/TxStatusError.kt b/domain/txhistory/models/src/main/kotlin/com/tangem/domain/txhistory/models/TxStatusError.kt new file mode 100644 index 0000000000..51a367f46f --- /dev/null +++ b/domain/txhistory/models/src/main/kotlin/com/tangem/domain/txhistory/models/TxStatusError.kt @@ -0,0 +1,7 @@ +package com.tangem.domain.txhistory.models + +sealed class TxStatusError { + + data object EmptyUrlError : TxStatusError() + data class DataError(val cause: Throwable) : TxStatusError() +} \ No newline at end of file diff --git a/domain/txhistory/src/main/kotlin/com/tangem/domain/txhistory/usecase/GetExplorerTransactionUrlUseCase.kt b/domain/txhistory/src/main/kotlin/com/tangem/domain/txhistory/usecase/GetExplorerTransactionUrlUseCase.kt index d5628aa838..974d439c7e 100644 --- a/domain/txhistory/src/main/kotlin/com/tangem/domain/txhistory/usecase/GetExplorerTransactionUrlUseCase.kt +++ b/domain/txhistory/src/main/kotlin/com/tangem/domain/txhistory/usecase/GetExplorerTransactionUrlUseCase.kt @@ -1,12 +1,25 @@ package com.tangem.domain.txhistory.usecase +import arrow.core.Either +import arrow.core.raise.catch +import arrow.core.raise.either import com.tangem.domain.tokens.model.Network +import com.tangem.domain.txhistory.models.TxStatusError import com.tangem.domain.txhistory.repository.TxHistoryRepository class GetExplorerTransactionUrlUseCase( private val repository: TxHistoryRepository, ) { - operator fun invoke(txHash: String, networkId: Network.ID): String { - return repository.getTxExploreUrl(txHash, networkId) + operator fun invoke(txHash: String, networkId: Network.ID): Either { + return either { + catch( + block = { + repository.getTxExploreUrl(txHash, networkId).ifEmpty { + raise(TxStatusError.EmptyUrlError) + } + }, + catch = { raise(TxStatusError.DataError(it)) }, + ) + } } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt index 619ec36aab..cbd7ea840b 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt @@ -233,7 +233,7 @@ internal class SendStateFactory( val txUrl = getExplorerTransactionUrlUseCase( txHash = txData.hash.orEmpty(), networkId = cryptoCurrency.network.id, - ) + ).getOrElse { "" } return state.copy( sendState = state.sendState.copy( transactionDate = txData.date?.timeInMillis ?: System.currentTimeMillis(), diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt index fbdbcf8273..c5d482b009 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt @@ -6,7 +6,6 @@ import androidx.compose.runtime.setValue import androidx.lifecycle.* import androidx.paging.cachedIn import arrow.core.getOrElse -import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.address.AddressType import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.deeplink.DeepLinksRegistry @@ -600,20 +599,13 @@ internal class TokenDetailsViewModel @Inject constructor( } override fun onTransactionClick(txHash: String) { - // TODO: Fix tx urls [REDACTED_TASK_KEY] - when (Blockchain.fromId(cryptoCurrency.network.id.value)) { - Blockchain.TON, Blockchain.TONTestnet, - Blockchain.Decimal, Blockchain.DecimalTestnet, - -> return - else -> { - router.openUrl( - url = getExplorerTransactionUrlUseCase( - txHash = txHash, - networkId = cryptoCurrency.network.id, - ), - ) - } - } + getExplorerTransactionUrlUseCase( + txHash = txHash, + networkId = cryptoCurrency.network.id, + ).fold( + ifLeft = { Timber.e(it.toString()) }, + ifRight = { router.openUrl(url = it) }, + ) } override fun onRefreshSwipe() { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletContentClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletContentClickIntents.kt index a91eec17a0..cae3373a31 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletContentClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletContentClickIntents.kt @@ -146,9 +146,11 @@ internal class WalletContentClickIntentsImplementor @Inject constructor( ?.currency ?: return@launch - router.openUrl( - url = getExplorerTransactionUrlUseCase(txHash = txHash, networkId = currency.network.id), - ) + getExplorerTransactionUrlUseCase(txHash = txHash, networkId = currency.network.id) + .fold( + ifLeft = { Timber.e(it.toString()) }, + ifRight = { router.openUrl(url = it) }, + ) } } } \ No newline at end of file From 01338ceb4d7a2d565c47cd724e67928a409ec645 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 18 Mar 2024 19:26:16 +0000 Subject: [PATCH 32/38] Updated on 2026-08-14 --- gradle/dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 22742d3d83..b92767f469 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -85,7 +85,7 @@ web3j = "4.10.1" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.8-535" +tangemBlockchainSdk = "release-app_5.8-536" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_5.8-336" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From a801bf9f29013dd61527ab69c378ab2d8bf1f95e Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 19 Mar 2024 14:35:21 +0500 Subject: [PATCH 33/38] Updated on 2026-08-14 --- .../feature/qrscanning/QrScanningFragment.kt | 28 +++++++++++++++++-- .../QrScanningCameraDeniedBottomSheet.kt | 2 +- .../viewmodel/QrScanningViewModel.kt | 7 ++++- 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningFragment.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningFragment.kt index 41a8b85226..1c66e62a33 100644 --- a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningFragment.kt +++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningFragment.kt @@ -20,10 +20,10 @@ import com.google.mlkit.vision.common.InputImage import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.screen.ComposeFragment import com.tangem.core.ui.theme.AppThemeModeHolder -import com.tangem.feature.qrscanning.presentation.QrScanningContent -import com.tangem.feature.qrscanning.viewmodel.QrScanningViewModel import com.tangem.feature.qrscanning.inner.MLKitBarcodeAnalyzer import com.tangem.feature.qrscanning.navigation.QrScanningInnerRouter +import com.tangem.feature.qrscanning.presentation.QrScanningContent +import com.tangem.feature.qrscanning.viewmodel.QrScanningViewModel import dagger.hilt.android.AndroidEntryPoint import java.util.concurrent.ExecutorService import java.util.concurrent.Executors @@ -69,6 +69,11 @@ internal class QrScanningFragment : ComposeFragment() { requestCameraPermission() } + override fun onResume() { + super.onResume() + checkPermissionGranted() + } + override fun onDestroy() { super.onDestroy() cameraPermissionLauncher.unregister() @@ -80,11 +85,14 @@ internal class QrScanningFragment : ComposeFragment() { StatusBarTransparencyDisposable() QrScanningContent( executor = { cameraExecutor }, - analyzer = { analyzer }, + analyzer = { cameraAnalyzer }, uiState = viewModel.uiState.collectAsStateWithLifecycle().value, ) } + /** + * Method for requesting permission if there isn't one. + */ private fun requestCameraPermission() { if ( ContextCompat.checkSelfPermission( @@ -96,6 +104,20 @@ internal class QrScanningFragment : ComposeFragment() { } } + /** + * Method for checking if permission was granted after user opened Settings screen. + * If permission was granted dismiss bottom sheet. + */ + private fun checkPermissionGranted() { + if (ContextCompat.checkSelfPermission( + requireContext(), + Manifest.permission.CAMERA, + ) == PackageManager.PERMISSION_GRANTED + ) { + viewModel.onDismissBottomSheetState() + } + } + companion object { fun create() = QrScanningFragment() diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/QrScanningCameraDeniedBottomSheet.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/QrScanningCameraDeniedBottomSheet.kt index a4ca7198dc..6edcbe2ea2 100644 --- a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/QrScanningCameraDeniedBottomSheet.kt +++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/QrScanningCameraDeniedBottomSheet.kt @@ -36,7 +36,7 @@ private fun CameraDeniedBottomSheet(content: CameraDeniedBottomSheetConfig) { val intent: Intent = Intent( Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.fromParts("package", context.packageName, null), - ).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + ) ContextCompat.startActivity(context, intent, null) }, ) diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/QrScanningViewModel.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/QrScanningViewModel.kt index 5402f469b0..9e17c2f6a7 100644 --- a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/QrScanningViewModel.kt +++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/QrScanningViewModel.kt @@ -10,8 +10,9 @@ import com.tangem.feature.qrscanning.SourceType import com.tangem.feature.qrscanning.navigation.QrScanningInnerRouter import com.tangem.feature.qrscanning.presentation.QrScanningState import com.tangem.feature.qrscanning.presentation.QrScanningStateController -import com.tangem.feature.qrscanning.presentation.transformers.ShowCameraDeniedBottomSheetTransformer +import com.tangem.feature.qrscanning.presentation.transformers.DismissBottomSheetTransformer import com.tangem.feature.qrscanning.presentation.transformers.InitializeQrScanningStateTransformer +import com.tangem.feature.qrscanning.presentation.transformers.ShowCameraDeniedBottomSheetTransformer import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.StateFlow import javax.inject.Inject @@ -43,4 +44,8 @@ internal class QrScanningViewModel @Inject constructor( fun onCameraDeniedState() { stateHolder.update(ShowCameraDeniedBottomSheetTransformer(clickIntents)) } + + fun onDismissBottomSheetState() { + stateHolder.update(DismissBottomSheetTransformer()) + } } \ No newline at end of file From b2172951b37b73f4fbf1aa5bea6eb35e6d388170 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 19 Mar 2024 14:35:37 +0500 Subject: [PATCH 34/38] Updated on 2026-08-14 --- .../java/com/tangem/feature/qrscanning/QrScanningFragment.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningFragment.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningFragment.kt index 1c66e62a33..577380dc6b 100644 --- a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningFragment.kt +++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningFragment.kt @@ -47,6 +47,11 @@ internal class QrScanningFragment : ComposeFragment() { private val viewModel by viewModels() private var cameraExecutor: ExecutorService by Delegates.notNull() + // Camera requires its own analyzer instance due to flow of frames needed to be analyzed. + // Each new frame can cancel previous analysis e.i. image from the gallery can be skipped. + private val cameraAnalyzer: MLKitBarcodeAnalyzer by lazy(LazyThreadSafetyMode.NONE) { + MLKitBarcodeAnalyzer(viewModel::onQrScanned) + } private val analyzer: MLKitBarcodeAnalyzer by lazy(LazyThreadSafetyMode.NONE) { MLKitBarcodeAnalyzer(viewModel::onQrScanned) } From 5d47619e8f513590d659d8eed3151753a49fabb0 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 19 Mar 2024 18:14:41 +0800 Subject: [PATCH 35/38] Updated on 2026-08-14 --- .../src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt | 2 ++ .../java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt | 2 ++ 2 files changed, 4 insertions(+) diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt index cea2fca9cc..4566c64fae 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt @@ -703,6 +703,7 @@ internal class StateBuilder( return uiState.copy( sendCardData = uiState.sendCardData.copy( balance = cryptoCurrencyStatus.getFormattedAmount(isNeedSymbol = false), + token = cryptoCurrencyStatus, ), ) } @@ -716,6 +717,7 @@ internal class StateBuilder( return uiState.copy( receiveCardData = uiState.receiveCardData.copy( balance = cryptoCurrencyStatus.getFormattedAmount(isNeedSymbol = false), + token = cryptoCurrencyStatus, ), ) } diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt index a559fb5054..51969a1ec1 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt @@ -745,6 +745,8 @@ internal class SwapViewModel @Inject constructor( dataState = dataState.copy(toCryptoCurrency = it) stateBuilder.updateReceiveCurrencyBalance(uiState, it) } + + startLoadingQuotesFromLastState(isSilent = true) } .flowOn(dispatchers.main) .launchIn(viewModelScope) From 3f6ca7f9e814252a080bca0fa57033ff4ad7774e Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 19 Mar 2024 21:25:42 +0800 Subject: [PATCH 36/38] Updated on 2026-08-14 --- .../wallet/viewmodels/WalletViewModel.kt | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt index dab3f9be92..65ca30c4ff 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt @@ -177,14 +177,8 @@ internal class WalletViewModel @Inject constructor( is WalletsUpdateActionResolver.Action.UpdateWalletName -> { stateHolder.update(transformer = RenameWalletTransformer(action.selectedWalletId, action.name)) } - is WalletsUpdateActionResolver.Action.NoAccessibleWallets -> { - stateHolder.clear() - router.popBackStack(screen = AppScreen.Welcome) - } - is WalletsUpdateActionResolver.Action.NoWallets -> { - stateHolder.clear() - router.popBackStack(screen = AppScreen.Home) - } + is WalletsUpdateActionResolver.Action.NoAccessibleWallets -> closeScreen(screen = AppScreen.Welcome) + is WalletsUpdateActionResolver.Action.NoWallets -> closeScreen(screen = AppScreen.Home) is WalletsUpdateActionResolver.Action.Unknown -> Unit } } @@ -295,6 +289,13 @@ internal class WalletViewModel @Inject constructor( ) } + private fun closeScreen(screen: AppScreen) { + if (!screenLifecycleProvider.isBackground) { + stateHolder.clear() + router.popBackStack(screen = screen) + } + } + private fun scrollToWallet(index: Int, onConsume: () -> Unit = {}) { stateHolder.update( ScrollToWalletTransformer( From cb9612ec4ec00cb5e1f602801a34a654af1709b8 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 19 Mar 2024 18:35:59 +0300 Subject: [PATCH 37/38] Updated on 2026-08-14 --- .../BiometricUserWalletsListManager.kt | 1 + .../saveWallet/redux/SaveWalletAction.kt | 18 +++++++++--------- .../saveWallet/redux/SaveWalletMiddleware.kt | 5 +++++ 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt index daf1ce408b..160d433012 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt @@ -277,6 +277,7 @@ internal class BiometricUserWalletsListManager( prevState.copy( userWallets = newUserWallets, + encryptionKeys = encryptionKeys, isLocked = newUserWallets.any(UserWallet::isLocked), ) } else { diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletAction.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletAction.kt index 934f7e8880..e5625ad1d2 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletAction.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletAction.kt @@ -11,20 +11,20 @@ internal sealed interface SaveWalletAction : Action { val backupCardsIds: Set?, ) : SaveWalletAction - object Save : SaveWalletAction { - object Success : SaveWalletAction + data object Save : SaveWalletAction { + data object Success : SaveWalletAction data class Error(val error: TangemError) : SaveWalletAction } - object AllowToUseBiometrics : SaveWalletAction + data object AllowToUseBiometrics : SaveWalletAction - object Dismiss : SaveWalletAction + data object Dismiss : SaveWalletAction - object CloseError : SaveWalletAction - object EnrollBiometrics : SaveWalletAction { - object Enroll : SaveWalletAction - object Cancel : SaveWalletAction + data object CloseError : SaveWalletAction + data object EnrollBiometrics : SaveWalletAction { + data object Enroll : SaveWalletAction + data object Cancel : SaveWalletAction } - object SaveWalletWasShown : SaveWalletAction + data object SaveWalletWasShown : SaveWalletAction } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt index ddf1c22d74..344a0909de 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt @@ -147,6 +147,11 @@ internal class SaveWalletMiddleware { } private fun allowToUseBiometrics(state: SaveWalletState) { + if (tangemSdkManager.needEnrollBiometrics) { + store.dispatchOnMain(SaveWalletAction.EnrollBiometrics) + return + } + val scanResponse = state.backupInfo?.scanResponse ?: store.state.globalState.scanResponse ?: return From d8368a08f0741033043db729b0465f0bb0155476 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 19 Mar 2024 16:24:15 +0000 Subject: [PATCH 38/38] Updated on 2026-08-14 --- .../models/SupportBlockchainType.kt | 6 ++++ .../presentation/routers/CustomTokenRouter.kt | 2 ++ .../routers/DefaultCustomTokenRouter.kt | 9 ++++++ .../viewmodels/AddCustomTokenViewModel.kt | 26 ++++++++++++---- .../router/DefaultTokensListRouter.kt | 9 ++++++ .../presentation/router/TokensListRouter.kt | 2 ++ .../viewmodels/TokensListViewModel.kt | 30 +++++++++++++------ 7 files changed, 69 insertions(+), 15 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/models/SupportBlockchainType.kt diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/models/SupportBlockchainType.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/models/SupportBlockchainType.kt new file mode 100644 index 0000000000..27215046bd --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/models/SupportBlockchainType.kt @@ -0,0 +1,6 @@ +package com.tangem.tap.features.customtoken.impl.presentation.models + +internal enum class SupportBlockchainType { + + SUPPORTED, UNSUPPORTED, UNABLE_TO_DETERMINE +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/routers/CustomTokenRouter.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/routers/CustomTokenRouter.kt index 4444aeea64..1c016c4993 100644 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/routers/CustomTokenRouter.kt +++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/routers/CustomTokenRouter.kt @@ -20,4 +20,6 @@ internal interface CustomTokenRouter { * @param blockchain blockchain to show alert */ fun openUnsupportedNetworkAlert(blockchain: Blockchain) + + fun showGenericErrorAlertAndPopBack() } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/routers/DefaultCustomTokenRouter.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/routers/DefaultCustomTokenRouter.kt index af1ebb8795..6391cc840f 100644 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/routers/DefaultCustomTokenRouter.kt +++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/routers/DefaultCustomTokenRouter.kt @@ -27,4 +27,13 @@ internal class DefaultCustomTokenRouter : CustomTokenRouter { ) store.dispatchDialogShow(alert) } + + override fun showGenericErrorAlertAndPopBack() { + val alert = AppDialog.SimpleOkDialogRes( + headerId = R.string.common_error, + messageId = R.string.common_unknown_error, + onOk = { store.dispatch(NavigationAction.PopBackTo()) }, + ) + store.dispatchDialogShow(alert) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt index b15a5fdee5..9fdc0581da 100644 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt @@ -697,14 +697,19 @@ internal class AddCustomTokenViewModel @Inject constructor( return derivationNetwork.derivationPath(derivationStyle) } - private fun isUnsupportedBlockchain(blockchain: Blockchain): Boolean { + private fun getSupportBlockchainType(blockchain: Blockchain): SupportBlockchainType { return getSelectedWalletSyncUseCase().fold( - ifLeft = { false }, + ifLeft = { SupportBlockchainType.UNABLE_TO_DETERMINE }, ifRight = { - !it.scanResponse.card.canHandleBlockchain( + val canHandleBlockchain = it.scanResponse.card.canHandleBlockchain( blockchain = blockchain, cardTypesResolver = it.scanResponse.cardTypesResolver, ) + if (canHandleBlockchain) { + SupportBlockchainType.SUPPORTED + } else { + SupportBlockchainType.UNSUPPORTED + } }, ) } @@ -824,9 +829,18 @@ internal class AddCustomTokenViewModel @Inject constructor( fun onAddCustomTokenClick() { if (!isNetworkSelected()) return val blockchain = uiState.form.networkSelectorField.selectedItem.blockchain - if (isUnsupportedBlockchain(blockchain)) { - featureRouter.openUnsupportedNetworkAlert(blockchain) - return + when (getSupportBlockchainType(blockchain)) { + SupportBlockchainType.SUPPORTED -> { + /* no-op */ + } + SupportBlockchainType.UNSUPPORTED -> { + featureRouter.openUnsupportedNetworkAlert(blockchain) + return + } + SupportBlockchainType.UNABLE_TO_DETERMINE -> { + featureRouter.showGenericErrorAlertAndPopBack() + return + } } val currency = when (getCustomTokenType()) { diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/router/DefaultTokensListRouter.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/router/DefaultTokensListRouter.kt index 15f39a8914..27418f3d01 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/router/DefaultTokensListRouter.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/router/DefaultTokensListRouter.kt @@ -50,6 +50,15 @@ internal class DefaultTokensListRouter : TokensListRouter { store.dispatchDialogShow(alert) } + override fun showGenericErrorAlertAndPopBack() { + val alert = AppDialog.SimpleOkDialogRes( + headerId = R.string.common_error, + messageId = R.string.common_unknown_error, + onOk = { store.dispatch(NavigationAction.PopBackTo()) }, + ) + store.dispatchDialogShow(alert) + } + override fun openNetworkTokensNotSupportAlert(networkName: String) { store.dispatchDialogShow( AppDialog.SimpleOkDialogRes( diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/router/TokensListRouter.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/router/TokensListRouter.kt index 8347f59a2e..34b5379b48 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/router/TokensListRouter.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/router/TokensListRouter.kt @@ -36,6 +36,8 @@ internal interface TokensListRouter { */ fun openUnsupportedNetworkAlert(blockchain: Blockchain) + fun showGenericErrorAlertAndPopBack() + /** * Open alert with unsupported networks tokens error */ diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListViewModel.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListViewModel.kt index 2464c82a17..0fa1976240 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListViewModel.kt @@ -29,6 +29,7 @@ import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase import com.tangem.tap.common.extensions.dispatchWithMain import com.tangem.tap.common.extensions.fullNameWithoutTestnet import com.tangem.tap.common.extensions.getNetworkName +import com.tangem.tap.features.customtoken.impl.presentation.models.SupportBlockchainType import com.tangem.tap.features.tokens.impl.domain.TokensListInteractor import com.tangem.tap.features.tokens.impl.domain.models.Token import com.tangem.tap.features.tokens.impl.domain.models.Token.Network @@ -380,12 +381,18 @@ internal class TokensListViewModel @Inject constructor( toggledNetwork.changeToggleState() } } else { - if (isUnsupportedBlockchain(blockchain)) { - router.openUnsupportedNetworkAlert(blockchain) - } else { - analyticsSender.sendWhenBlockchainAdded(blockchain) - changedBlockchainList.add(blockchain) - toggledNetwork.changeToggleState() + when (getSupportBlockchainType(blockchain)) { + SupportBlockchainType.SUPPORTED -> { + analyticsSender.sendWhenBlockchainAdded(blockchain) + changedBlockchainList.add(blockchain) + toggledNetwork.changeToggleState() + } + SupportBlockchainType.UNSUPPORTED -> { + router.openUnsupportedNetworkAlert(blockchain) + } + SupportBlockchainType.UNABLE_TO_DETERMINE -> { + router.showGenericErrorAlertAndPopBack() + } } } } @@ -469,14 +476,19 @@ internal class TokensListViewModel @Inject constructor( ) } - private fun isUnsupportedBlockchain(blockchain: Blockchain): Boolean { + private fun getSupportBlockchainType(blockchain: Blockchain): SupportBlockchainType { return getSelectedWalletSyncUseCase().fold( - ifLeft = { false }, + ifLeft = { SupportBlockchainType.UNABLE_TO_DETERMINE }, ifRight = { - !it.scanResponse.card.canHandleBlockchain( + val canHandleBlockchain = it.scanResponse.card.canHandleBlockchain( blockchain = blockchain, cardTypesResolver = it.scanResponse.cardTypesResolver, ) + if (canHandleBlockchain) { + SupportBlockchainType.SUPPORTED + } else { + SupportBlockchainType.UNSUPPORTED + } }, ) }