Updated on 2026-08-14
This commit is contained in:
commit
036e031c1f
1212 changed files with 28739 additions and 8811 deletions
|
|
@ -80,6 +80,7 @@ configurations.androidTestImplementation {
|
|||
dependencies {
|
||||
implementation(projects.domain.legacy)
|
||||
implementation(projects.libs.blockchainSdk)
|
||||
implementation(projects.domain.account)
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.core)
|
||||
implementation(projects.domain.card)
|
||||
|
|
@ -144,6 +145,7 @@ dependencies {
|
|||
implementation(projects.libs.blockchainSdk)
|
||||
implementation(projects.libs.tangemSdkApi)
|
||||
|
||||
implementation(projects.data.account)
|
||||
implementation(projects.data.appCurrency)
|
||||
implementation(projects.data.appTheme)
|
||||
implementation(projects.data.balanceHiding)
|
||||
|
|
@ -226,13 +228,23 @@ dependencies {
|
|||
implementation(projects.features.usedesk.impl)
|
||||
implementation(projects.features.hotWallet.api)
|
||||
implementation(projects.features.hotWallet.impl)
|
||||
implementation(projects.features.kyc.api)
|
||||
//TODO disable for release because of the permissions
|
||||
// implementation(projects.features.kyc.api)
|
||||
// implementation(projects.features.kyc.impl)
|
||||
implementation(projects.features.welcome.api)
|
||||
implementation(projects.features.welcome.impl)
|
||||
implementation(projects.features.createWalletSelection.api)
|
||||
implementation(projects.features.createWalletSelection.impl)
|
||||
implementation(projects.features.home.api)
|
||||
implementation(projects.features.home.impl)
|
||||
implementation(projects.features.account.api)
|
||||
implementation(projects.features.account.impl)
|
||||
implementation(projects.features.tangempay.details.api)
|
||||
implementation(projects.features.tangempay.details.impl)
|
||||
implementation(projects.features.tangempay.main.api)
|
||||
implementation(projects.features.tangempay.main.impl)
|
||||
implementation(projects.features.tokenRecieve.api)
|
||||
implementation(projects.features.tokenRecieve.impl)
|
||||
|
||||
/** AndroidX libraries */
|
||||
implementation(deps.androidx.core.ktx)
|
||||
|
|
@ -255,13 +267,11 @@ dependencies {
|
|||
|
||||
/** Compose libraries */
|
||||
implementation(deps.compose.constraintLayout)
|
||||
implementation(deps.compose.material)
|
||||
implementation(deps.compose.material3)
|
||||
implementation(deps.compose.animation)
|
||||
implementation(deps.compose.coil)
|
||||
implementation(deps.compose.constraintLayout)
|
||||
implementation(deps.compose.foundation)
|
||||
implementation(deps.compose.material)
|
||||
implementation(deps.compose.navigation.hilt)
|
||||
implementation(deps.compose.shimmer)
|
||||
implementation(deps.compose.ui)
|
||||
|
|
@ -346,7 +356,7 @@ dependencies {
|
|||
|
||||
/** Chucker */
|
||||
debugImplementation(deps.chucker)
|
||||
mockedImplementation(deps.chuckerStub)
|
||||
mockedImplementation(deps.chucker)
|
||||
externalImplementation(deps.chuckerStub)
|
||||
internalImplementation(deps.chuckerStub)
|
||||
releaseImplementation(deps.chuckerStub)
|
||||
|
|
|
|||
|
|
@ -15,8 +15,11 @@ import com.kaspersky.kaspresso.testcases.api.testcase.TestCase
|
|||
import com.tangem.common.allure.FailedStepScreenshotInterceptor
|
||||
import com.tangem.common.rules.ApiEnvironmentRule
|
||||
import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.tap.MainActivity
|
||||
import dagger.hilt.android.testing.HiltAndroidRule
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Rule
|
||||
import org.junit.rules.RuleChain
|
||||
import org.junit.rules.TestRule
|
||||
|
|
@ -40,6 +43,9 @@ abstract class BaseTestCase : TestCase(
|
|||
@Inject
|
||||
lateinit var apiConfigsManager: ApiConfigsManager
|
||||
|
||||
@Inject
|
||||
lateinit var appPreferencesStore: AppPreferencesStore
|
||||
|
||||
private val hiltRule = HiltAndroidRule(this)
|
||||
private val apiEnvironmentRule = ApiEnvironmentRule()
|
||||
private val permissionRule = GrantPermissionRule.grant(
|
||||
|
|
@ -73,6 +79,14 @@ abstract class BaseTestCase : TestCase(
|
|||
additionalAfterSection: () -> Unit = {},
|
||||
) = before {
|
||||
hiltRule.inject()
|
||||
runBlocking {
|
||||
appPreferencesStore.editData { mutablePreferences ->
|
||||
mutablePreferences.set(
|
||||
key = PreferencesKeys.NOTIFICATIONS_USER_ALLOW_SEND_ADDRESSES_KEY,
|
||||
value = false
|
||||
)
|
||||
}
|
||||
}
|
||||
apiEnvironmentRule.setup(apiConfigsManager)
|
||||
ActivityScenario.launch(MainActivity::class.java)
|
||||
Intents.init()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
package com.tangem.common.constants
|
||||
|
||||
object TestConstants {
|
||||
const val TOTAL_BALANCE = "$3,299.18"
|
||||
|
||||
const val WAIT_UNTIL_TIMEOUT = 20_000L
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.common.extensions
|
||||
|
||||
import com.tangem.common.BaseTestCase
|
||||
|
||||
fun BaseTestCase.swipeUp(
|
||||
startHeightRatio: Float = 0.8f,
|
||||
endHeightRatio: Float = 0.03f,
|
||||
steps: Int = 15
|
||||
) {
|
||||
device.uiDevice.swipe(
|
||||
device.uiDevice.displayWidth / 2,
|
||||
(device.uiDevice.displayHeight * startHeightRatio).toInt(),
|
||||
device.uiDevice.displayWidth / 2,
|
||||
(device.uiDevice.displayHeight * endHeightRatio).toInt(),
|
||||
steps
|
||||
)
|
||||
}
|
||||
|
|
@ -127,6 +127,7 @@ class ApiEnvironmentRule : TestRule {
|
|||
ApiConfig.ID.TangemTech,
|
||||
ApiConfig.ID.Express,
|
||||
ApiConfig.ID.TangemPay,
|
||||
ApiConfig.ID.StakeKit,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
package com.tangem.common.utils
|
||||
|
||||
import okhttp3.*
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import timber.log.Timber
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* Method uses to set WireMock scenario state
|
||||
* @param scenarioName Name of the scenario to modify
|
||||
* @param state The target state to set (must be one of the scenario's possibleStates)
|
||||
* @param baseUrl WireMock base URL
|
||||
* @return true if state was set successfully, false otherwise
|
||||
*/
|
||||
fun setWireMockScenarioState(
|
||||
scenarioName: String,
|
||||
state: String,
|
||||
baseUrl: String = "[REDACTED_ENV_URL]"
|
||||
): Boolean {
|
||||
Timber.i("=== WireMock Scenario Set ===")
|
||||
Timber.i("Setting scenario '$scenarioName' to state: $state")
|
||||
val client = OkHttpClient()
|
||||
val json = """{"state": "$state"}"""
|
||||
val mediaType = "application/json".toMediaType()
|
||||
|
||||
val request = Request.Builder()
|
||||
.url("$baseUrl/__admin/scenarios/$scenarioName/state")
|
||||
.put(json.toRequestBody(mediaType))
|
||||
.build()
|
||||
|
||||
return try {
|
||||
client.newCall(request).execute().use { response ->
|
||||
val body = response.body?.string() ?: ""
|
||||
Timber.d("WireMock scenario request URL: ${request.url}")
|
||||
Timber.d("WireMock scenario request body: $json")
|
||||
Timber.d("WireMock scenario response: ${response.code} - ${response.message}")
|
||||
Timber.d("WireMock scenario response body: $body")
|
||||
response.isSuccessful
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Timber.e(e, "WireMock scenario error")
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method checks accessibility of WireMock
|
||||
*/
|
||||
fun checkWireMockStatus(baseUrl: String = "[REDACTED_ENV_URL]"): Boolean {
|
||||
val client = OkHttpClient()
|
||||
val request = Request.Builder()
|
||||
.url("$baseUrl/__admin/scenarios")
|
||||
.get()
|
||||
.build()
|
||||
|
||||
return try {
|
||||
client.newCall(request).execute().use { response ->
|
||||
val body = response.body?.string() ?: ""
|
||||
Timber.d("WireMock status check: ${response.code}")
|
||||
Timber.d("Available scenarios: $body")
|
||||
response.isSuccessful
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Timber.e(e, "WireMock not accessible")
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to reset all WireMock scenarios
|
||||
*/
|
||||
fun resetWireMockScenarios(baseUrl: String = "[REDACTED_ENV_URL]"): Boolean {
|
||||
Timber.i("=== WireMock Scenarios Reset ===")
|
||||
Timber.i("Base URL: $baseUrl")
|
||||
|
||||
val client = OkHttpClient()
|
||||
val url = "$baseUrl/__admin/scenarios/reset"
|
||||
Timber.i("Request URL: $url")
|
||||
|
||||
val request = Request.Builder()
|
||||
.url(url)
|
||||
.post("".toRequestBody())
|
||||
.build()
|
||||
|
||||
return try {
|
||||
Timber.d("Sending reset request...")
|
||||
client.newCall(request).execute().use { response ->
|
||||
Timber.d("Response code: ${response.code}")
|
||||
Timber.d("Response message: ${response.message}")
|
||||
val responseBody = response.body?.string() ?: ""
|
||||
Timber.d("Response body: $responseBody")
|
||||
|
||||
val isSuccessful = response.isSuccessful
|
||||
Timber.d("Is successful: $isSuccessful")
|
||||
isSuccessful
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Timber.e(e, "Exception during reset")
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to reset a specific WireMock scenario to its initial state
|
||||
* @param scenarioName Name of the scenario to reset
|
||||
* @param initialState The target state to reset the scenario to (must be one of the scenario's possibleStates)
|
||||
* @param baseUrl WireMock base URL
|
||||
* @return true if reset was successful, false otherwise
|
||||
*/
|
||||
fun resetWireMockScenarioState(
|
||||
scenarioName: String,
|
||||
initialState: String = "Started",
|
||||
baseUrl: String = "[REDACTED_ENV_URL]"
|
||||
): Boolean {
|
||||
Timber.i("=== WireMock Scenario Reset ===")
|
||||
Timber.i("Resetting scenario '$scenarioName' to initial state: $initialState")
|
||||
return setWireMockScenarioState(scenarioName, initialState, baseUrl)
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
package com.tangem.screens
|
||||
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.test.BaseButtonTestTags
|
||||
import com.tangem.core.ui.test.BuyTokenDetailsScreenTestTags
|
||||
import com.tangem.core.ui.test.NotificationTestTags
|
||||
import com.tangem.core.ui.test.TopAppBarTestTags
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.KNode
|
||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||
import androidx.compose.ui.test.hasTestTag as withTestTag
|
||||
|
||||
class BuyTokenDetailsPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<BuyTokenDetailsPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val topBarTitle: KNode = child {
|
||||
hasTestTag(TopAppBarTestTags.TITLE)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val topBarMoreButton: KNode = child {
|
||||
hasTestTag(TopAppBarTestTags.MORE_BUTTON)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val topBarCloseButton: KNode = child {
|
||||
hasTestTag(TopAppBarTestTags.CLOSE_BUTTON)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val errorNotificationTitle: KNode = child {
|
||||
hasTestTag(NotificationTestTags.TITLE)
|
||||
hasText(getResourceString(R.string.common_error))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val errorNotificationText: KNode = child {
|
||||
hasTestTag(NotificationTestTags.TEXT)
|
||||
hasText(getResourceString(R.string.common_unknown_error))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val refreshButton: KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.BUTTON)
|
||||
hasText(getResourceString(R.string.warning_button_refresh))
|
||||
}
|
||||
|
||||
val fiatCurrencyIcon: KNode = child {
|
||||
hasTestTag(BuyTokenDetailsScreenTestTags.FIAT_CURRENCY_ICON)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val expandFiatListButton: KNode = child {
|
||||
hasTestTag(BuyTokenDetailsScreenTestTags.EXPAND_FIAT_LIST_BUTTON)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val fiatAmountTextField: KNode = child {
|
||||
hasParent(withTestTag(BuyTokenDetailsScreenTestTags.FIAT_AMOUNT_TEXT_FIELD))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val tokenAmountField: KNode = child {
|
||||
hasParent(withTestTag(BuyTokenDetailsScreenTestTags.TOKEN_AMOUNT))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val providerLoadingTitle: KNode = child {
|
||||
hasTestTag(BuyTokenDetailsScreenTestTags.PROVIDER_LOADING_TITLE)
|
||||
}
|
||||
|
||||
val providerLoadingText: KNode = child {
|
||||
hasTestTag(BuyTokenDetailsScreenTestTags.PROVIDER_LOADING_TEXT)
|
||||
}
|
||||
|
||||
val providerTitle: KNode = child {
|
||||
hasTestTag(BuyTokenDetailsScreenTestTags.PROVIDER_TITLE)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val providerText: KNode = child {
|
||||
hasTestTag(BuyTokenDetailsScreenTestTags.PROVIDER_TEXT)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val buyButton: KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.BUTTON)
|
||||
hasText(getResourceString(R.string.common_buy))
|
||||
}
|
||||
|
||||
val toSBlock: KNode = child {
|
||||
hasTestTag(BuyTokenDetailsScreenTestTags.TOS_BLOCK)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onBuyTokenDetailsScreen(function: BuyTokenDetailsPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
package com.tangem.screens
|
||||
|
||||
import androidx.compose.ui.test.SemanticsMatcher
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.utils.LazyListItemNode
|
||||
import com.tangem.core.ui.test.BuyTokenFiatListTestTags
|
||||
import com.tangem.core.ui.utils.LazyListItemPositionSemantics
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.KNode
|
||||
import io.github.kakaocup.compose.node.element.lazylist.KLazyListNode
|
||||
|
||||
class BuyTokenFiatListPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<BuyTokenFiatListPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
|
||||
private val lazyList = KLazyListNode(
|
||||
semanticsProvider = semanticsProvider,
|
||||
viewBuilderAction = { hasTestTag(BuyTokenFiatListTestTags.LAZY_LIST) },
|
||||
itemTypeBuilder = { itemType(::LazyListItemNode) },
|
||||
positionMatcher = { position ->
|
||||
SemanticsMatcher.expectValue(
|
||||
LazyListItemPositionSemantics,
|
||||
position
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
fun fiatListItemWithTitle(title: String): KNode {
|
||||
return lazyList.child<KNode> {
|
||||
hasText(title)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onBuyTokenFiatListBottomSheet(function: BuyTokenFiatListPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
package com.tangem.screens
|
||||
|
||||
import androidx.compose.ui.test.ExperimentalTestApi
|
||||
import androidx.compose.ui.test.SemanticsMatcher
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.utils.LazyListItemNode
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.test.BuyTokenScreenTestTags
|
||||
import com.tangem.core.ui.test.TokenElementsTestTags
|
||||
import com.tangem.core.ui.test.TopAppBarTestTags
|
||||
import com.tangem.core.ui.utils.LazyListItemPositionSemantics
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.KNode
|
||||
import io.github.kakaocup.compose.node.element.lazylist.KLazyListNode
|
||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||
|
||||
class BuyTokenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<BuyTokenPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val topAppBarTitle: KNode = child {
|
||||
hasTestTag(TopAppBarTestTags.TITLE)
|
||||
hasText(getResourceString(R.string.common_buy))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
private val lazyList = KLazyListNode(
|
||||
semanticsProvider = semanticsProvider,
|
||||
viewBuilderAction = { hasTestTag(BuyTokenScreenTestTags.LAZY_LIST) },
|
||||
itemTypeBuilder = { itemType(::LazyListItemNode) },
|
||||
positionMatcher = { position ->
|
||||
SemanticsMatcher.expectValue(
|
||||
LazyListItemPositionSemantics,
|
||||
position
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalTestApi::class)
|
||||
fun tokenWithTitleAndFiatAmount(tokenTitle: String): KNode {
|
||||
return lazyList.childWith<LazyListItemNode> {
|
||||
hasTestTag(BuyTokenScreenTestTags.LAZY_LIST_ITEM)
|
||||
hasText(tokenTitle)
|
||||
useUnmergedTree = true
|
||||
}.child<KNode> {
|
||||
hasTestTag(TokenElementsTestTags.TOKEN_FIAT_AMOUNT)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onBuyTokenScreen(function: BuyTokenPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -3,6 +3,7 @@ package com.tangem.screens
|
|||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.test.BaseButtonTestTags
|
||||
import com.tangem.core.ui.test.DialogTestTags
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
||||
|
|
@ -17,14 +18,19 @@ class DialogPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
|||
}
|
||||
|
||||
val cancelButton: KNode = child {
|
||||
hasTestTag(DialogTestTags.BUTTON)
|
||||
hasTestTag(BaseButtonTestTags.BUTTON)
|
||||
hasText(getResourceString(R.string.common_cancel))
|
||||
}
|
||||
|
||||
val hideButton: KNode = child {
|
||||
hasTestTag(DialogTestTags.BUTTON)
|
||||
hasTestTag(BaseButtonTestTags.BUTTON)
|
||||
hasText(getResourceString(R.string.token_details_hide_alert_hide))
|
||||
}
|
||||
|
||||
val confirmButton: KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.BUTTON)
|
||||
hasText(getResourceString(R.string.common_confirm))
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onDialog(function: DialogPageObject.() -> Unit) =
|
||||
|
|
|
|||
|
|
@ -3,9 +3,12 @@ package com.tangem.screens
|
|||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.core.ui.test.DisclaimerScreenTestTags
|
||||
import com.tangem.core.ui.test.TopAppBarTestTags
|
||||
import com.tangem.features.disclaimer.impl.R
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.KNode
|
||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||
|
||||
class DisclaimerPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<DisclaimerPageObject>(
|
||||
|
|
@ -13,6 +16,15 @@ class DisclaimerPageObject(semanticsProvider: SemanticsNodeInteractionsProvider)
|
|||
viewBuilderAction = { hasTestTag(DisclaimerScreenTestTags.SCREEN_CONTAINER) }
|
||||
) {
|
||||
|
||||
val title: KNode = child {
|
||||
hasTestTag(TopAppBarTestTags.TITLE)
|
||||
hasText(getResourceString(R.string.disclaimer_title))
|
||||
}
|
||||
|
||||
val webView: KNode = child {
|
||||
hasTestTag(DisclaimerScreenTestTags.WEB_VIEW)
|
||||
}
|
||||
|
||||
val acceptButton: KNode = child {
|
||||
hasTestTag(DisclaimerScreenTestTags.ACCEPT_BUTTON)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,6 +39,11 @@ class MainScreenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider)
|
|||
hasText(getResourceString(R.string.common_generate_addresses))
|
||||
}
|
||||
|
||||
val buyButton: KNode = child {
|
||||
hasTestTag(MainScreenTestTags.MULTI_CURRENCY_ACTION_BUTTON)
|
||||
hasText(getResourceString(R.string.common_buy))
|
||||
}
|
||||
|
||||
/**
|
||||
* Find token list item with title and address
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -0,0 +1,84 @@
|
|||
package com.tangem.screens
|
||||
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.core.ui.test.BaseButtonTestTags
|
||||
import com.tangem.core.ui.test.ReferralProgramScreenTestTags
|
||||
import com.tangem.core.ui.test.TopAppBarTestTags
|
||||
import com.tangem.wallet.R
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.KNode
|
||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||
import com.tangem.feature.referral.presentation.R as ReferralPresentationR
|
||||
import androidx.compose.ui.test.hasText as withText
|
||||
|
||||
class ReferralProgramPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<ReferralProgramPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val title: KNode = child {
|
||||
hasTestTag(TopAppBarTestTags.TITLE)
|
||||
hasText(getResourceString(R.string.details_referral_title))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val referTitle: KNode = child {
|
||||
hasText(getResourceString(R.string.referral_title))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val image: KNode = child {
|
||||
hasTestTag(ReferralProgramScreenTestTags.IMAGE)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val infoForYouText: KNode = child {
|
||||
hasTestTag(ReferralProgramScreenTestTags.INFO_FOR_YOU_TEXT)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val infoForYouBlock: KNode = child {
|
||||
hasTestTag(ReferralProgramScreenTestTags.CONDITION_BLOCK)
|
||||
hasAnyDescendant(
|
||||
withText(
|
||||
getResourceString(ReferralPresentationR.string.referral_point_currencies_title),
|
||||
substring = true
|
||||
)
|
||||
)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val infoForYourFriendText: KNode = child {
|
||||
hasTestTag(ReferralProgramScreenTestTags.INFO_FOR_YOUR_FRIEND_TEXT)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val infoForYourFriendBlock: KNode = child {
|
||||
hasTestTag(ReferralProgramScreenTestTags.CONDITION_BLOCK)
|
||||
hasAnyDescendant(
|
||||
withText(
|
||||
getResourceString(ReferralPresentationR.string.referral_point_discount_title),
|
||||
substring = true
|
||||
)
|
||||
)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val agreementText: KNode = child {
|
||||
hasText(getResourceString(
|
||||
ReferralPresentationR.string.referral_tos_not_enroled_prefix) + " " +
|
||||
getResourceString(ReferralPresentationR.string.common_terms_and_conditions ) + " " +
|
||||
getResourceString(ReferralPresentationR.string.referral_tos_suffix),
|
||||
substring = true
|
||||
)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val participateButton: KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.TEXT)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onReferralProgramScreen(function: ReferralProgramPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package com.tangem.screens
|
||||
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.test.ResidenceSettingsScreenTestTags
|
||||
import com.tangem.core.ui.test.TopAppBarTestTags
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.KNode
|
||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||
import com.tangem.features.onramp.impl.R as OnrampImplR
|
||||
|
||||
class ResidenceSettingsPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<ResidenceSettingsPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val topBarTitle: KNode = child {
|
||||
hasTestTag(TopAppBarTestTags.TITLE)
|
||||
hasText(getResourceString(R.string.onramp_settings_title))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val topBarCloseButton: KNode = child {
|
||||
hasTestTag(TopAppBarTestTags.CLOSE_BUTTON)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val residenceButton: KNode = child {
|
||||
hasText(getResourceString(OnrampImplR.string.onramp_settings_residence))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val countryName: KNode = child {
|
||||
hasTestTag(ResidenceSettingsScreenTestTags.COUNTRY_NAME)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val residenceSettingsDescription: KNode = child {
|
||||
hasText(getResourceString(OnrampImplR.string.onramp_settings_residence_description))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onResidenceSettingsScreen(function: ResidenceSettingsPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
package com.tangem.screens
|
||||
|
||||
import androidx.compose.ui.test.SemanticsMatcher
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.utils.LazyListItemNode
|
||||
import com.tangem.core.ui.test.SelectCountryBottomSheetTestTags
|
||||
import com.tangem.core.ui.utils.LazyListItemPositionSemantics
|
||||
import com.tangem.features.onramp.impl.R
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.KNode
|
||||
import io.github.kakaocup.compose.node.element.lazylist.KLazyListNode
|
||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||
import androidx.compose.ui.test.hasText as withText
|
||||
import androidx.compose.ui.test.hasTestTag as withTestTag
|
||||
|
||||
|
||||
class SelectCountryPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<SelectCountryPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
|
||||
private val lazyList = KLazyListNode(
|
||||
semanticsProvider = semanticsProvider,
|
||||
viewBuilderAction = { hasTestTag(SelectCountryBottomSheetTestTags.LAZY_LIST) },
|
||||
itemTypeBuilder = { itemType(::LazyListItemNode) },
|
||||
positionMatcher = { position ->
|
||||
SemanticsMatcher.expectValue(
|
||||
LazyListItemPositionSemantics,
|
||||
position
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
val searchBar: KNode = child {
|
||||
hasTestTag(SelectCountryBottomSheetTestTags.SEARCH_BAR)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
fun countryWithNameAndIcon(name: String): KNode {
|
||||
return lazyList.child<KNode> {
|
||||
hasText(name)
|
||||
hasAnySibling(withTestTag(SelectCountryBottomSheetTestTags.COUNTRY_ICON))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
}
|
||||
|
||||
fun unavailableCountryWithNameAndIcon(name: String): KNode {
|
||||
return lazyList.child<KNode> {
|
||||
hasText(name)
|
||||
hasAnySibling(withText(getResourceString(R.string.onramp_country_unavailable)))
|
||||
hasAnySibling(withTestTag(SelectCountryBottomSheetTestTags.UNAVAILABLE_COUNTRY_ICON))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onSelectCountryBottomSheet(function: SelectCountryPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
package com.tangem.screens
|
||||
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.core.ui.test.SelectNetworkFeeBottomSheetTestTags
|
||||
import com.tangem.core.ui.test.TopAppBarTestTags
|
||||
import com.tangem.wallet.R
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.KNode
|
||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||
import androidx.compose.ui.test.hasText as withText
|
||||
|
||||
class SelectNetworkFeePageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<SelectNetworkFeePageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val title: KNode = child {
|
||||
hasTestTag(TopAppBarTestTags.TITLE)
|
||||
hasText(getResourceString(R.string.common_fee_selector_title))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val marketSelectorItem: KNode = child {
|
||||
hasTestTag(SelectNetworkFeeBottomSheetTestTags.SELECTOR_ITEM)
|
||||
hasAnyChild(withText(getResourceString(R.string.common_fee_selector_option_market)))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val fastSelectorItem: KNode = child {
|
||||
hasTestTag(SelectNetworkFeeBottomSheetTestTags.SELECTOR_ITEM)
|
||||
hasAnyChild(withText(getResourceString(R.string.common_fee_selector_option_fast)))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val readMoreTextBlock: KNode = child {
|
||||
hasTestTag(SelectNetworkFeeBottomSheetTestTags.READ_MORE_TEXT)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onSelectNetworkFeeBottomSheet(function: SelectNetworkFeePageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
package com.tangem.screens
|
||||
|
||||
import androidx.compose.ui.test.SemanticsMatcher
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.utils.LazyListItemNode
|
||||
import com.tangem.core.ui.test.SelectPaymentMethodBottomSheetTestTags
|
||||
import com.tangem.core.ui.test.TopAppBarTestTags
|
||||
import com.tangem.core.ui.utils.LazyListItemPositionSemantics
|
||||
import com.tangem.features.onramp.impl.R
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.KNode
|
||||
import io.github.kakaocup.compose.node.element.lazylist.KLazyListNode
|
||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||
import androidx.compose.ui.test.hasTestTag as withTestTag
|
||||
import androidx.compose.ui.test.hasText as withText
|
||||
|
||||
class SelectPaymentMethodPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<SelectPaymentMethodPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
|
||||
private val lazyList = KLazyListNode(
|
||||
semanticsProvider = semanticsProvider,
|
||||
viewBuilderAction = { hasTestTag(SelectPaymentMethodBottomSheetTestTags.LAZY_LIST) },
|
||||
itemTypeBuilder = { itemType(::LazyListItemNode) },
|
||||
positionMatcher = { position ->
|
||||
SemanticsMatcher.expectValue(
|
||||
LazyListItemPositionSemantics,
|
||||
position
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
val title: KNode = child {
|
||||
hasTestTag(TopAppBarTestTags.TITLE)
|
||||
hasText(getResourceString(R.string.onramp_pay_with))
|
||||
}
|
||||
|
||||
fun paymentMethodWithNameAndIcon(name: String): KNode {
|
||||
return lazyList.child<KNode> {
|
||||
hasAnyDescendant(withText(name))
|
||||
hasAnyDescendant(withTestTag(SelectPaymentMethodBottomSheetTestTags.PAYMENT_METHOD_ICON))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onSelectPaymentMethodBottomSheet(function: SelectPaymentMethodPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
package com.tangem.screens
|
||||
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.core.ui.test.SelectProviderBottomSheetTestTags
|
||||
import com.tangem.features.onramp.impl.R
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.KNode
|
||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||
import androidx.compose.ui.test.hasText as withText
|
||||
|
||||
class SelectProviderPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<SelectProviderPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val title: KNode = child {
|
||||
hasText(getResourceString(R.string.onramp_choose_provider_title_hint))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val paymentMethodIcon: KNode = child {
|
||||
hasTestTag(SelectProviderBottomSheetTestTags.PAYMENT_METHOD_ICON)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val paymentMethodTitle: KNode = child {
|
||||
hasTestTag(SelectProviderBottomSheetTestTags.PAYMENT_METHOD_NAME)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val paymentMethodName: KNode = child {
|
||||
hasTestTag(SelectProviderBottomSheetTestTags.PAYMENT_METHOD_NAME)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val paymentMethodExpandButton: KNode = child {
|
||||
hasTestTag(SelectProviderBottomSheetTestTags.PAYMENT_METHOD_EXPAND_BUTTON)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val availableProviderItem: KNode = child {
|
||||
hasTestTag(SelectProviderBottomSheetTestTags.AVAILABLE_PROVIDER_ITEM)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val availableProviderName: KNode = child {
|
||||
hasTestTag(SelectProviderBottomSheetTestTags.AVAILABLE_PROVIDER_NAME)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val tokenAmount: KNode = child {
|
||||
hasTestTag(SelectProviderBottomSheetTestTags.TOKEN_AMOUNT)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val unavailableProviderItem: KNode = child {
|
||||
hasTestTag(SelectProviderBottomSheetTestTags.UNAVAILABLE_PROVIDER_ITEM)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val unavailableProviderName: KNode = child {
|
||||
hasTestTag(SelectProviderBottomSheetTestTags.UNAVAILABLE_PROVIDER_NAME)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val moreProvidersIcon: KNode = child {
|
||||
hasTestTag(SelectProviderBottomSheetTestTags.MORE_PROVIDERS_ICON)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val moreProvidersText: KNode = child {
|
||||
hasTestTag(SelectProviderBottomSheetTestTags.MORE_PROVIDERS_TEXT)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val bestRateLabel: KNode = child {
|
||||
hasTestTag(SelectProviderBottomSheetTestTags.BEST_RATE_LABEL)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
fun availableProviderWithName(name: String, tokenAmount: String, rate: String): KNode = child {
|
||||
hasTestTag(SelectProviderBottomSheetTestTags.AVAILABLE_PROVIDER_ITEM)
|
||||
hasAnyChild(withText(name))
|
||||
hasAnyChild(withText(tokenAmount))
|
||||
hasAnyChild(withText(rate))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onSelectProviderBottomSheet(function: SelectProviderPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
package com.tangem.screens
|
||||
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.core.ui.test.*
|
||||
import com.tangem.features.tokendetails.impl.R
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.KNode
|
||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||
import com.tangem.features.staking.impl.R as StakingImplR
|
||||
import androidx.compose.ui.test.hasTestTag as withTestTag
|
||||
|
||||
class StakingDetailsPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<StakingDetailsPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val screenContainer: KNode = child {
|
||||
hasTestTag(TokenDetailsScreenTestTags.SCREEN_CONTAINER)
|
||||
}
|
||||
|
||||
val stakingTitle: KNode = child {
|
||||
hasTestTag(TopAppBarTestTags.TITLE)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val bannerImage: KNode = child {
|
||||
hasTestTag(StakingDetailsScreenTestTags.BANNER_IMAGE)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val bannerText: KNode = child {
|
||||
hasTestTag(StakingDetailsScreenTestTags.BANNER_TEXT)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val annualPercentageRate: KNode = child {
|
||||
hasParent(withTestTag(StakingDetailsScreenTestTags.PARAMETER_BLOCK))
|
||||
hasTestTag(StakingDetailsScreenTestTags.PARAMETER_NAME)
|
||||
hasText(getResourceString(StakingImplR.string.staking_details_annual_percentage_rate))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val availableBlock: KNode = child {
|
||||
hasParent(withTestTag(StakingDetailsScreenTestTags.PARAMETER_BLOCK))
|
||||
hasTestTag(StakingDetailsScreenTestTags.PARAMETER_NAME)
|
||||
hasText(getResourceString(StakingImplR.string.staking_details_available))
|
||||
useUnmergedTree = true
|
||||
|
||||
}
|
||||
|
||||
val unbondingPeriodBlock: KNode = child {
|
||||
hasParent(withTestTag(StakingDetailsScreenTestTags.PARAMETER_BLOCK))
|
||||
hasTestTag(StakingDetailsScreenTestTags.PARAMETER_NAME)
|
||||
hasText(getResourceString(StakingImplR.string.staking_details_unbonding_period))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val rewardClaimingBlock: KNode = child {
|
||||
hasParent(withTestTag(StakingDetailsScreenTestTags.PARAMETER_BLOCK))
|
||||
hasTestTag(StakingDetailsScreenTestTags.PARAMETER_NAME)
|
||||
hasText(getResourceString(StakingImplR.string.staking_details_reward_claiming))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val rewardScheduleBlock: KNode = child {
|
||||
hasParent(withTestTag(StakingDetailsScreenTestTags.PARAMETER_BLOCK))
|
||||
hasTestTag(StakingDetailsScreenTestTags.PARAMETER_NAME)
|
||||
hasText(getResourceString(StakingImplR.string.staking_details_reward_schedule))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val rewardsBlock: KNode = child {
|
||||
hasTestTag(BaseBlockTestTags.BLOCK)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val rewardsBlockTitle: KNode = child {
|
||||
hasTestTag(BaseBlockTestTags.BLOCK_TITLE)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val rewardsBlockText: KNode = child {
|
||||
hasTestTag(BaseBlockTestTags.BLOCK_TEXT)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val yourStakesTitle: KNode = child {
|
||||
hasText(getResourceString(StakingImplR.string.staking_your_stakes))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val activeStakingBlock: KNode = child {
|
||||
hasTestTag(StakingDetailsScreenTestTags.ACTIVE_STAKING_BLOCK)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val toSText: KNode = child {
|
||||
hasTestTag(StakingDetailsScreenTestTags.TOS_TEXT)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val stakeMoreButton: KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.TEXT)
|
||||
hasText(getResourceString(R.string.staking_stake_more))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val stakeButton: KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.TEXT)
|
||||
hasText(getResourceString(R.string.common_stake))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onStakingDetailsScreen(function: StakingDetailsPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
package com.tangem.screens
|
||||
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.test.BaseButtonTestTags
|
||||
import com.tangem.core.ui.test.StakingSendDetailsScreenTestTags
|
||||
import com.tangem.core.ui.test.TopAppBarTestTags
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.KNode
|
||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||
|
||||
class StakingSendDetailsPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<StakingSendDetailsPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val title: KNode = child {
|
||||
hasTestTag(TopAppBarTestTags.TITLE)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val primaryAmount: KNode = child {
|
||||
hasTestTag(StakingSendDetailsScreenTestTags.PRIMARY_AMOUNT)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val secondaryAmount: KNode = child {
|
||||
hasTestTag(StakingSendDetailsScreenTestTags.SECONDARY_AMOUNT)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val validatorBlock: KNode = child {
|
||||
hasTestTag(StakingSendDetailsScreenTestTags.VALIDATOR_BLOCK)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val networkFeeBlock: KNode = child {
|
||||
hasTestTag(StakingSendDetailsScreenTestTags.NETWORK_FEE_BLOCK)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val stakeButton: KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.TEXT)
|
||||
hasText(getResourceString(R.string.common_stake))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onStakingSendDetailsScreen(function: StakingSendDetailsPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
package com.tangem.screens
|
||||
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.core.ui.test.BaseButtonTestTags
|
||||
import com.tangem.core.ui.test.StakingSendScreenTestTags
|
||||
import com.tangem.core.ui.test.TopAppBarTestTags
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.KNode
|
||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||
import com.tangem.features.send.v2.impl.R as SendR
|
||||
import androidx.compose.ui.test.hasTestTag as withTestTag
|
||||
|
||||
class StakingSendPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<StakingSendPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val screenContainer: KNode = child {
|
||||
hasTestTag(StakingSendScreenTestTags.SCREEN_CONTAINER)
|
||||
}
|
||||
|
||||
val title: KNode = child {
|
||||
hasTestTag(TopAppBarTestTags.TITLE)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val amountContainerTitle: KNode = child {
|
||||
hasTestTag(StakingSendScreenTestTags.AMOUNT_CONTAINER_TITLE)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val amountContainerText: KNode = child {
|
||||
hasTestTag(StakingSendScreenTestTags.AMOUNT_CONTAINER_TEXT)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val amountInputTextField: KNode = child {
|
||||
hasTestTag(StakingSendScreenTestTags.INPUT_TEXT_FIELD)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val secondaryAmount: KNode = child {
|
||||
hasTestTag(StakingSendScreenTestTags.SECONDARY_AMOUNT)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val currencyButton: KNode = child {
|
||||
hasTestTag(StakingSendScreenTestTags.CURRENCY_BUTTON)
|
||||
hasAnyChild(withTestTag(StakingSendScreenTestTags.CURRENCY_ICON))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val fiatButton: KNode = child {
|
||||
hasTestTag(StakingSendScreenTestTags.CURRENCY_BUTTON)
|
||||
hasAnyChild(withTestTag(StakingSendScreenTestTags.FIAT_ICON))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val maxButton: KNode = child {
|
||||
hasTestTag(StakingSendScreenTestTags.MAX_BUTTON)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val previousButton: KNode = child {
|
||||
hasTestTag(StakingSendScreenTestTags.PREVIOUS_BUTTON)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val nextButton: KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.TEXT)
|
||||
hasText(getResourceString(SendR.string.common_next))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onStakingSendScreen(function: StakingSendPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.screens
|
||||
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.core.ui.test.SwapStoriesScreenTestTags
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.KNode
|
||||
|
||||
class SwapStoriesPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<SwapStoriesPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val closeButton: KNode = child {
|
||||
hasTestTag(SwapStoriesScreenTestTags.CLOSE_BUTTON)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onSwapStoriesScreen(function: SwapStoriesPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
package com.tangem.screens
|
||||
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.test.*
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.KNode
|
||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||
import androidx.compose.ui.test.hasTestTag as withTestTag
|
||||
|
||||
class SwapTokenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<SwapTokenPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val title: KNode = child {
|
||||
hasTestTag(TopAppBarTestTags.TITLE)
|
||||
hasText(getResourceString(R.string.common_swap))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val closeButton: KNode = child {
|
||||
hasTestTag(TopAppBarTestTags.CLOSE_BUTTON)
|
||||
}
|
||||
|
||||
val textInput: KNode = child {
|
||||
hasParent(withTestTag(SwapTokenScreenTestTags.SWAP_TEXT_FIELD))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val networkFeeBlock: KNode = child {
|
||||
hasTestTag(BaseBlockTestTags.BLOCK)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val receiveAmountShimmer: KNode = child {
|
||||
hasTestTag(SwapTokenScreenTestTags.RECEIVE_AMOUNT_SHIMMER)
|
||||
}
|
||||
|
||||
val swapTokensOnscreenButton: KNode = child {
|
||||
hasTestTag(SwapTokenScreenTestTags.SWAP_BUTTON)
|
||||
}
|
||||
|
||||
val receiveAmount: KNode = child {
|
||||
hasTestTag(SwapTokenScreenTestTags.RECEIVE_TEXT_FIELD)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val providersBlock: KNode = child {
|
||||
hasTestTag(SwapTokenScreenTestTags.PROVIDERS_BLOCK)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val errorNotificationTitle: KNode = child {
|
||||
hasTestTag(NotificationTestTags.TITLE)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val errorNotificationText: KNode = child {
|
||||
hasTestTag(NotificationTestTags.TEXT)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val refreshButton: KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.BUTTON)
|
||||
hasText(getResourceString(R.string.warning_button_refresh))
|
||||
}
|
||||
|
||||
val swapButton: KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.BUTTON)
|
||||
hasText(getResourceString(R.string.common_swap))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onSwapTokenScreen(function: SwapTokenPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -1,11 +1,19 @@
|
|||
package com.tangem.screens
|
||||
|
||||
import androidx.compose.ui.test.ExperimentalTestApi
|
||||
import androidx.compose.ui.test.SemanticsMatcher
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.core.ui.test.BaseButtonTestTags
|
||||
import com.tangem.common.utils.LazyListItemNode
|
||||
import com.tangem.core.ui.test.TokenDetailsScreenTestTags
|
||||
import com.tangem.features.tokendetails.impl.R
|
||||
import com.tangem.core.ui.utils.LazyListItemPositionSemantics
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.KNode
|
||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||
import io.github.kakaocup.compose.node.element.lazylist.KLazyListNode
|
||||
|
||||
class TokenDetailsPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<TokenDetailsPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
|
@ -13,6 +21,96 @@ class TokenDetailsPageObject(semanticsProvider: SemanticsNodeInteractionsProvide
|
|||
val screenContainer: KNode = child {
|
||||
hasTestTag(TokenDetailsScreenTestTags.SCREEN_CONTAINER)
|
||||
}
|
||||
|
||||
val availableStakingBlock: KNode = child {
|
||||
hasTestTag(TokenDetailsScreenTestTags.STAKING_AVAILABLE_BLOCK)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val stakingBlock: KNode = child {
|
||||
hasTestTag(TokenDetailsScreenTestTags.STAKING_BLOCK)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val availableStakingBlockTitle: KNode = child {
|
||||
hasTestTag(TokenDetailsScreenTestTags.STAKING_SERVICE_TITLE)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val availableStakingBlockText: KNode = child {
|
||||
hasTestTag(TokenDetailsScreenTestTags.STAKING_SERVICE_TEXT)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val availableStakingBlockCurrencyIcon: KNode = child {
|
||||
hasTestTag(TokenDetailsScreenTestTags.STAKING_CURRENCY_ICON)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val stakeButton: KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.TEXT)
|
||||
hasText(getResourceString(R.string.common_stake))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val stakingFiatAmount: KNode = child {
|
||||
hasTestTag(TokenDetailsScreenTestTags.STAKING_FIAT_AMOUNT)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val stakingDot: KNode = child {
|
||||
hasTestTag(TokenDetailsScreenTestTags.STAKING_DOT)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val stakingTokenAmount: KNode = child {
|
||||
hasTestTag(TokenDetailsScreenTestTags.STAKING_TOKEN_AMOUNT)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val stakingChevronIcon: KNode = child {
|
||||
hasTestTag(TokenDetailsScreenTestTags.STAKING_CHEVRON_ICON)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val stakingTitle: KNode = child {
|
||||
hasText(getResourceString(R.string.staking_native))
|
||||
}
|
||||
|
||||
val title: KNode = child {
|
||||
hasTestTag(TokenDetailsScreenTestTags.TOKEN_TITLE)
|
||||
}
|
||||
|
||||
private val horizontalActionChips = KLazyListNode(
|
||||
semanticsProvider = semanticsProvider,
|
||||
viewBuilderAction = { hasTestTag(TokenDetailsScreenTestTags.HORIZONTAL_ACTION_CHIPS) },
|
||||
itemTypeBuilder = { itemType(::LazyListItemNode) },
|
||||
positionMatcher = { position ->
|
||||
SemanticsMatcher.expectValue(
|
||||
LazyListItemPositionSemantics,
|
||||
position
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalTestApi::class)
|
||||
val swapButton: LazyListItemNode = horizontalActionChips.childWith<LazyListItemNode> {
|
||||
hasTestTag(TokenDetailsScreenTestTags.ACTION_BUTTON)
|
||||
hasText(getResourceString(R.string.common_swap))
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalTestApi::class)
|
||||
val sellButton: LazyListItemNode = horizontalActionChips.childWith<LazyListItemNode> {
|
||||
hasTestTag(TokenDetailsScreenTestTags.ACTION_BUTTON)
|
||||
hasText(getResourceString(R.string.common_sell))
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalTestApi::class)
|
||||
val buyButton: LazyListItemNode = horizontalActionChips.childWith<LazyListItemNode> {
|
||||
hasTestTag(TokenDetailsScreenTestTags.ACTION_BUTTON)
|
||||
hasText(getResourceString(R.string.common_buy))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onTokenDetailsScreen(function: TokenDetailsPageObject.() -> Unit) =
|
||||
|
|
|
|||
499
app/src/androidTest/kotlin/com/tangem/tests/BuyTokenTest.kt
Normal file
499
app/src/androidTest/kotlin/com/tangem/tests/BuyTokenTest.kt
Normal file
|
|
@ -0,0 +1,499 @@
|
|||
package com.tangem.tests
|
||||
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.constants.TestConstants.TOTAL_BALANCE
|
||||
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT
|
||||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.common.utils.resetWireMockScenarioState
|
||||
import com.tangem.common.utils.setWireMockScenarioState
|
||||
import com.tangem.scenarios.OpenMainScreenScenario
|
||||
import com.tangem.screens.*
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import io.qameta.allure.kotlin.AllureId
|
||||
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||
import org.junit.Test
|
||||
|
||||
@HiltAndroidTest
|
||||
class BuyTokenTest : BaseTestCase() {
|
||||
|
||||
@AllureId("3478")
|
||||
@DisplayName("Onramp: error in providers loading")
|
||||
@Test
|
||||
fun errorInProvidersLoadingTest() {
|
||||
val scenarioName = "payment_methods"
|
||||
val tokenTitle = "Bitcoin"
|
||||
val balance = TOTAL_BALANCE
|
||||
|
||||
setupHooks(
|
||||
additionalAfterSection = {
|
||||
resetWireMockScenarioState(scenarioName)
|
||||
}
|
||||
).run {
|
||||
step("Reset WireMock scenario '$scenarioName'") {
|
||||
resetWireMockScenarioState(scenarioName)
|
||||
}
|
||||
step("Setup WireMock scenario '$scenarioName' for 'Error' state") {
|
||||
setWireMockScenarioState(scenarioName, "Error")
|
||||
}
|
||||
step("Open 'Main Screen'") {
|
||||
scenario(OpenMainScreenScenario(composeTestRule))
|
||||
}
|
||||
step("Click on 'Synchronize addresses' button") {
|
||||
onMainScreen { synchronizeAddressesButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert wallet balance = $balance") {
|
||||
onMainScreen { walletBalance().assertTextContains(balance) }
|
||||
}
|
||||
step("Click on 'Buy' button") {
|
||||
onMainScreen { buyButton.clickWithAssertion() }
|
||||
}
|
||||
step("Click on token with name: '$tokenTitle'") {
|
||||
onBuyTokenScreen {
|
||||
topAppBarTitle.assertIsDisplayed()
|
||||
tokenWithTitleAndFiatAmount(tokenTitle).clickWithAssertion()
|
||||
}
|
||||
}
|
||||
step("Assert error notification title is displayed") {
|
||||
onBuyTokenDetailsScreen { errorNotificationTitle.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert error notification text is displayed") {
|
||||
onBuyTokenDetailsScreen { errorNotificationText.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Refresh' button is displayed and clickable") {
|
||||
onBuyTokenDetailsScreen { refreshButton.clickWithAssertion() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("2565")
|
||||
@DisplayName("Onramp: validate currency selector")
|
||||
@Test
|
||||
fun validateCurrencySelectorTest() {
|
||||
setupHooks().run {
|
||||
val tokenTitle = "Polygon"
|
||||
val balance = TOTAL_BALANCE
|
||||
val popularFiatsTitle = "Popular Fiats"
|
||||
val otherCurrenciesTitle = "Other currencies"
|
||||
val australianDollar = "AUD"
|
||||
val fiatAmount = "1"
|
||||
val tokenAmount = "POL 488.24938338"
|
||||
val scenarioName = "payment_methods"
|
||||
|
||||
step("Reset WireMock scenario '$scenarioName'") {
|
||||
resetWireMockScenarioState(scenarioName)
|
||||
}
|
||||
|
||||
step("Open 'Main Screen'") {
|
||||
scenario(OpenMainScreenScenario(composeTestRule))
|
||||
}
|
||||
step("Click on 'Synchronize addresses' button") {
|
||||
onMainScreen { synchronizeAddressesButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert wallet balance = '$balance'") {
|
||||
onMainScreen { walletBalance().assertTextContains(balance) }
|
||||
}
|
||||
step("Click on 'Buy' button") {
|
||||
onMainScreen { buyButton.clickWithAssertion() }
|
||||
}
|
||||
step("Click on token with name: '$tokenTitle'") {
|
||||
onBuyTokenScreen {
|
||||
topAppBarTitle.assertIsDisplayed()
|
||||
tokenWithTitleAndFiatAmount(tokenTitle).clickWithAssertion()
|
||||
}
|
||||
}
|
||||
step("Click on 'Confirm' button in 'Dialog'") {
|
||||
onDialog { confirmButton.clickWithAssertion() }
|
||||
}
|
||||
step("Write fiat amount = '$fiatAmount'") {
|
||||
onBuyTokenDetailsScreen { fiatAmountTextField.performTextInput(fiatAmount) }
|
||||
}
|
||||
step("Assert 'Provider loading block' is displayed") {
|
||||
onBuyTokenDetailsScreen {
|
||||
providerLoadingTitle.assertIsDisplayed()
|
||||
providerLoadingText.assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
step("Assert 'Provider block' is displayed") {
|
||||
onBuyTokenDetailsScreen {
|
||||
providerTitle.assertIsDisplayed()
|
||||
providerText.assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
step("Assert token amount = '$tokenAmount'") {
|
||||
onBuyTokenDetailsScreen {
|
||||
tokenAmountField.assertTextContains(tokenAmount)
|
||||
}
|
||||
}
|
||||
step("Fiat currency icon is displayed") {
|
||||
onBuyTokenDetailsScreen { fiatCurrencyIcon.assertIsDisplayed() }
|
||||
}
|
||||
step("Click on 'Expand fiat list' button") {
|
||||
onBuyTokenDetailsScreen { expandFiatListButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert '$popularFiatsTitle' is displayed") {
|
||||
onBuyTokenFiatListBottomSheet {
|
||||
fiatListItemWithTitle(popularFiatsTitle).assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
step("Assert '$otherCurrenciesTitle' is displayed") {
|
||||
onBuyTokenFiatListBottomSheet {
|
||||
fiatListItemWithTitle(otherCurrenciesTitle).assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
step("Click on fiat with title: '$australianDollar'") {
|
||||
onBuyTokenFiatListBottomSheet {
|
||||
fiatListItemWithTitle(australianDollar).performClick()
|
||||
}
|
||||
}
|
||||
step("Assert new fiat currency: '$australianDollar' is displayed") {
|
||||
onBuyTokenDetailsScreen {
|
||||
fiatAmountTextField.assertTextContains(australianDollar + fiatAmount)
|
||||
}
|
||||
}
|
||||
step("Assert token amount = '$tokenAmount'") {
|
||||
onBuyTokenDetailsScreen {
|
||||
tokenAmountField.assertTextContains(tokenAmount)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("2566")
|
||||
@DisplayName("Onramp: validate 'Buy token' screen")
|
||||
@Test
|
||||
fun validateBuyTokenScreenTest() {
|
||||
setupHooks().run {
|
||||
val tokenTitle = "Polygon"
|
||||
val balance = TOTAL_BALANCE
|
||||
val euro = "EUR"
|
||||
val fiatAmount = "1"
|
||||
val tokenAmount = "POL 488.24938338"
|
||||
val scenarioName = "payment_methods"
|
||||
|
||||
step("Reset WireMock scenario '$scenarioName'") {
|
||||
resetWireMockScenarioState(scenarioName)
|
||||
}
|
||||
|
||||
step("Open 'Main Screen'") {
|
||||
scenario(OpenMainScreenScenario(composeTestRule))
|
||||
}
|
||||
step("Click on 'Synchronize addresses' button") {
|
||||
onMainScreen { synchronizeAddressesButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert wallet balance = '$balance'") {
|
||||
onMainScreen { walletBalance().assertTextContains(balance) }
|
||||
}
|
||||
step("Click on 'Buy' button") {
|
||||
onMainScreen { buyButton.clickWithAssertion() }
|
||||
}
|
||||
step("Click on token with name: '$tokenTitle'") {
|
||||
onBuyTokenScreen {
|
||||
topAppBarTitle.assertIsDisplayed()
|
||||
tokenWithTitleAndFiatAmount(tokenTitle).clickWithAssertion()
|
||||
}
|
||||
}
|
||||
step("Click on 'Confirm' button in 'Dialog'") {
|
||||
onDialog { confirmButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Buy Token' title is displayed") {
|
||||
onBuyTokenDetailsScreen { topBarTitle.assertTextContains("Buy $tokenTitle") }
|
||||
}
|
||||
step("Assert 'More button' in top bar is displayed") {
|
||||
onBuyTokenDetailsScreen { topBarMoreButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Write fiat amount = '$fiatAmount'") {
|
||||
onBuyTokenDetailsScreen { fiatAmountTextField.performTextInput(fiatAmount) }
|
||||
}
|
||||
step("Assert fiat amount = '$fiatAmount'") {
|
||||
onBuyTokenDetailsScreen { fiatAmountTextField.assertTextContains(euro + fiatAmount) }
|
||||
}
|
||||
step("Assert 'Provider loading block' is displayed") {
|
||||
onBuyTokenDetailsScreen {
|
||||
providerLoadingTitle.assertIsDisplayed()
|
||||
providerLoadingText.assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
step("Assert 'Provider block' is displayed") {
|
||||
onBuyTokenDetailsScreen {
|
||||
providerTitle.assertIsDisplayed()
|
||||
providerText.assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
step("Assert token amount = '$tokenAmount'") {
|
||||
onBuyTokenDetailsScreen { tokenAmountField.assertTextContains(tokenAmount) }
|
||||
}
|
||||
step("Assert 'ToS' block is displayed") {
|
||||
onBuyTokenDetailsScreen { toSBlock.assertIsDisplayed()}
|
||||
}
|
||||
step("Assert 'Buy' button is displayed") {
|
||||
onBuyTokenDetailsScreen { buyButton.assertIsDisplayed()}
|
||||
}
|
||||
step("Assert 'Close' button in top bar is displayed") {
|
||||
onBuyTokenDetailsScreen { topBarCloseButton.assertIsDisplayed() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("2563")
|
||||
@DisplayName("Onramp: validate 'Residence' settings screen")
|
||||
@Test
|
||||
fun validateResidenceSettingsScreenTest() {
|
||||
setupHooks().run {
|
||||
val tokenTitle = "Polygon"
|
||||
val balance = TOTAL_BALANCE
|
||||
val country = "Albania"
|
||||
val unavailableCountry = "Lebanon"
|
||||
val scenarioName = "payment_methods"
|
||||
|
||||
step("Reset WireMock scenario '$scenarioName'") {
|
||||
resetWireMockScenarioState(scenarioName)
|
||||
}
|
||||
|
||||
step("Open 'Main Screen'") {
|
||||
scenario(OpenMainScreenScenario(composeTestRule))
|
||||
}
|
||||
step("Click on 'Synchronize addresses' button") {
|
||||
onMainScreen { synchronizeAddressesButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert wallet balance = '$balance'") {
|
||||
onMainScreen { walletBalance().assertTextContains(balance) }
|
||||
}
|
||||
step("Click on 'Buy' button") {
|
||||
onMainScreen { buyButton.clickWithAssertion() }
|
||||
}
|
||||
step("Click on token with name: '$tokenTitle'") {
|
||||
onBuyTokenScreen {
|
||||
topAppBarTitle.assertIsDisplayed()
|
||||
tokenWithTitleAndFiatAmount(tokenTitle).clickWithAssertion()
|
||||
}
|
||||
}
|
||||
step("Click on 'Confirm' button in 'Dialog'") {
|
||||
onDialog { confirmButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Buy $tokenTitle' title is displayed") {
|
||||
onBuyTokenDetailsScreen { topBarTitle.assertTextContains("Buy $tokenTitle") }
|
||||
}
|
||||
step("Click 'More' button in tab bar") {
|
||||
onBuyTokenDetailsScreen { topBarMoreButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Residence Settings' screen top bar title is displayed") {
|
||||
onResidenceSettingsScreen { topBarTitle.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Residence Settings' screen top bar 'Close' button is displayed") {
|
||||
onResidenceSettingsScreen { topBarCloseButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Residence' button is displayed on 'Residence Settings' screen") {
|
||||
onResidenceSettingsScreen { residenceButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert country name is displayed on 'Residence Settings' screen") {
|
||||
onResidenceSettingsScreen { countryName.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert residence settings description is displayed on 'Residence Settings' screen") {
|
||||
onResidenceSettingsScreen { residenceSettingsDescription.assertIsDisplayed() }
|
||||
}
|
||||
step("Click 'Residence button'") {
|
||||
onResidenceSettingsScreen { residenceButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Search bar' is displayed") {
|
||||
onSelectCountryBottomSheet { searchBar.assertIsDisplayed() }
|
||||
}
|
||||
step("Type unavailable country name: '$unavailableCountry' in 'Search bar'") {
|
||||
onSelectCountryBottomSheet { searchBar.performTextReplacement(unavailableCountry) }
|
||||
}
|
||||
step("Unavailable country: '$unavailableCountry' is displayed") {
|
||||
onSelectCountryBottomSheet { unavailableCountryWithNameAndIcon(unavailableCountry).assertIsDisplayed() }
|
||||
}
|
||||
step("Type country name: '$country' in 'Search bar'") {
|
||||
onSelectCountryBottomSheet { searchBar.performTextReplacement(country) }
|
||||
}
|
||||
step("Available country: '$country' is displayed") {
|
||||
onSelectCountryBottomSheet { countryWithNameAndIcon(country).assertIsDisplayed() }
|
||||
}
|
||||
step("Click on country: '$country'") {
|
||||
onSelectCountryBottomSheet { countryWithNameAndIcon(country).clickWithAssertion() }
|
||||
}
|
||||
step("Assert country: '$country' is displayed on 'Residence Settings' screen") {
|
||||
onResidenceSettingsScreen { countryName.assertTextContains(country) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("2570")
|
||||
@DisplayName("Onramp: validate 'Select provider' bottom sheet")
|
||||
@Test
|
||||
fun validateProvidersScreenTest() {
|
||||
setupHooks().run {
|
||||
val tokenTitle = "Polygon"
|
||||
val balance = TOTAL_BALANCE
|
||||
val paymentMethod = "Card"
|
||||
val fiatAmount = "1"
|
||||
val providerNameMercuryo = "Mercuryo"
|
||||
val providerNameSimplex = "Simplex"
|
||||
val tokenAmount = "POL 488.24938338"
|
||||
val bestRate = "Best rate"
|
||||
val rate = "-0.00%"
|
||||
val scenarioName = "payment_methods"
|
||||
|
||||
step("Reset WireMock scenario '$scenarioName'") {
|
||||
resetWireMockScenarioState(scenarioName)
|
||||
}
|
||||
|
||||
step("Open 'Main Screen'") {
|
||||
scenario(OpenMainScreenScenario(composeTestRule))
|
||||
}
|
||||
step("Click on 'Synchronize addresses' button") {
|
||||
onMainScreen { synchronizeAddressesButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert wallet balance = '$balance'") {
|
||||
onMainScreen { walletBalance().assertTextContains(balance) }
|
||||
}
|
||||
step("Click on 'Buy' button") {
|
||||
onMainScreen { buyButton.clickWithAssertion() }
|
||||
}
|
||||
step("Click on token with name: '$tokenTitle'") {
|
||||
onBuyTokenScreen {
|
||||
topAppBarTitle.assertIsDisplayed()
|
||||
tokenWithTitleAndFiatAmount(tokenTitle).clickWithAssertion()
|
||||
}
|
||||
}
|
||||
step("Click on 'Confirm' button in 'Dialog'") {
|
||||
onDialog { confirmButton.clickWithAssertion() }
|
||||
}
|
||||
step("Write fiat amount = '$fiatAmount'") {
|
||||
onBuyTokenDetailsScreen { fiatAmountTextField.performTextInput(fiatAmount) }
|
||||
}
|
||||
step("Assert 'Provider block' is displayed") {
|
||||
onBuyTokenDetailsScreen {
|
||||
providerTitle.assertIsDisplayed()
|
||||
providerText.assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
step("Open 'Select Provider' bottom sheet") {
|
||||
onBuyTokenDetailsScreen { providerTitle.performClick() }
|
||||
}
|
||||
step("Assert available provider name is displayed") {
|
||||
onSelectProviderBottomSheet {
|
||||
flakySafely(WAIT_UNTIL_TIMEOUT) {
|
||||
availableProviderItem.assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
}
|
||||
step("Click on 'Expand payment methods' button") {
|
||||
onSelectProviderBottomSheet { paymentMethodExpandButton.clickWithAssertion() }
|
||||
}
|
||||
step("Click on payment method: '$paymentMethod'") {
|
||||
onSelectPaymentMethodBottomSheet { paymentMethodWithNameAndIcon(paymentMethod).clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Select Provider' bottom sheet title is displayed") {
|
||||
onSelectProviderBottomSheet { title.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert payment method icon is displayed") {
|
||||
onSelectProviderBottomSheet { paymentMethodIcon.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert payment method title is displayed") {
|
||||
onSelectProviderBottomSheet { paymentMethodTitle.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert payment method name is displayed") {
|
||||
onSelectProviderBottomSheet { paymentMethodName.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert provider with name: '$providerNameMercuryo' and rate: '$bestRate' is displayed") {
|
||||
onSelectProviderBottomSheet {
|
||||
availableProviderWithName(providerNameMercuryo, tokenAmount, bestRate).assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
step("Assert provider with name: '$providerNameSimplex' and rate: '$rate' is displayed") {
|
||||
onSelectProviderBottomSheet {
|
||||
availableProviderWithName(providerNameSimplex, tokenAmount, rate).assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
step("Assert 'More providers' icon is displayed") {
|
||||
onSelectProviderBottomSheet { moreProvidersIcon.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'More providers' text is displayed") {
|
||||
onSelectProviderBottomSheet { moreProvidersText.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Best rate' label is displayed") {
|
||||
onSelectProviderBottomSheet { bestRateLabel.assertIsDisplayed() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("3479")
|
||||
@DisplayName("Onramp: validate 'Select payment method' bottom sheet")
|
||||
@Test
|
||||
fun validatePaymentMethodScreenTest() {
|
||||
setupHooks().run {
|
||||
val tokenTitle = "Polygon"
|
||||
val balance = TOTAL_BALANCE
|
||||
val card = "Card"
|
||||
val googlePay = "Google Pay"
|
||||
val invoiceRevolutPay = "Invoice Revolut Pay"
|
||||
val sepa = "Sepa"
|
||||
val fiatAmount = "1"
|
||||
val scenarioName = "payment_methods"
|
||||
|
||||
step("Reset WireMock scenario '$scenarioName'") {
|
||||
resetWireMockScenarioState(scenarioName)
|
||||
}
|
||||
|
||||
step("Open 'Main Screen'") {
|
||||
scenario(OpenMainScreenScenario(composeTestRule))
|
||||
}
|
||||
step("Click on 'Synchronize addresses' button") {
|
||||
onMainScreen { synchronizeAddressesButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert wallet balance = '$balance'") {
|
||||
onMainScreen { walletBalance().assertTextContains(balance) }
|
||||
}
|
||||
step("Click on 'Buy' button") {
|
||||
onMainScreen { buyButton.clickWithAssertion() }
|
||||
}
|
||||
step("Click on token with name: '$tokenTitle'") {
|
||||
onBuyTokenScreen {
|
||||
topAppBarTitle.assertIsDisplayed()
|
||||
tokenWithTitleAndFiatAmount(tokenTitle).clickWithAssertion()
|
||||
}
|
||||
}
|
||||
step("Click on 'Confirm' button in 'Dialog'") {
|
||||
onDialog { confirmButton.clickWithAssertion() }
|
||||
}
|
||||
step("Write fiat amount = '$fiatAmount'") {
|
||||
onBuyTokenDetailsScreen { fiatAmountTextField.performTextInput(fiatAmount) }
|
||||
}
|
||||
step("Assert 'Provider block' is displayed") {
|
||||
onBuyTokenDetailsScreen {
|
||||
providerTitle.assertIsDisplayed()
|
||||
providerText.assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
step("Open 'Select Provider' bottom sheet") {
|
||||
onBuyTokenDetailsScreen { providerTitle.performClick() }
|
||||
}
|
||||
step("Click on 'Expand payment methods' button") {
|
||||
onSelectProviderBottomSheet { paymentMethodExpandButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Select Payment Method' bottom sheet title is displayed") {
|
||||
onSelectPaymentMethodBottomSheet { title.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert payment method: '$card' is displayed") {
|
||||
onSelectPaymentMethodBottomSheet { paymentMethodWithNameAndIcon(card).assertIsDisplayed() }
|
||||
}
|
||||
step("Assert payment method: '$googlePay' is displayed") {
|
||||
onSelectPaymentMethodBottomSheet { paymentMethodWithNameAndIcon(googlePay).assertIsDisplayed() }
|
||||
}
|
||||
step("Assert payment method: '$invoiceRevolutPay' is displayed") {
|
||||
onSelectPaymentMethodBottomSheet { paymentMethodWithNameAndIcon(invoiceRevolutPay).assertIsDisplayed() }
|
||||
}
|
||||
step("Assert payment method: '$sepa' is displayed") {
|
||||
onSelectPaymentMethodBottomSheet { paymentMethodWithNameAndIcon(sepa).assertIsDisplayed() }
|
||||
}
|
||||
step("Press 'Back' button") {
|
||||
onSelectPaymentMethodBottomSheet { device.uiDevice.pressBack() }
|
||||
}
|
||||
step("Assert 'Select Provider' bottom sheet title is displayed") {
|
||||
onSelectProviderBottomSheet { title.assertIsDisplayed() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -5,9 +5,12 @@ import com.tangem.common.extensions.clickWithAssertion
|
|||
import com.tangem.domain.models.scan.ProductType
|
||||
import com.tangem.scenarios.OpenMainScreenScenario
|
||||
import com.tangem.screens.onDetailsScreen
|
||||
import com.tangem.screens.onReferralProgramScreen
|
||||
import com.tangem.screens.onTopBar
|
||||
import com.tangem.screens.onWalletSettingsScreen
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import io.qameta.allure.kotlin.AllureId
|
||||
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||
import org.junit.Test
|
||||
|
||||
@HiltAndroidTest
|
||||
|
|
@ -61,7 +64,7 @@ class DetailsTest : BaseTestCase() {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
// @Test
|
||||
fun wallet2DetailsTest() =
|
||||
setupHooks().run {
|
||||
scenario(OpenMainScreenScenario(composeTestRule, ProductType.Wallet2))
|
||||
|
|
@ -153,4 +156,48 @@ class DetailsTest : BaseTestCase() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("3647")
|
||||
@DisplayName("Referral program: validate screen")
|
||||
@Test
|
||||
fun validateReferralProgramScreenTest() =
|
||||
setupHooks().run {
|
||||
scenario(OpenMainScreenScenario(composeTestRule))
|
||||
step("Open wallet details") {
|
||||
onTopBar { moreButton.clickWithAssertion() }
|
||||
}
|
||||
step("Open 'Wallet settings' screen") {
|
||||
onDetailsScreen { walletNameButton.clickWithAssertion() }
|
||||
}
|
||||
step("Click on 'Referral program' button ") {
|
||||
onWalletSettingsScreen { referralProgramButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Referral program' screen title is displayed") {
|
||||
onReferralProgramScreen { title.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Referral program' screen image is displayed") {
|
||||
onReferralProgramScreen { image.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Referral program' screen refer title is displayed") {
|
||||
onReferralProgramScreen { referTitle.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert info for you title is displayed") {
|
||||
onReferralProgramScreen { infoForYouText.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert info for you text is displayed") {
|
||||
onReferralProgramScreen { infoForYouBlock.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert info for your friend title is displayed") {
|
||||
onReferralProgramScreen { infoForYourFriendText.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert info for your friend text is displayed") {
|
||||
onReferralProgramScreen { infoForYourFriendBlock.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert agreement text is displayed") {
|
||||
onReferralProgramScreen { agreementText.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Participate' button is displayed") {
|
||||
onReferralProgramScreen { participateButton.assertIsDisplayed() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.tests
|
||||
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.constants.TestConstants.TOTAL_BALANCE
|
||||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.scenarios.OpenMainScreenScenario
|
||||
import com.tangem.screens.*
|
||||
|
|
@ -17,7 +18,7 @@ class HideTokenTest : BaseTestCase() {
|
|||
@Test
|
||||
fun hideWalletTokenByHideButtonTest() {
|
||||
val tokenTitle = "Polygon"
|
||||
val balance = "<$0.01"
|
||||
val balance = TOTAL_BALANCE
|
||||
setupHooks().run {
|
||||
step("Open 'Main Screen'") {
|
||||
scenario(OpenMainScreenScenario(composeTestRule))
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@ package com.tangem.tests
|
|||
|
||||
import androidx.compose.ui.test.onAllNodesWithText
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.constants.TestConstants.TOTAL_BALANCE
|
||||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.common.extensions.swipeUp
|
||||
import com.tangem.scenarios.OpenMainScreenScenario
|
||||
import com.tangem.screens.onMainScreen
|
||||
import com.tangem.screens.onOrganizeTokensScreen
|
||||
|
|
@ -27,6 +29,10 @@ class OrganizeTokensTest : BaseTestCase() {
|
|||
step("Click on 'Synchronize addresses' button" ) {
|
||||
onMainScreen { synchronizeAddressesButton.clickWithAssertion() }
|
||||
}
|
||||
step("Swipe to 'Organize tokens' button") {
|
||||
swipeUp()
|
||||
swipeUp()
|
||||
}
|
||||
step("Click 'Organize tokens' button") {
|
||||
onMainScreen { organizeTokensButton().clickWithAssertion() }
|
||||
}
|
||||
|
|
@ -48,6 +54,10 @@ class OrganizeTokensTest : BaseTestCase() {
|
|||
step("Assert tokens were grouped on 'Main screen'") {
|
||||
onMainScreen { tokenNetworkGroupTitle(tokenNetwork).assertIsDisplayed() }
|
||||
}
|
||||
step("Swipe to 'Organize tokens' button") {
|
||||
swipeUp()
|
||||
swipeUp()
|
||||
}
|
||||
step("Click 'Organize tokens' button") {
|
||||
onMainScreen { organizeTokensButton().clickWithAssertion() }
|
||||
}
|
||||
|
|
@ -79,18 +89,26 @@ class OrganizeTokensTest : BaseTestCase() {
|
|||
setupHooks().run {
|
||||
val ethereumTitle = "Ethereum"
|
||||
val bitcoinTitle = "Bitcoin"
|
||||
val balance = TOTAL_BALANCE
|
||||
step("Open 'Main Screen'") {
|
||||
scenario(OpenMainScreenScenario(composeTestRule))
|
||||
}
|
||||
step("Click on 'Synchronize addresses' button" ) {
|
||||
onMainScreen { synchronizeAddressesButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert wallet balance = '$balance'") {
|
||||
onMainScreen { walletBalance().assertTextContains(balance) }
|
||||
}
|
||||
step("Check positions of tokens on 'Main Screen'") {
|
||||
onMainScreen {
|
||||
tokenWithTitleAndPosition(bitcoinTitle, 0).assertIsDisplayed()
|
||||
tokenWithTitleAndPosition(ethereumTitle, 1).assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
step("Swipe to 'Organize tokens' button") {
|
||||
swipeUp()
|
||||
swipeUp()
|
||||
}
|
||||
step("Click 'Organize tokens' button") {
|
||||
onMainScreen { organizeTokensButton().clickWithAssertion() }
|
||||
}
|
||||
|
|
@ -123,6 +141,10 @@ class OrganizeTokensTest : BaseTestCase() {
|
|||
tokenWithTitleAndPosition(ethereumTitle, 1).assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
step("Swipe to 'Organize tokens' button") {
|
||||
swipeUp()
|
||||
swipeUp()
|
||||
}
|
||||
step("Click 'Organize tokens' button") {
|
||||
onMainScreen { organizeTokensButton().clickWithAssertion() }
|
||||
}
|
||||
|
|
@ -154,12 +176,17 @@ class OrganizeTokensTest : BaseTestCase() {
|
|||
val ethereumTitle = "Ethereum"
|
||||
val bitcoinTitle = "Bitcoin"
|
||||
val polygonTitle = "Polygon"
|
||||
val polExMaticTitle = "POL (ex-MATIC)"
|
||||
val balance = TOTAL_BALANCE
|
||||
step("Open 'Main Screen'") {
|
||||
scenario(OpenMainScreenScenario(composeTestRule))
|
||||
}
|
||||
step("Click on 'Synchronize addresses' button" ) {
|
||||
onMainScreen { synchronizeAddressesButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert wallet balance = '$balance'") {
|
||||
onMainScreen { walletBalance().assertTextContains(balance) }
|
||||
}
|
||||
step("Check positions of tokens on 'Main Screen'") {
|
||||
onMainScreen {
|
||||
tokenWithTitleAndPosition(bitcoinTitle, 0).assertIsDisplayed()
|
||||
|
|
@ -167,6 +194,10 @@ class OrganizeTokensTest : BaseTestCase() {
|
|||
tokenWithTitleAndPosition(polygonTitle, 2).assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
step("Swipe to 'Organize tokens' button") {
|
||||
swipeUp()
|
||||
swipeUp()
|
||||
}
|
||||
step("Click 'Organize tokens' button") {
|
||||
onMainScreen { organizeTokensButton().clickWithAssertion() }
|
||||
}
|
||||
|
|
@ -175,6 +206,7 @@ class OrganizeTokensTest : BaseTestCase() {
|
|||
tokenWithTitleAndPosition(bitcoinTitle, 1).assertIsDisplayed()
|
||||
tokenWithTitleAndPosition(ethereumTitle, 2).assertIsDisplayed()
|
||||
tokenWithTitleAndPosition(polygonTitle, 3).assertIsDisplayed()
|
||||
tokenWithTitleAndPosition(polExMaticTitle, 4).assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
step("Click 'By Balance' button") {
|
||||
|
|
@ -185,8 +217,9 @@ class OrganizeTokensTest : BaseTestCase() {
|
|||
step("Check positions of tokens by balance on 'Organize tokens' screen") {
|
||||
onOrganizeTokensScreen {
|
||||
tokenWithTitleAndPosition(ethereumTitle, 1).assertIsDisplayed()
|
||||
tokenWithTitleAndPosition(polygonTitle, 2).assertIsDisplayed()
|
||||
tokenWithTitleAndPosition(bitcoinTitle, 3).assertIsDisplayed()
|
||||
tokenWithTitleAndPosition(polExMaticTitle, 2).assertIsDisplayed()
|
||||
tokenWithTitleAndPosition(polygonTitle, 3).assertIsDisplayed()
|
||||
tokenWithTitleAndPosition(bitcoinTitle, 4).assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
step("Click 'Apply' button") {
|
||||
|
|
@ -195,8 +228,9 @@ class OrganizeTokensTest : BaseTestCase() {
|
|||
step("Check positions of tokens by balance on 'Organize tokens' screen") {
|
||||
onMainScreen {
|
||||
tokenWithTitleAndPosition(ethereumTitle, 0).assertIsDisplayed()
|
||||
tokenWithTitleAndPosition(polygonTitle, 1).assertIsDisplayed()
|
||||
tokenWithTitleAndPosition(bitcoinTitle, 2).assertIsDisplayed()
|
||||
tokenWithTitleAndPosition(polExMaticTitle, 1).assertIsDisplayed()
|
||||
tokenWithTitleAndPosition(polygonTitle, 2).assertIsDisplayed()
|
||||
tokenWithTitleAndPosition(bitcoinTitle, 3).assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
379
app/src/androidTest/kotlin/com/tangem/tests/StakingTest.kt
Normal file
379
app/src/androidTest/kotlin/com/tangem/tests/StakingTest.kt
Normal file
|
|
@ -0,0 +1,379 @@
|
|||
package com.tangem.tests
|
||||
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.constants.TestConstants.TOTAL_BALANCE
|
||||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.common.utils.resetWireMockScenarioState
|
||||
import com.tangem.common.utils.setWireMockScenarioState
|
||||
import com.tangem.scenarios.OpenMainScreenScenario
|
||||
import com.tangem.screens.*
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import io.qameta.allure.kotlin.AllureId
|
||||
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||
import org.junit.Test
|
||||
|
||||
@HiltAndroidTest
|
||||
class StakingTest : BaseTestCase() {
|
||||
|
||||
@AllureId("3558")
|
||||
@DisplayName("Staking: validate staking block on 'Token details' screen")
|
||||
@Test
|
||||
fun validateStakingBlockTest() {
|
||||
val tokenTitle = "POL (ex-MATIC)"
|
||||
val balance = TOTAL_BALANCE
|
||||
val scenarioName = "staking_eth_pol_balances_android"
|
||||
val scenarioState = "Staked"
|
||||
|
||||
setupHooks(
|
||||
additionalAfterSection = {
|
||||
resetWireMockScenarioState(scenarioName)
|
||||
}
|
||||
).run {
|
||||
|
||||
step("Set WireMock scenario: '$scenarioName' to state: '$scenarioState'") {
|
||||
setWireMockScenarioState(scenarioName = scenarioName, state = scenarioState)
|
||||
}
|
||||
|
||||
step("Open 'Main Screen'") {
|
||||
scenario(OpenMainScreenScenario(composeTestRule))
|
||||
}
|
||||
step("Click on 'Synchronize addresses' button") {
|
||||
onMainScreen { synchronizeAddressesButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert wallet balance = $balance") {
|
||||
onMainScreen { walletBalance().assertTextContains(balance) }
|
||||
}
|
||||
step("Click on token with name: '$tokenTitle'") {
|
||||
onMainScreen { tokenWithTitleAndAddress(tokenTitle).clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Token details screen' open") {
|
||||
onTokenDetailsScreen { screenContainer.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Staking block' is displayed") {
|
||||
onTokenDetailsScreen { stakingBlock.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Staking title' is displayed") {
|
||||
onTokenDetailsScreen { stakingTitle.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Staking fiat amount' is displayed") {
|
||||
onTokenDetailsScreen { stakingFiatAmount.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Staking dot' is displayed") {
|
||||
onTokenDetailsScreen { stakingDot.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Staking token amount' is displayed") {
|
||||
onTokenDetailsScreen { stakingTokenAmount.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Staking block chevron icon' is displayed") {
|
||||
onTokenDetailsScreen { stakingChevronIcon.assertIsDisplayed() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("3550")
|
||||
@DisplayName("Staking: validate staking more screens")
|
||||
@Test
|
||||
fun validateStakingMoreScreensTest() {
|
||||
val tokenTitle = "POL (ex-MATIC)"
|
||||
val balance = TOTAL_BALANCE
|
||||
val scenarioName = "staking_eth_pol_balances_android"
|
||||
val scenarioState = "Staked"
|
||||
val stakingAmount = "1"
|
||||
|
||||
setupHooks(
|
||||
additionalAfterSection = {
|
||||
resetWireMockScenarioState(scenarioName)
|
||||
}
|
||||
).run {
|
||||
|
||||
step("Set WireMock scenario: '$scenarioName' to state: '$scenarioState'") {
|
||||
setWireMockScenarioState(scenarioName = scenarioName, state = scenarioState)
|
||||
}
|
||||
|
||||
step("Open 'Main Screen'") {
|
||||
scenario(OpenMainScreenScenario(composeTestRule))
|
||||
}
|
||||
step("Click on 'Synchronize addresses' button") {
|
||||
onMainScreen { synchronizeAddressesButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert wallet balance = $balance") {
|
||||
onMainScreen { walletBalance().assertTextContains(balance) }
|
||||
}
|
||||
step("Click on token with name: '$tokenTitle'") {
|
||||
onMainScreen { tokenWithTitleAndAddress(tokenTitle).clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Token details screen' open") {
|
||||
onTokenDetailsScreen { screenContainer.assertIsDisplayed() }
|
||||
}
|
||||
step("Click on 'Staking block'") {
|
||||
onTokenDetailsScreen { stakingBlock.clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Title' is displayed") {
|
||||
onStakingDetailsScreen { stakingTitle.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Annual percentage rate' is displayed") {
|
||||
onStakingDetailsScreen { annualPercentageRate.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Available' block is displayed") {
|
||||
onStakingDetailsScreen { availableBlock.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Unbonding Period' block is displayed") {
|
||||
onStakingDetailsScreen { unbondingPeriodBlock.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Reward claiming' block is displayed") {
|
||||
onStakingDetailsScreen { rewardClaimingBlock.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Reward schedule' block is displayed") {
|
||||
onStakingDetailsScreen { rewardScheduleBlock.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Rewards block' is displayed") {
|
||||
onStakingDetailsScreen { rewardsBlock.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Rewards block' title is displayed") {
|
||||
onStakingDetailsScreen { rewardsBlockTitle.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Rewards block' text is displayed") {
|
||||
onStakingDetailsScreen { rewardsBlockText.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Active staking block' is displayed") {
|
||||
onStakingDetailsScreen { activeStakingBlock.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Your stakes' title is displayed") {
|
||||
onStakingDetailsScreen { yourStakesTitle.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'ToS' text is displayed") {
|
||||
onStakingDetailsScreen { toSText.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Stake more' button is displayed") {
|
||||
onStakingDetailsScreen { stakeMoreButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Click 'Stake more' button") {
|
||||
onStakingDetailsScreen { stakeMoreButton.performClick() }
|
||||
}
|
||||
step("Assert 'Send' screen is displayed") {
|
||||
onStakingSendScreen { screenContainer.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Send' screen title is displayed") {
|
||||
onStakingSendScreen { title.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert amount container title is displayed") {
|
||||
onStakingSendScreen { amountContainerTitle.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert amount container text is displayed") {
|
||||
onStakingSendScreen { amountContainerText.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert input text field is displayed") {
|
||||
onStakingSendScreen { amountInputTextField.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert secondary amount is displayed") {
|
||||
onStakingSendScreen { secondaryAmount.assertIsDisplayed() }
|
||||
}
|
||||
step("Type '$stakingAmount' in input text field") {
|
||||
onStakingSendScreen {
|
||||
amountInputTextField.performClick()
|
||||
amountInputTextField.performTextReplacement(stakingAmount)
|
||||
}
|
||||
}
|
||||
step("Assert input text field has value: '$stakingAmount'") {
|
||||
onStakingSendScreen { amountInputTextField.assertTextContains(value = stakingAmount, substring = true) }
|
||||
}
|
||||
step("Assert currency button is displayed") {
|
||||
onStakingSendScreen { currencyButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert fiat button is displayed") {
|
||||
onStakingSendScreen { fiatButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert currency button is displayed") {
|
||||
onStakingSendScreen { currencyButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert fiat button is displayed") {
|
||||
onStakingSendScreen { fiatButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Max' button is displayed") {
|
||||
onStakingSendScreen { maxButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert previous button is displayed") {
|
||||
onStakingSendScreen { previousButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Next' button is displayed") {
|
||||
onStakingSendScreen { nextButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onStakingSendScreen { nextButton.performClick() }
|
||||
}
|
||||
step("Assert 'Send details' screen title is displayed") {
|
||||
onStakingSendDetailsScreen { title.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert primary amount is displayed") {
|
||||
onStakingSendDetailsScreen { primaryAmount.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert secondary amount is displayed") {
|
||||
onStakingSendDetailsScreen { secondaryAmount.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Validator' block is displayed") {
|
||||
onStakingSendDetailsScreen { validatorBlock.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Network Fee' block is displayed") {
|
||||
onStakingSendDetailsScreen { networkFeeBlock.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Stake' button is displayed") {
|
||||
onStakingSendDetailsScreen { stakeButton.assertIsDisplayed() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("3548")
|
||||
@DisplayName("Staking: validate staking screens")
|
||||
@Test
|
||||
fun validateStakingScreensTest() {
|
||||
val tokenTitle = "POL (ex-MATIC)"
|
||||
val balance = TOTAL_BALANCE
|
||||
val scenarioName = "staking_eth_pol_balances_android"
|
||||
val scenarioState = "Started"
|
||||
val stakingAmount = "1"
|
||||
|
||||
setupHooks(
|
||||
additionalAfterSection = {
|
||||
resetWireMockScenarioState(scenarioName)
|
||||
}
|
||||
).run {
|
||||
|
||||
step("Set WireMock scenario: '$scenarioName' to state: '$scenarioState'") {
|
||||
setWireMockScenarioState(scenarioName = scenarioName, state = scenarioState)
|
||||
}
|
||||
|
||||
step("Open 'Main Screen'") {
|
||||
scenario(OpenMainScreenScenario(composeTestRule))
|
||||
}
|
||||
step("Click on 'Synchronize addresses' button") {
|
||||
onMainScreen { synchronizeAddressesButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert wallet balance = $balance") {
|
||||
onMainScreen { walletBalance().assertTextContains(balance) }
|
||||
}
|
||||
step("Click on token with name: '$tokenTitle'") {
|
||||
onMainScreen { tokenWithTitleAndAddress(tokenTitle).clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Token details screen' open") {
|
||||
onTokenDetailsScreen { screenContainer.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Available staking block' is displayed") {
|
||||
onTokenDetailsScreen { availableStakingBlock.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Available staking block' title is displayed") {
|
||||
onTokenDetailsScreen { availableStakingBlockTitle.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Available staking block' text is displayed") {
|
||||
onTokenDetailsScreen { availableStakingBlockText.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Available staking block' currency icon is displayed") {
|
||||
onTokenDetailsScreen { availableStakingBlockCurrencyIcon.assertIsDisplayed() }
|
||||
}
|
||||
step("Click on 'Stake' button") {
|
||||
onTokenDetailsScreen { stakeButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Title' is displayed") {
|
||||
onStakingDetailsScreen { stakingTitle.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert banner image is displayed") {
|
||||
onStakingDetailsScreen { bannerImage.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert banner text is displayed") {
|
||||
onStakingDetailsScreen { bannerText.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Annual percentage rate' is displayed") {
|
||||
onStakingDetailsScreen { annualPercentageRate.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Available' block is displayed") {
|
||||
onStakingDetailsScreen { availableBlock.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Unbonding Period' block is displayed") {
|
||||
onStakingDetailsScreen { unbondingPeriodBlock.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Reward claiming' block is displayed") {
|
||||
onStakingDetailsScreen { rewardClaimingBlock.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Reward schedule' block is displayed") {
|
||||
onStakingDetailsScreen { rewardScheduleBlock.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'ToS' text is displayed") {
|
||||
onStakingDetailsScreen { toSText.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Stake' button is displayed") {
|
||||
onStakingDetailsScreen { stakeButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Click 'Stake' button") {
|
||||
onStakingDetailsScreen { stakeButton.performClick() }
|
||||
}
|
||||
step("Assert 'Send' screen is displayed") {
|
||||
onStakingSendScreen { screenContainer.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Send' screen title is displayed") {
|
||||
onStakingSendScreen { title.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert amount container title is displayed") {
|
||||
onStakingSendScreen { amountContainerTitle.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert amount container text is displayed") {
|
||||
onStakingSendScreen { amountContainerText.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert input text field is displayed") {
|
||||
onStakingSendScreen { amountInputTextField.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert secondary amount is displayed") {
|
||||
onStakingSendScreen { secondaryAmount.assertIsDisplayed() }
|
||||
}
|
||||
step("Type '$stakingAmount' in input text field") {
|
||||
onStakingSendScreen {
|
||||
amountInputTextField.performClick()
|
||||
amountInputTextField.performTextReplacement(stakingAmount)
|
||||
}
|
||||
}
|
||||
step("Assert input text field has value: '$stakingAmount'") {
|
||||
onStakingSendScreen { amountInputTextField.assertTextContains(value = stakingAmount, substring = true) }
|
||||
}
|
||||
step("Assert currency button is displayed") {
|
||||
onStakingSendScreen { currencyButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert fiat button is displayed") {
|
||||
onStakingSendScreen { fiatButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert currency button is displayed") {
|
||||
onStakingSendScreen { currencyButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert fiat button is displayed") {
|
||||
onStakingSendScreen { fiatButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Max' button is displayed") {
|
||||
onStakingSendScreen { maxButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert previous button is displayed") {
|
||||
onStakingSendScreen { previousButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Next' button is displayed") {
|
||||
onStakingSendScreen { nextButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onStakingSendScreen { nextButton.performClick() }
|
||||
}
|
||||
step("Assert 'Send details' screen title is displayed") {
|
||||
onStakingSendDetailsScreen { title.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert primary amount is displayed") {
|
||||
onStakingSendDetailsScreen { primaryAmount.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert secondary amount is displayed") {
|
||||
onStakingSendDetailsScreen { secondaryAmount.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Validator' block is displayed") {
|
||||
onStakingSendDetailsScreen { validatorBlock.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Network Fee' block is displayed") {
|
||||
onStakingSendDetailsScreen { networkFeeBlock.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Stake' button is displayed") {
|
||||
onStakingSendDetailsScreen { stakeButton.assertIsDisplayed() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,17 +5,16 @@ import com.tangem.common.BaseTestCase
|
|||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.screens.onDisclaimerScreen
|
||||
import com.tangem.screens.onStoriesScreen
|
||||
import com.tangem.tap.features.home.redux.HomeMiddleware.NEW_BUY_WALLET_URL
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import io.github.kakaocup.kakao.intent.KIntent
|
||||
import org.junit.Test
|
||||
|
||||
@HiltAndroidTest
|
||||
class StoriesTest : BaseTestCase() {
|
||||
|
||||
@Test
|
||||
// @Test
|
||||
fun clickOnOrderButtonTest() =
|
||||
setupHooks().run {
|
||||
val buyWalletUrl = "https://buy.tangem.com/"
|
||||
onDisclaimerScreen {
|
||||
step("Click on 'Accept' button") {
|
||||
acceptButton.clickWithAssertion()
|
||||
|
|
@ -28,7 +27,7 @@ class StoriesTest : BaseTestCase() {
|
|||
step("Assert: browser opened") {
|
||||
val expectedIntent = KIntent {
|
||||
hasAction(ACTION_VIEW)
|
||||
hasData { toString().startsWith(NEW_BUY_WALLET_URL) }
|
||||
hasData { toString().startsWith(buyWalletUrl) }
|
||||
}
|
||||
expectedIntent.intended()
|
||||
device.uiDevice.pressBack()
|
||||
|
|
|
|||
240
app/src/androidTest/kotlin/com/tangem/tests/SwapTokenTest.kt
Normal file
240
app/src/androidTest/kotlin/com/tangem/tests/SwapTokenTest.kt
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
package com.tangem.tests
|
||||
|
||||
import androidx.compose.ui.test.hasText
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.annotations.ApiEnv
|
||||
import com.tangem.common.annotations.ApiEnvConfig
|
||||
import com.tangem.common.constants.TestConstants.TOTAL_BALANCE
|
||||
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT
|
||||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.datasource.api.common.config.ApiConfig
|
||||
import com.tangem.datasource.api.common.config.ApiEnvironment
|
||||
import com.tangem.scenarios.OpenMainScreenScenario
|
||||
import com.tangem.screens.*
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import io.qameta.allure.kotlin.AllureId
|
||||
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||
import org.junit.Test
|
||||
|
||||
@HiltAndroidTest
|
||||
class SwapTokenTest : BaseTestCase() {
|
||||
|
||||
@ApiEnv(
|
||||
ApiEnvConfig(ApiConfig.ID.Express, ApiEnvironment.PROD)
|
||||
)
|
||||
@AllureId("3546")
|
||||
@DisplayName("Swap: network fee")
|
||||
@Test
|
||||
fun networkFeeTest() {
|
||||
val inputAmount = "100"
|
||||
setupHooks().run {
|
||||
val tokenTitle = "Polygon"
|
||||
val balance = TOTAL_BALANCE
|
||||
|
||||
step("Open 'Main Screen'") {
|
||||
scenario(OpenMainScreenScenario(composeTestRule))
|
||||
}
|
||||
step("Click on 'Synchronize addresses' button") {
|
||||
onMainScreen { synchronizeAddressesButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert wallet balance = $balance") {
|
||||
onMainScreen { walletBalance().assertTextContains(balance) }
|
||||
}
|
||||
step("Click on token with name: '$tokenTitle'") {
|
||||
onMainScreen { tokenWithTitleAndAddress(tokenTitle).clickWithAssertion() }
|
||||
}
|
||||
step("Click on token with name: '$tokenTitle'") {
|
||||
onTokenDetailsScreen { title.assertIsDisplayed() }
|
||||
}
|
||||
step("Click on 'Swap' button") {
|
||||
onTokenDetailsScreen { swapButton.performClick() }
|
||||
}
|
||||
step("Close 'Stories' screen") {
|
||||
onSwapStoriesScreen { closeButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Swap' screen title is displayed") {
|
||||
onSwapTokenScreen { title.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Close' button is displayed") {
|
||||
onSwapTokenScreen { closeButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Swap tokens on screen' button is displayed") {
|
||||
onSwapTokenScreen {
|
||||
flakySafely(WAIT_UNTIL_TIMEOUT) {
|
||||
swapTokensOnscreenButton.assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
}
|
||||
step("Assert receive amount is displayed") {
|
||||
onSwapTokenScreen {
|
||||
flakySafely(WAIT_UNTIL_TIMEOUT) {
|
||||
receiveAmount.assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
}
|
||||
step("Input swap amount = '$inputAmount'") {
|
||||
composeTestRule.waitForIdle()
|
||||
onSwapTokenScreen {
|
||||
textInput.clickWithAssertion()
|
||||
textInput.performTextReplacement(inputAmount)
|
||||
}
|
||||
}
|
||||
step("Assert input amount = '$inputAmount'") {
|
||||
onSwapTokenScreen { textInput.assertTextEquals(inputAmount) }
|
||||
}
|
||||
step("Assert 'Providers' block is displayed") {
|
||||
onSwapTokenScreen {
|
||||
flakySafely(WAIT_UNTIL_TIMEOUT) {
|
||||
providersBlock.assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
}
|
||||
step("Assert 'Network fee' block is displayed") {
|
||||
onSwapTokenScreen {
|
||||
flakySafely(WAIT_UNTIL_TIMEOUT) {
|
||||
networkFeeBlock.assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
}
|
||||
step("Assert receive amount is not equal to '0'") {
|
||||
onSwapTokenScreen { receiveAmount.assert(!hasText("0")) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("3549")
|
||||
@DisplayName("Swap: network error test")
|
||||
@Test
|
||||
fun networkErrorSwapTest() {
|
||||
setupHooks().run {
|
||||
val tokenTitle = "Polygon"
|
||||
val balance = TOTAL_BALANCE
|
||||
|
||||
step("Open 'Main Screen'") {
|
||||
scenario(OpenMainScreenScenario(composeTestRule))
|
||||
}
|
||||
step("Click on 'Synchronize addresses' button") {
|
||||
onMainScreen { synchronizeAddressesButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert wallet balance = $balance") {
|
||||
onMainScreen { walletBalance().assertTextContains(balance) }
|
||||
}
|
||||
step("Click on token with name: '$tokenTitle'") {
|
||||
onMainScreen { tokenWithTitleAndAddress(tokenTitle).clickWithAssertion() }
|
||||
}
|
||||
step("Click on token with name: '$tokenTitle'") {
|
||||
onTokenDetailsScreen { title.assertIsDisplayed() }
|
||||
}
|
||||
step("Click on 'Swap' button") {
|
||||
onTokenDetailsScreen { swapButton.performClick() }
|
||||
}
|
||||
step("Close 'Stories' screen") {
|
||||
onSwapStoriesScreen { closeButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Swap' screen title is displayed") {
|
||||
onSwapTokenScreen { title.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert error notification title is displayed") {
|
||||
onSwapTokenScreen { errorNotificationTitle.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert error notification text is displayed") {
|
||||
onSwapTokenScreen { errorNotificationText.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Refresh' button is displayed") {
|
||||
onSwapTokenScreen { refreshButton.assertIsDisplayed() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ApiEnv(
|
||||
ApiEnvConfig(ApiConfig.ID.Express, ApiEnvironment.PROD)
|
||||
)
|
||||
@AllureId("3546")
|
||||
@DisplayName("Swap: change network fee")
|
||||
@Test
|
||||
fun changeNetworkFeeTest() {
|
||||
val inputAmount = "100"
|
||||
setupHooks().run {
|
||||
val tokenTitle = "Polygon"
|
||||
val balance = TOTAL_BALANCE
|
||||
|
||||
step("Open 'Main Screen'") {
|
||||
scenario(OpenMainScreenScenario(composeTestRule))
|
||||
}
|
||||
step("Click on 'Synchronize addresses' button") {
|
||||
onMainScreen { synchronizeAddressesButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert wallet balance = $balance") {
|
||||
onMainScreen { walletBalance().assertTextContains(balance) }
|
||||
}
|
||||
step("Click on token with name: '$tokenTitle'") {
|
||||
onMainScreen { tokenWithTitleAndAddress(tokenTitle).clickWithAssertion() }
|
||||
}
|
||||
step("Click on token with name: '$tokenTitle'") {
|
||||
onTokenDetailsScreen { title.assertIsDisplayed() }
|
||||
}
|
||||
step("Click on 'Swap' button") {
|
||||
onTokenDetailsScreen { swapButton.performClick() }
|
||||
}
|
||||
step("Close 'Stories' screen") {
|
||||
onSwapStoriesScreen { closeButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Swap' screen title is displayed") {
|
||||
onSwapTokenScreen { title.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Swap tokens on screen' button is displayed") {
|
||||
onSwapTokenScreen {
|
||||
flakySafely(WAIT_UNTIL_TIMEOUT) {
|
||||
swapTokensOnscreenButton.assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
}
|
||||
step("Assert receive amount is displayed") {
|
||||
onSwapTokenScreen {
|
||||
flakySafely(WAIT_UNTIL_TIMEOUT) {
|
||||
receiveAmount.assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
}
|
||||
step("Input swap amount = '$inputAmount'") {
|
||||
composeTestRule.waitForIdle()
|
||||
onSwapTokenScreen {
|
||||
textInput.clickWithAssertion()
|
||||
textInput.performTextReplacement(inputAmount)
|
||||
}
|
||||
}
|
||||
step("Assert input amount = '$inputAmount'") {
|
||||
onSwapTokenScreen { textInput.assertTextEquals(inputAmount) }
|
||||
}
|
||||
step("Click on 'Network fee' block") {
|
||||
onSwapTokenScreen {
|
||||
flakySafely(WAIT_UNTIL_TIMEOUT) {
|
||||
networkFeeBlock.clickWithAssertion()
|
||||
}
|
||||
}
|
||||
}
|
||||
step("Assert 'Select fee' bottom sheet title is displayed") {
|
||||
onSelectNetworkFeeBottomSheet { title.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Market' item is displayed") {
|
||||
onSelectNetworkFeeBottomSheet { marketSelectorItem.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Fast' item is displayed") {
|
||||
onSelectNetworkFeeBottomSheet { fastSelectorItem.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Read more' text block is displayed") {
|
||||
onSelectNetworkFeeBottomSheet { readMoreTextBlock.assertIsDisplayed() }
|
||||
}
|
||||
step("Click on 'Fast' item") {
|
||||
onSelectNetworkFeeBottomSheet { fastSelectorItem.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Network fee' block is displayed") {
|
||||
onSwapTokenScreen {
|
||||
flakySafely(WAIT_UNTIL_TIMEOUT) {
|
||||
networkFeeBlock.assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
package com.tangem.tests
|
||||
|
||||
import androidx.test.InstrumentationRegistry.getTargetContext
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.common.extensions.swipeUp
|
||||
import com.tangem.screens.onDisclaimerScreen
|
||||
import com.tangem.screens.onStoriesScreen
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import io.qameta.allure.kotlin.AllureId
|
||||
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||
import org.junit.Test
|
||||
|
||||
@HiltAndroidTest
|
||||
class TermsOfServiceTest : BaseTestCase() {
|
||||
|
||||
@AllureId("3573")
|
||||
@DisplayName("ToS: success acceptance")
|
||||
@Test
|
||||
fun validateTermsOfServiceScreenTest() {
|
||||
setupHooks().run {
|
||||
val tosUrl = "https://tangem.com/tangem_tos.html"
|
||||
step("Assert title of 'Disclaimer screen' is displayed") {
|
||||
onDisclaimerScreen { title.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert title of 'Disclaimer screen' is displayed") {
|
||||
onDisclaimerScreen { webView.assertIsDisplayed() }
|
||||
}
|
||||
step("Verify WebView loads correct URL") {
|
||||
onDisclaimerScreen {
|
||||
webView.assertContentDescriptionContains(value = tosUrl, substring = true)
|
||||
}
|
||||
}
|
||||
step("Click on 'Accept' button") {
|
||||
onDisclaimerScreen { acceptButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Stories' screen is opened") {
|
||||
onStoriesScreen {
|
||||
scanButton.assertIsDisplayed()
|
||||
orderButton.assertIsDisplayed()}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("3574")
|
||||
@DisplayName("ToS: accept after app restart")
|
||||
@Test
|
||||
fun acceptTermsOfServiceAfterAppRestart() {
|
||||
val packageName = getTargetContext().packageName
|
||||
setupHooks().run {
|
||||
val tosUrl = "https://tangem.com/tangem_tos.html"
|
||||
step("Assert title of 'Disclaimer screen' is displayed") {
|
||||
onDisclaimerScreen { title.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert WebView of 'Disclaimer screen' is displayed") {
|
||||
onDisclaimerScreen { webView.assertIsDisplayed() }
|
||||
}
|
||||
step("Verify WebView loads correct URL") {
|
||||
onDisclaimerScreen {
|
||||
webView.assertContentDescriptionContains(value = tosUrl, substring = true)
|
||||
}
|
||||
}
|
||||
step("'Accept' button is displayed") {
|
||||
onDisclaimerScreen { acceptButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Open recent apps") {
|
||||
device.uiDevice.pressRecentApps()
|
||||
}
|
||||
step("Stop app by swipe") {
|
||||
swipeUp(startHeightRatio = 0.5f)
|
||||
}
|
||||
step("Launch app") {
|
||||
device.apps.launch(packageName)
|
||||
}
|
||||
step("Assert title of 'Disclaimer screen' is displayed") {
|
||||
onDisclaimerScreen { title.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert WebView of 'Disclaimer screen' is displayed") {
|
||||
onDisclaimerScreen { webView.assertIsDisplayed() }
|
||||
}
|
||||
step("Verify WebView loads correct URL") {
|
||||
onDisclaimerScreen {
|
||||
webView.assertContentDescriptionContains(value = tosUrl, substring = true)
|
||||
}
|
||||
}
|
||||
step("Click on 'Accept' button") {
|
||||
onDisclaimerScreen { acceptButton.clickWithAssertion() }
|
||||
}
|
||||
step("Open recent apps") {
|
||||
device.uiDevice.pressRecentApps()
|
||||
}
|
||||
step("Stop app by swipe") {
|
||||
swipeUp(startHeightRatio = 0.5f)
|
||||
}
|
||||
step("Launch app") {
|
||||
device.apps.launch(packageName)
|
||||
}
|
||||
step("Assert 'Stories' screen is opened") {
|
||||
onStoriesScreen {
|
||||
scanButton.assertIsDisplayed()
|
||||
orderButton.assertIsDisplayed()}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -28,6 +28,7 @@ import com.tangem.domain.apptheme.repository.AppThemeModeRepository
|
|||
import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository
|
||||
import com.tangem.domain.card.ScanCardProcessor
|
||||
import com.tangem.domain.card.repository.CardRepository
|
||||
import com.tangem.domain.core.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.feedback.GetCardInfoUseCase
|
||||
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
|
||||
import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase
|
||||
|
|
@ -38,7 +39,9 @@ import com.tangem.domain.walletmanager.WalletManagersFacade
|
|||
import com.tangem.domain.wallets.builder.ColdUserWalletBuilder
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
||||
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
|
||||
import com.tangem.hot.sdk.TangemHotSdk
|
||||
import com.tangem.tap.common.analytics.handlers.BlockchainExceptionHandler
|
||||
import com.tangem.tap.common.log.TangemAppLoggerInitializer
|
||||
import com.tangem.tap.domain.scanCard.CardScanningFeatureToggles
|
||||
|
|
@ -142,4 +145,10 @@ interface ApplicationEntryPoint {
|
|||
fun getApiConfigsManager(): ApiConfigsManager
|
||||
|
||||
fun getUserTokensResponseStore(): UserTokensResponseStore
|
||||
|
||||
fun getUserWalletsListRepository(): UserWalletsListRepository
|
||||
|
||||
fun getTangemHotSdk(): TangemHotSdk
|
||||
|
||||
fun getHotWalletFeatureToggles(): HotWalletFeatureToggles
|
||||
}
|
||||
|
|
@ -7,6 +7,8 @@ import androidx.work.WorkerParameters
|
|||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.legacy.asLockable
|
||||
import com.tangem.domain.core.wallets.UserWalletsListRepository
|
||||
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import timber.log.Timber
|
||||
|
|
@ -17,13 +19,22 @@ class LockTimerWorker @AssistedInject constructor(
|
|||
@Assisted params: WorkerParameters,
|
||||
private val settingsRepository: SettingsRepository,
|
||||
private val userWalletsListManager: UserWalletsListManager,
|
||||
private val userWalletsListRepository: UserWalletsListRepository,
|
||||
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
) : CoroutineWorker(context, params) {
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
Timber.i("onStart job")
|
||||
val userWalletsListManagerLockable = userWalletsListManager.asLockable() ?: return Result.failure()
|
||||
userWalletsListManagerLockable.lock()
|
||||
settingsRepository.setShouldOpenWelcomeScreenOnResume(value = true)
|
||||
if (hotWalletFeatureToggles.isHotWalletEnabled) {
|
||||
userWalletsListRepository.lockAllWallets()
|
||||
.onRight {
|
||||
settingsRepository.setShouldOpenWelcomeScreenOnResume(value = true)
|
||||
}
|
||||
} else {
|
||||
val userWalletsListManagerLockable = userWalletsListManager.asLockable() ?: return Result.failure()
|
||||
userWalletsListManagerLockable.lock()
|
||||
settingsRepository.setShouldOpenWelcomeScreenOnResume(value = true)
|
||||
}
|
||||
Timber.i("onStart job complete")
|
||||
return Result.success()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ import com.tangem.common.routing.AppRoute
|
|||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.legacy.asLockable
|
||||
import com.tangem.domain.core.wallets.UserWalletsListRepository
|
||||
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
||||
import com.tangem.tap.LockTimerWorker.Companion.TAG
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
|
@ -25,6 +27,8 @@ internal class LockUserWalletsTimer(
|
|||
private val settingsRepository: SettingsRepository,
|
||||
private val duration: Duration = with(Duration) { 5.minutes },
|
||||
private val userWalletsListManager: UserWalletsListManager,
|
||||
private val userWalletsListRepository: UserWalletsListRepository,
|
||||
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
private val coroutineScope: CoroutineScope,
|
||||
) : LifecycleOwner by context as LifecycleOwner,
|
||||
DefaultLifecycleObserver {
|
||||
|
|
@ -108,20 +112,33 @@ internal class LockUserWalletsTimer(
|
|||
|
||||
delay(duration)
|
||||
|
||||
val userWalletsListManager = userWalletsListManager.asLockable() ?: return@launch
|
||||
if (hotWalletFeatureToggles.isHotWalletEnabled) {
|
||||
val userWallets = userWalletsListRepository.userWalletsSync()
|
||||
if (userWallets.isNotEmpty()) {
|
||||
userWalletsListRepository.lockAllWallets()
|
||||
.onLeft {
|
||||
start()
|
||||
}
|
||||
.onRight {
|
||||
store.dispatchNavigationAction { replaceAll(AppRoute.Welcome()) }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val userWalletsListManager = userWalletsListManager.asLockable() ?: return@launch
|
||||
|
||||
if (userWalletsListManager.hasUserWallets) {
|
||||
val currentTime = System.currentTimeMillis()
|
||||
if (userWalletsListManager.hasUserWallets) {
|
||||
val currentTime = System.currentTimeMillis()
|
||||
|
||||
Timber.i(
|
||||
"""
|
||||
Timber.i(
|
||||
"""
|
||||
Finished
|
||||
|- Millis passed: ${currentTime - startTime}
|
||||
""".trimIndent(),
|
||||
)
|
||||
""".trimIndent(),
|
||||
)
|
||||
|
||||
userWalletsListManager.lock()
|
||||
store.dispatchNavigationAction { replaceAll(AppRoute.Welcome()) }
|
||||
userWalletsListManager.lock()
|
||||
store.dispatchNavigationAction { replaceAll(AppRoute.Welcome()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import android.content.pm.ActivityInfo
|
|||
import android.content.res.Configuration
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.KeyEvent
|
||||
import android.view.MotionEvent
|
||||
import android.view.WindowManager
|
||||
import androidx.activity.SystemBarStyle
|
||||
|
|
@ -39,6 +40,7 @@ import com.tangem.domain.apptheme.model.AppThemeMode
|
|||
import com.tangem.domain.card.ScanCardUseCase
|
||||
import com.tangem.domain.card.repository.CardRepository
|
||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||
import com.tangem.domain.models.wallet.isLocked
|
||||
import com.tangem.domain.settings.SetGooglePayAvailabilityUseCase
|
||||
import com.tangem.domain.settings.SetGoogleServicesAvailabilityUseCase
|
||||
import com.tangem.domain.settings.ShouldInitiallyAskPermissionUseCase
|
||||
|
|
@ -47,7 +49,10 @@ import com.tangem.domain.staking.SendUnsubmittedHashesUseCase
|
|||
import com.tangem.domain.tokens.GetPolkadotCheckHasImmortalUseCase
|
||||
import com.tangem.domain.tokens.GetPolkadotCheckHasResetUseCase
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.core.wallets.UserWalletsListRepository
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent
|
||||
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
||||
import com.tangem.features.tangempay.TangemPayFeatureToggles
|
||||
import com.tangem.features.tester.api.TesterMenuLauncher
|
||||
import com.tangem.features.walletconnect.components.WalletConnectFeatureToggles
|
||||
import com.tangem.google.GoogleServicesHelper
|
||||
|
|
@ -175,13 +180,31 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
|
|||
@Inject
|
||||
internal lateinit var testerMenuLauncher: TesterMenuLauncher
|
||||
|
||||
@Inject
|
||||
internal lateinit var intentProcessor: IntentProcessor
|
||||
|
||||
@Inject
|
||||
internal lateinit var walletConnectLinkIntentHandler: WalletConnectLinkIntentHandler
|
||||
|
||||
@Inject
|
||||
internal lateinit var onPushClickedIntentHandler: OnPushClickedIntentHandler
|
||||
|
||||
@Inject
|
||||
internal lateinit var backgroundScanIntentHandler: BackgroundScanIntentHandler
|
||||
|
||||
@Inject
|
||||
internal lateinit var userWalletsListRepository: UserWalletsListRepository
|
||||
|
||||
@Inject
|
||||
internal lateinit var hotWalletFeatureToggles: HotWalletFeatureToggles
|
||||
|
||||
@Inject
|
||||
internal lateinit var tangemPayFeatureToggles: TangemPayFeatureToggles
|
||||
|
||||
internal val viewModel: MainViewModel by viewModels()
|
||||
|
||||
private lateinit var appThemeModeFlow: SharedFlow<AppThemeMode>
|
||||
|
||||
// TODO: fixme: inject through DI
|
||||
private val intentProcessor: IntentProcessor = IntentProcessor()
|
||||
|
||||
private val dialogManager = DialogManager()
|
||||
|
||||
private val onActivityResultCallbacks = mutableListOf<OnActivityResultCallback>()
|
||||
|
|
@ -231,7 +254,7 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
|
|||
lifecycle.addObserver(defaultDeviceFlipDetector)
|
||||
|
||||
if (BuildConfig.TESTER_MENU_ENABLED) {
|
||||
lifecycle.addObserver(testerMenuLauncher.launchOnShakeObserver)
|
||||
lifecycle.addObserver(testerMenuLauncher.launchOnKeyEventObserver)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -261,6 +284,8 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
|
|||
settingsRepository = settingsRepository,
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
coroutineScope = mainScope,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
hotWalletFeatureToggles = hotWalletFeatureToggles,
|
||||
)
|
||||
|
||||
initIntentHandlers()
|
||||
|
|
@ -343,12 +368,10 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
|
|||
}
|
||||
|
||||
private fun initIntentHandlers() {
|
||||
val hasSavedWalletsProvider = { userWalletsListManager.hasUserWallets }
|
||||
intentProcessor.addHandler(OnPushClickedIntentHandler(analyticsEventsHandler))
|
||||
intentProcessor.addHandler(BackgroundScanIntentHandler(hasSavedWalletsProvider, lifecycleScope))
|
||||
intentProcessor.addHandler(onPushClickedIntentHandler)
|
||||
|
||||
if (!walletConnectFeatureToggles.isRedesignedWalletConnectEnabled) {
|
||||
intentProcessor.addHandler(WalletConnectLinkIntentHandler())
|
||||
intentProcessor.addHandler(walletConnectLinkIntentHandler)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -409,11 +432,24 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
|
|||
|
||||
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
|
||||
val result = WindowObscurationObserver.dispatchTouchEvent(event, analyticsEventsHandler)
|
||||
|
||||
return if (result) super.dispatchTouchEvent(event) else false
|
||||
}
|
||||
|
||||
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
||||
return if (BuildConfig.TESTER_MENU_ENABLED) {
|
||||
testerMenuLauncher.launchOnKeyEventObserver.dispatchKeyEvent(event) || super.dispatchKeyEvent(event)
|
||||
} else {
|
||||
super.dispatchKeyEvent(event)
|
||||
}
|
||||
}
|
||||
|
||||
private fun navigateToInitialScreenIfNeeded(intentWhichStartedActivity: Intent?) {
|
||||
// TODO refactor this method to return a route instead of navigating directly
|
||||
if (hotWalletFeatureToggles.isHotWalletEnabled) {
|
||||
navigateToInitialScreenIfNeededNew(intentWhichStartedActivity)
|
||||
return
|
||||
}
|
||||
|
||||
val backStack = appRouterConfig.stack ?: emptyList()
|
||||
// TODO move inital navigation to navigation component ([REDACTED_JIRA])
|
||||
val isOnlyInitialRoute = backStack.all { it is AppRoute.Initial }
|
||||
|
|
@ -433,10 +469,73 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
|
|||
}
|
||||
}
|
||||
|
||||
@Deprecated("Refactor this method to return a route instead of navigating directly")
|
||||
private fun navigateToInitialScreenIfNeededNew(intentWhichStartedActivity: Intent?) {
|
||||
lifecycleScope.launch {
|
||||
val userWallets = userWalletsListRepository.userWalletsSync()
|
||||
val launchMode = backgroundScanIntentHandler.getInitScreenLaunchMode(intentWhichStartedActivity)
|
||||
if (userWallets.isEmpty()) {
|
||||
val shouldShowTos = !cardRepository.isTangemTOSAccepted()
|
||||
|
||||
val route = if (shouldShowTos) {
|
||||
AppRoute.Disclaimer(isTosAccepted = false)
|
||||
} else {
|
||||
AppRoute.Home(launchMode = launchMode)
|
||||
}
|
||||
|
||||
store.dispatchNavigationAction { replaceAll(route) }
|
||||
intentProcessor.handleIntent(
|
||||
intent = intentWhichStartedActivity,
|
||||
isFromForeground = false,
|
||||
skipNavigationHandlers = false,
|
||||
)
|
||||
} else {
|
||||
if (userWallets.any { it.isLocked }) {
|
||||
store.dispatchNavigationAction {
|
||||
replaceAll(
|
||||
AppRoute.Welcome(
|
||||
launchMode = launchMode,
|
||||
intent = intentWhichStartedActivity?.let(::SerializableIntent),
|
||||
),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
store.dispatchNavigationAction {
|
||||
replaceAll(AppRoute.Wallet)
|
||||
}
|
||||
}
|
||||
|
||||
intentProcessor.handleIntent(
|
||||
intent = intentWhichStartedActivity,
|
||||
isFromForeground = false,
|
||||
skipNavigationHandlers = true,
|
||||
)
|
||||
}
|
||||
|
||||
if (intent != null) {
|
||||
handleDeepLink(intent = intent, isFromOnNewIntent = false)
|
||||
}
|
||||
|
||||
viewModel.checkForUnfinishedBackup()
|
||||
}
|
||||
}
|
||||
|
||||
private fun navigateToInitialScreen(intentWhichStartedActivity: Intent?) {
|
||||
if (userWalletsListManager.isLockable && userWalletsListManager.hasUserWallets) {
|
||||
val launchMode = backgroundScanIntentHandler.getInitScreenLaunchMode(intentWhichStartedActivity)
|
||||
|
||||
// Workaround to navigate to TangemPayDetails screen. Will be deleted in next PRs
|
||||
if (tangemPayFeatureToggles.isTangemPayEnabled) {
|
||||
store.dispatchNavigationAction {
|
||||
replaceAll(AppRoute.Welcome(intentWhichStartedActivity?.let(::SerializableIntent)))
|
||||
replaceAll(AppRoute.TangemPayDetails)
|
||||
}
|
||||
} else if (userWalletsListManager.isLockable && userWalletsListManager.hasUserWallets) {
|
||||
store.dispatchNavigationAction {
|
||||
replaceAll(
|
||||
AppRoute.Welcome(
|
||||
launchMode = launchMode,
|
||||
intent = intentWhichStartedActivity?.let(::SerializableIntent),
|
||||
),
|
||||
)
|
||||
}
|
||||
intentProcessor.handleIntent(
|
||||
intent = intentWhichStartedActivity,
|
||||
|
|
@ -450,7 +549,7 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
|
|||
val route = if (shouldShowTos) {
|
||||
AppRoute.Disclaimer(isTosAccepted = false)
|
||||
} else {
|
||||
AppRoute.Home
|
||||
AppRoute.Home(launchMode = launchMode)
|
||||
}
|
||||
|
||||
store.dispatchNavigationAction { replaceAll(route) }
|
||||
|
|
|
|||
|
|
@ -227,6 +227,15 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
|
|||
private val userTokensResponseStore: UserTokensResponseStore
|
||||
get() = entryPoint.getUserTokensResponseStore()
|
||||
|
||||
private val userWalletsListRepository
|
||||
get() = entryPoint.getUserWalletsListRepository()
|
||||
|
||||
private val tangemHotSdk
|
||||
get() = entryPoint.getTangemHotSdk()
|
||||
|
||||
private val hotWalletFeatureToggles
|
||||
get() = entryPoint.getHotWalletFeatureToggles()
|
||||
|
||||
// endregion
|
||||
|
||||
private val appScope = MainScope()
|
||||
|
|
@ -364,6 +373,9 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
|
|||
uiMessageSender = uiMessageSender,
|
||||
coldUserWalletBuilderFactory = coldUserWalletBuilderFactory,
|
||||
userTokensResponseStore = userTokensResponseStore,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
tangemHotSdk = tangemHotSdk,
|
||||
hotWalletFeatureToggles = hotWalletFeatureToggles,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,18 +0,0 @@
|
|||
package com.tangem.tap.common.analytics.events
|
||||
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed class IntroductionProcess(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : AnalyticsEvent("Introduction Process", event, params) {
|
||||
|
||||
class ScreenOpened : IntroductionProcess("Introduction Process Screen Opened")
|
||||
class ButtonTokensList : IntroductionProcess("Button - Tokens List")
|
||||
class ButtonBuyCards : IntroductionProcess("Button - Buy Cards")
|
||||
class ButtonScanCard : IntroductionProcess("Button - Scan Card")
|
||||
class ButtonRequestSupport : IntroductionProcess("Button - Request Support")
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
package com.tangem.tap.common.analytics.events
|
||||
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
import com.tangem.tap.common.extensions.filterNotNull
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed class Shop(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : AnalyticsEvent("Shop", event, params) {
|
||||
|
||||
class ScreenOpened : Shop("Shop Screen Opened")
|
||||
|
||||
class Purchased(sku: String, count: String, amount: String, couponCode: String?) : Shop(
|
||||
event = "Purchased",
|
||||
params = mapOf(
|
||||
"SKU" to sku,
|
||||
"Count" to count,
|
||||
"Amount" to amount,
|
||||
"Coupon Code" to couponCode,
|
||||
).filterNotNull(),
|
||||
)
|
||||
|
||||
class Redirected(partnerName: String?) : Shop(
|
||||
event = "Redirected",
|
||||
params = partnerName?.let { mapOf("Partner" to partnerName) } ?: mapOf(),
|
||||
)
|
||||
}
|
||||
|
|
@ -2,13 +2,13 @@ package com.tangem.tap.common.analytics.paramsInterceptor
|
|||
|
||||
import com.tangem.core.analytics.api.ParamsInterceptor
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
import com.tangem.domain.card.analytics.IntroductionProcess
|
||||
import com.tangem.domain.card.analytics.ParamCardCurrencyConverter
|
||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.scan.ProductType
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
||||
import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.IntroductionProcess
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
|
|
|
|||
|
|
@ -1,17 +0,0 @@
|
|||
package com.tangem.tap.common.compose.extensions
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
fun Dp.toPx(): Float {
|
||||
val currentDp = this
|
||||
return with(LocalDensity.current) { currentDp.toPx() }
|
||||
}
|
||||
|
||||
fun DpSize.halfHeight(): Dp = this.height / 2
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
package com.tangem.tap.common.compose.extensions
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.sdk.extensions.pxToDp
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
fun Painter.dpSize(): DpSize = DpSize(
|
||||
intrinsicSize.width.pxToDp().dp,
|
||||
intrinsicSize.height.pxToDp().dp,
|
||||
)
|
||||
|
||||
@Composable
|
||||
private fun Float.pxToDp(): Float = LocalContext.current.pxToDp(this)
|
||||
|
|
@ -24,7 +24,7 @@ fun Analytics.setContext(scanResponse: ScanResponse) {
|
|||
|
||||
fun Analytics.setContext(userWallet: UserWallet) {
|
||||
setUserId(userWallet.walletId.stringValue)
|
||||
// TODO add product type for hot ([REDACTED_TASK_KEY])
|
||||
// TODO add product type for hot ([REDACTED_TASK_KEY] [Hot Wallet] Analytics)
|
||||
|
||||
if (userWallet is UserWallet.Cold) {
|
||||
addParamsInterceptor(LinkedCardContextInterceptor(userWallet.scanResponse))
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
package com.tangem.tap.common.extensions
|
||||
|
||||
fun Int.isEven() = this and 1 == 0
|
||||
|
|
@ -3,7 +3,6 @@ package com.tangem.tap.common.redux
|
|||
import com.tangem.tap.common.redux.global.globalReducer
|
||||
import com.tangem.tap.features.details.redux.DetailsReducer
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectReducer
|
||||
import com.tangem.tap.features.home.redux.HomeReducer
|
||||
import com.tangem.tap.features.welcome.redux.WelcomeReducer
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphReducer
|
||||
import org.rekotlin.Action
|
||||
|
|
@ -14,7 +13,6 @@ fun appReducer(action: Action, state: AppState?): AppState {
|
|||
|
||||
return AppState(
|
||||
globalState = globalReducer(action, state),
|
||||
homeState = HomeReducer.reduce(action, state),
|
||||
detailsState = DetailsReducer.reduce(action, state),
|
||||
walletConnectState = WalletConnectReducer.reduce(action, state.walletConnectState),
|
||||
welcomeState = WelcomeReducer.reduce(action, state),
|
||||
|
|
|
|||
|
|
@ -7,8 +7,6 @@ import com.tangem.tap.features.details.redux.DetailsMiddleware
|
|||
import com.tangem.tap.features.details.redux.DetailsState
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectMiddleware
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectState
|
||||
import com.tangem.tap.features.home.redux.HomeMiddleware
|
||||
import com.tangem.tap.features.home.redux.HomeState
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupMiddleware
|
||||
import com.tangem.tap.features.wallet.redux.middlewares.TradeCryptoMiddleware
|
||||
import com.tangem.tap.features.welcome.redux.WelcomeMiddleware
|
||||
|
|
@ -20,7 +18,6 @@ import org.rekotlin.StateType
|
|||
|
||||
data class AppState(
|
||||
val globalState: GlobalState = GlobalState(),
|
||||
val homeState: HomeState = HomeState(),
|
||||
val detailsState: DetailsState = DetailsState(),
|
||||
val walletConnectState: WalletConnectState = WalletConnectState(),
|
||||
val welcomeState: WelcomeState = WelcomeState(),
|
||||
|
|
@ -32,7 +29,6 @@ data class AppState(
|
|||
return listOf(
|
||||
logMiddleware,
|
||||
GlobalMiddleware.handler,
|
||||
HomeMiddleware.handler,
|
||||
DetailsMiddleware().detailsMiddleware,
|
||||
WalletConnectMiddleware().walletConnectMiddleware,
|
||||
BackupMiddleware().backupMiddleware,
|
||||
|
|
|
|||
|
|
@ -26,10 +26,9 @@ internal object LegacyMiddleware {
|
|||
{ action ->
|
||||
when (action) {
|
||||
is LegacyAction.PrepareDetailsScreen -> {
|
||||
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
|
||||
val walletsRepository = store.inject(DaggerGraphState::walletsRepository)
|
||||
|
||||
userWalletsListManager.selectedUserWallet
|
||||
selectedUserWallet()
|
||||
.distinctUntilChanged()
|
||||
.onEach { selectedUserWallet ->
|
||||
val initializedAppSettingsStateContent = initializeAppSettingsState(
|
||||
|
|
@ -52,6 +51,16 @@ internal object LegacyMiddleware {
|
|||
}
|
||||
}
|
||||
|
||||
private fun selectedUserWallet(): Flow<UserWallet> {
|
||||
val hotWalletFeatureToggles = store.inject(DaggerGraphState::hotWalletFeatureToggles)
|
||||
return if (hotWalletFeatureToggles.isHotWalletEnabled) {
|
||||
store.inject(DaggerGraphState::userWalletsListRepository).selectedUserWallet.filterNotNull()
|
||||
} else {
|
||||
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
|
||||
userWalletsListManager.selectedUserWallet
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* LEGACY: We need to initialize [AppSettingsState] async to avoid drawing blocking
|
||||
* previously it was initialized in runBlocking and blocked details screen
|
||||
|
|
@ -64,6 +73,8 @@ internal object LegacyMiddleware {
|
|||
selectedAppCurrency = store.state.globalState.appCurrency,
|
||||
selectedThemeMode = store.inject(DaggerGraphState::appThemeModeRepository).getAppThemeMode().firstOrNull()
|
||||
?: AppThemeMode.DEFAULT,
|
||||
requireAccessCode = store.inject(DaggerGraphState::walletsRepository).requireAccessCode(),
|
||||
useBiometricAuthentication = store.inject(DaggerGraphState::walletsRepository).useBiometricAuthentication(),
|
||||
isHidingEnabled = store.inject(DaggerGraphState::balanceHidingRepository)
|
||||
.getBalanceHidingSettings().isHidingEnabledInSettings,
|
||||
needEnrollBiometrics = runCatching(tangemSdkManager::needEnrollBiometrics).getOrNull() == true,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import android.content.Context
|
|||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.compose.ui.text.intl.Locale
|
||||
import androidx.core.view.isVisible
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
|
|
@ -14,8 +15,6 @@ import com.tangem.tap.common.analytics.events.ScanFailsDialogAnalytics
|
|||
import com.tangem.tap.common.extensions.dispatchDialogHide
|
||||
import com.tangem.tap.common.extensions.dispatchOpenUrl
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.features.home.LocaleRegionProvider
|
||||
import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
|
|
@ -29,6 +28,7 @@ internal object ScanFailsDialog {
|
|||
|
||||
private const val HOW_TO_SCAN_RU_LINK = "https://tangem.com/ru/blog/post/scan-tangem-card/"
|
||||
private const val HOW_TO_SCAN_LINK = "https://tangem.com/en/blog/post/scan-tangem-card/"
|
||||
private const val RUSSIA_LOCALE = "ru"
|
||||
|
||||
fun create(context: Context, source: StateDialog.ScanFailsSource, onTryAgain: (() -> Unit)? = null): AlertDialog {
|
||||
return AlertDialog.Builder(context, R.style.CustomMaterialDialog).apply {
|
||||
|
|
@ -62,8 +62,8 @@ internal object ScanFailsDialog {
|
|||
source = sourceAnalytics,
|
||||
),
|
||||
)
|
||||
val locale = LocaleRegionProvider().getRegion()
|
||||
val link = if (locale.lowercase() == RUSSIA_COUNTRY_CODE) HOW_TO_SCAN_RU_LINK else HOW_TO_SCAN_LINK
|
||||
val locale = Locale.current.region
|
||||
val link = if (locale.lowercase() == RUSSIA_LOCALE) HOW_TO_SCAN_RU_LINK else HOW_TO_SCAN_LINK
|
||||
store.dispatchOpenUrl(link)
|
||||
}
|
||||
customView.findViewById<TextView>(R.id.request_support_button)?.setOnClickListener {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import com.tangem.domain.models.wallet.UserWallet
|
|||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
|
||||
// FIXME: Workaround, remove it once the normal UserWalletsStore has been implemented
|
||||
// [REDACTED_JIRA]
|
||||
|
|
@ -28,10 +27,6 @@ internal class RuntimeUserWalletsStore(
|
|||
return requireNotNull(getSyncOrNull(key)) { "Unable to find user wallet with provided ID: $key" }
|
||||
}
|
||||
|
||||
override suspend fun getAllSyncOrNull(): List<UserWallet>? {
|
||||
return userWalletsListManager.userWallets.firstOrNull()
|
||||
}
|
||||
|
||||
override suspend fun update(
|
||||
userWalletId: UserWalletId,
|
||||
update: suspend (UserWallet) -> UserWallet,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
package com.tangem.tap.data
|
||||
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.catching
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.core.wallets.UserWalletsListRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
|
||||
class UserWalletsStoreRepositoryProxy(
|
||||
private val userWalletsListRepository: UserWalletsListRepository,
|
||||
) : UserWalletsStore {
|
||||
|
||||
override val selectedUserWalletOrNull: UserWallet?
|
||||
get() = userWalletsListRepository.selectedUserWallet.value
|
||||
|
||||
override val userWallets: Flow<List<UserWallet>>
|
||||
get() = flow {
|
||||
userWalletsListRepository.load()
|
||||
userWalletsListRepository.userWallets.collect {
|
||||
emit(requireNotNull(it))
|
||||
}
|
||||
}
|
||||
|
||||
override fun getSyncOrNull(key: UserWalletId): UserWallet? {
|
||||
return userWalletsListRepository.userWallets.value?.find { it.walletId == key }
|
||||
}
|
||||
|
||||
override fun getSyncStrict(key: UserWalletId): UserWallet {
|
||||
return requireNotNull(getSyncOrNull(key)) { "Unable to find user wallet with provided ID: $key" }
|
||||
}
|
||||
|
||||
override suspend fun update(
|
||||
userWalletId: UserWalletId,
|
||||
update: suspend (UserWallet) -> UserWallet,
|
||||
): CompletionResult<UserWallet> {
|
||||
return catching {
|
||||
val userWallet = userWalletsListRepository.userWallets.value?.find { it.walletId == userWalletId }
|
||||
requireNotNull(userWallet) { "Unable to find user wallet with provided ID: $userWalletId" }
|
||||
val updatedUserWallet = update(userWallet)
|
||||
userWalletsListRepository.saveWithoutLock(
|
||||
userWallet = updatedUserWallet,
|
||||
canOverride = true,
|
||||
)
|
||||
updatedUserWallet
|
||||
}
|
||||
}
|
||||
}
|
||||
34
app/src/main/java/com/tangem/tap/di/IntentHandlingModule.kt
Normal file
34
app/src/main/java/com/tangem/tap/di/IntentHandlingModule.kt
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
package com.tangem.tap.di
|
||||
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.tap.features.intentHandler.IntentProcessor
|
||||
import com.tangem.tap.features.intentHandler.handlers.BackgroundScanIntentHandler
|
||||
import com.tangem.tap.features.intentHandler.handlers.OnPushClickedIntentHandler
|
||||
import com.tangem.tap.features.intentHandler.handlers.WalletConnectLinkIntentHandler
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object IntentHandlingModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideBackgroundScanIntentHandler(): BackgroundScanIntentHandler = BackgroundScanIntentHandler()
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideWalletConnectLinkIntentHandler(): WalletConnectLinkIntentHandler = WalletConnectLinkIntentHandler()
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideOnPushClickedIntentHandler(analyticsEventHandler: AnalyticsEventHandler): OnPushClickedIntentHandler =
|
||||
OnPushClickedIntentHandler(analyticsEventHandler)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideIntentProcessor(): IntentProcessor = IntentProcessor()
|
||||
}
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
package com.tangem.tap.di.data
|
||||
|
||||
import com.tangem.data.common.network.NetworkFactory
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.card.repository.DerivationsRepository
|
||||
import com.tangem.sdk.api.TangemSdkManager
|
||||
import com.tangem.tap.domain.card.DefaultDerivationsRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object CardDataModule {
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun providesDerivationsRepository(
|
||||
tangemSdkManager: TangemSdkManager,
|
||||
userWalletsStore: UserWalletsStore,
|
||||
networkFactory: NetworkFactory,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): DerivationsRepository {
|
||||
return DefaultDerivationsRepository(
|
||||
tangemSdkManager = tangemSdkManager,
|
||||
userWalletsStore = userWalletsStore,
|
||||
networkFactory = networkFactory,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,10 @@ package com.tangem.tap.di.data
|
|||
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.core.wallets.UserWalletsListRepository
|
||||
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
||||
import com.tangem.tap.data.RuntimeUserWalletsStore
|
||||
import com.tangem.tap.data.UserWalletsStoreRepositoryProxy
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -15,7 +18,15 @@ internal object UserWalletsStoreModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideUserWalletsStore(userWalletsListManager: UserWalletsListManager): UserWalletsStore {
|
||||
return RuntimeUserWalletsStore(userWalletsListManager = userWalletsListManager)
|
||||
fun provideUserWalletsStore(
|
||||
userWalletsListManager: UserWalletsListManager,
|
||||
userWalletsListRepository: UserWalletsListRepository,
|
||||
hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
): UserWalletsStore {
|
||||
return if (hotWalletFeatureToggles.isHotWalletEnabled) {
|
||||
UserWalletsStoreRepositoryProxy(userWalletsListRepository)
|
||||
} else {
|
||||
RuntimeUserWalletsStore(userWalletsListManager = userWalletsListManager)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
package com.tangem.tap.di.domain
|
||||
|
||||
import com.tangem.domain.account.repository.AccountsCRUDRepository
|
||||
import com.tangem.domain.account.usecase.*
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object AccountDomainModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideAddCryptoPortfolioUseCase(accountsCRUDRepository: AccountsCRUDRepository): AddCryptoPortfolioUseCase {
|
||||
return AddCryptoPortfolioUseCase(crudRepository = accountsCRUDRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideUpdateCryptoPortfolioUseCase(
|
||||
accountsCRUDRepository: AccountsCRUDRepository,
|
||||
): UpdateCryptoPortfolioUseCase {
|
||||
return UpdateCryptoPortfolioUseCase(crudRepository = accountsCRUDRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideArchiveCryptoPortfolioUseCase(
|
||||
accountsCRUDRepository: AccountsCRUDRepository,
|
||||
): ArchiveCryptoPortfolioUseCase {
|
||||
return ArchiveCryptoPortfolioUseCase(crudRepository = accountsCRUDRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideRecoverCryptoPortfolioUseCase(
|
||||
accountsCRUDRepository: AccountsCRUDRepository,
|
||||
): RecoverCryptoPortfolioUseCase {
|
||||
return RecoverCryptoPortfolioUseCase(crudRepository = accountsCRUDRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetUnoccupiedAccountIndexUseCase(
|
||||
accountsCRUDRepository: AccountsCRUDRepository,
|
||||
): GetUnoccupiedAccountIndexUseCase {
|
||||
return GetUnoccupiedAccountIndexUseCase(crudRepository = accountsCRUDRepository)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,13 +2,18 @@ package com.tangem.tap.di.domain
|
|||
|
||||
import com.tangem.domain.card.*
|
||||
import com.tangem.domain.card.repository.CardRepository
|
||||
import com.tangem.domain.card.repository.DerivationsRepository
|
||||
import com.tangem.domain.wallets.derivations.DerivationsRepository
|
||||
import com.tangem.domain.demo.models.DemoConfig
|
||||
import com.tangem.domain.demo.IsDemoCardUseCase
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.core.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.wallets.usecase.DerivePublicKeysUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetExtendedPublicKeyForCurrencyUseCase
|
||||
import com.tangem.domain.wallets.usecase.HasMissedDerivationsUseCase
|
||||
import com.tangem.domain.wallets.usecase.IsNeedToBackupUseCase
|
||||
import com.tangem.domain.wallets.usecase.NetworkHasDerivationUseCase
|
||||
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
||||
import com.tangem.sdk.api.TangemSdkManager
|
||||
import com.tangem.tap.domain.card.DefaultDeleteSavedAccessCodesUseCase
|
||||
import com.tangem.tap.domain.card.DefaultResetCardUseCase
|
||||
|
|
@ -39,9 +44,16 @@ internal object CardDomainModule {
|
|||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideIsNeedToBackupUseCase(userWalletsListManager: UserWalletsListManager): IsNeedToBackupUseCase {
|
||||
return IsNeedToBackupUseCase(userWalletsListManager = userWalletsListManager)
|
||||
fun provideIsNeedToBackupUseCase(
|
||||
userWalletsListManager: UserWalletsListManager,
|
||||
userWalletsListRepository: UserWalletsListRepository,
|
||||
hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
): IsNeedToBackupUseCase {
|
||||
return IsNeedToBackupUseCase(
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
useNewRepository = hotWalletFeatureToggles.isHotWalletEnabled,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@ package com.tangem.tap.di.domain
|
|||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
import com.tangem.domain.card.ScanCardProcessor
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.core.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
|
||||
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
||||
import com.tangem.tap.domain.scanCard.CardScanningFeatureToggles
|
||||
import com.tangem.tap.domain.scanCard.DefaultScanCardProcessor
|
||||
import com.tangem.tap.domain.scanCard.LegacyScanProcessor
|
||||
|
|
@ -31,7 +33,15 @@ internal object CardLegacyDomainModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesWalletNameGenerateUseCase(userWalletsListManager: UserWalletsListManager): GenerateWalletNameUseCase {
|
||||
return GenerateWalletNameUseCase(userWalletsListManager)
|
||||
fun providesWalletNameGenerateUseCase(
|
||||
userWalletsListManager: UserWalletsListManager,
|
||||
userWalletsListRepository: UserWalletsListRepository,
|
||||
hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
): GenerateWalletNameUseCase {
|
||||
return GenerateWalletNameUseCase(
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
useNewRepository = hotWalletFeatureToggles.isHotWalletEnabled,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,12 @@
|
|||
package com.tangem.tap.di.domain
|
||||
|
||||
import com.tangem.domain.card.repository.DerivationsRepository
|
||||
import com.tangem.domain.wallets.derivations.DerivationsRepository
|
||||
import com.tangem.domain.managetokens.*
|
||||
import com.tangem.domain.managetokens.repository.CustomTokensRepository
|
||||
import com.tangem.domain.managetokens.repository.ManageTokensRepository
|
||||
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
|
||||
import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher
|
||||
import com.tangem.domain.staking.StakingIdFactory
|
||||
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
|
|
@ -72,6 +73,7 @@ internal object ManageTokensDomainModule {
|
|||
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
||||
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
|
||||
stakingIdFactory: StakingIdFactory,
|
||||
): SaveManagedTokensUseCase {
|
||||
return SaveManagedTokensUseCase(
|
||||
customTokensRepository = customTokensRepository,
|
||||
|
|
@ -81,6 +83,7 @@ internal object ManageTokensDomainModule {
|
|||
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
||||
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
|
||||
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
|
||||
stakingIdFactory = stakingIdFactory,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package com.tangem.tap.di.domain
|
||||
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.domain.card.repository.DerivationsRepository
|
||||
import com.tangem.domain.wallets.derivations.DerivationsRepository
|
||||
import com.tangem.domain.markets.*
|
||||
import com.tangem.domain.markets.repositories.MarketsTokenRepository
|
||||
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
|
||||
|
|
@ -9,9 +9,12 @@ import com.tangem.domain.promo.PromoRepository
|
|||
import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher
|
||||
import com.tangem.domain.quotes.single.SingleQuoteStatusSupplier
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.staking.StakingIdFactory
|
||||
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.core.wallets.UserWalletsListRepository
|
||||
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -63,6 +66,7 @@ object MarketsDomainModule {
|
|||
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
||||
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
|
||||
stakingIdFactory: StakingIdFactory,
|
||||
): SaveMarketTokensUseCase {
|
||||
return SaveMarketTokensUseCase(
|
||||
derivationsRepository = derivationsRepository,
|
||||
|
|
@ -71,6 +75,7 @@ object MarketsDomainModule {
|
|||
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
||||
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
|
||||
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
|
||||
stakingIdFactory = stakingIdFactory,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -78,10 +83,14 @@ object MarketsDomainModule {
|
|||
@Singleton
|
||||
fun provideFilterNetworksUseCase(
|
||||
userWalletsListManager: UserWalletsListManager,
|
||||
userWalletsListRepository: UserWalletsListRepository,
|
||||
hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
excludedBlockchains: ExcludedBlockchains,
|
||||
): FilterAvailableNetworksForWalletUseCase {
|
||||
return FilterAvailableNetworksForWalletUseCase(
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
useNewRepository = hotWalletFeatureToggles.isHotWalletEnabled,
|
||||
excludedBlockchains = excludedBlockchains,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -154,4 +154,10 @@ internal object NFTDomainModule {
|
|||
): ObserveAndClearNFTCacheIfNeedUseCase {
|
||||
return ObserveAndClearNFTCacheIfNeedUseCase(nftRepository, currenciesRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetNftCurrencyUseCase(nftRepository: NFTRepository): GetNFTCurrencyUseCase {
|
||||
return GetNFTCurrencyUseCase(nftRepository)
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package com.tangem.tap.di.domain
|
|||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
import com.tangem.domain.notifications.*
|
||||
import com.tangem.domain.notifications.repository.NotificationsRepository
|
||||
import com.tangem.domain.notifications.repository.PushNotificationsRepository
|
||||
import com.tangem.domain.notifications.toggles.NotificationsFeatureToggles
|
||||
import com.tangem.tap.domain.notifications.DefaultNotificationsFeatureToggles
|
||||
import com.tangem.utils.notifications.PushNotificationsTokenProvider
|
||||
|
|
@ -18,20 +19,22 @@ internal object NotificationsDomainModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesGetApplicationIdUseCase(notificationsRepository: NotificationsRepository): GetApplicationIdUseCase {
|
||||
fun providesGetApplicationIdUseCase(
|
||||
pushNotificationsRepository: PushNotificationsRepository,
|
||||
): GetApplicationIdUseCase {
|
||||
return GetApplicationIdUseCase(
|
||||
notificationsRepository = notificationsRepository,
|
||||
pushNotificationsRepository = pushNotificationsRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesSendPushTokenUseCase(
|
||||
notificationsRepository: NotificationsRepository,
|
||||
pushNotificationsRepository: PushNotificationsRepository,
|
||||
pushNotificationsTokenProvider: PushNotificationsTokenProvider,
|
||||
): SendPushTokenUseCase {
|
||||
return SendPushTokenUseCase(
|
||||
notificationsRepository = notificationsRepository,
|
||||
pushNotificationsRepository = pushNotificationsRepository,
|
||||
pushNotificationsTokenProvider = pushNotificationsTokenProvider,
|
||||
)
|
||||
}
|
||||
|
|
@ -56,6 +59,26 @@ internal object NotificationsDomainModule {
|
|||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesShouldShowNotificationUseCase(
|
||||
notificationsRepository: NotificationsRepository,
|
||||
): ShouldShowNotificationUseCase {
|
||||
return ShouldShowNotificationUseCase(
|
||||
notificationsRepository = notificationsRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesSetShouldShowNotificationUseCase(
|
||||
notificationsRepository: NotificationsRepository,
|
||||
): SetShouldShowNotificationUseCase {
|
||||
return SetShouldShowNotificationUseCase(
|
||||
notificationsRepository = notificationsRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideNotificationsFeatureToggles(featureTogglesManager: FeatureTogglesManager): NotificationsFeatureToggles {
|
||||
|
|
@ -65,8 +88,8 @@ internal object NotificationsDomainModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun provideGetNetworksAvailableForNotifications(
|
||||
notificationsRepository: NotificationsRepository,
|
||||
pushNotificationsRepository: PushNotificationsRepository,
|
||||
): GetNetworksAvailableForNotificationsUseCase {
|
||||
return GetNetworksAvailableForNotificationsUseCase(notificationsRepository = notificationsRepository)
|
||||
return GetNetworksAvailableForNotificationsUseCase(pushNotificationsRepository = pushNotificationsRepository)
|
||||
}
|
||||
}
|
||||
|
|
@ -94,12 +94,12 @@ internal object StakingDomainModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun provideFetchStakingYieldBalanceUseCase(
|
||||
stakingErrorResolver: StakingErrorResolver,
|
||||
singleYieldBalanceFetcher: SingleYieldBalanceFetcher,
|
||||
stakingIdFactory: StakingIdFactory,
|
||||
): FetchStakingYieldBalanceUseCase {
|
||||
return FetchStakingYieldBalanceUseCase(
|
||||
stakingErrorResolver = stakingErrorResolver,
|
||||
singleYieldBalanceFetcher = singleYieldBalanceFetcher,
|
||||
stakingIdFactory = stakingIdFactory,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -173,18 +173,6 @@ internal object StakingDomainModule {
|
|||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideIsApproveNeededUseCase(
|
||||
stakingRepository: StakingRepository,
|
||||
stakingErrorResolver: StakingErrorResolver,
|
||||
): IsApproveNeededUseCase {
|
||||
return IsApproveNeededUseCase(
|
||||
stakingRepository = stakingRepository,
|
||||
stakingErrorResolver = stakingErrorResolver,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetConstructedStakingTransactionUseCase(
|
||||
|
|
@ -209,12 +197,6 @@ internal object StakingDomainModule {
|
|||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetStakingIntegrationIdUseCase(stakingRepository: StakingRepository): GetStakingIntegrationIdUseCase {
|
||||
return GetStakingIntegrationIdUseCase(stakingRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideCheckAccountInitializedUseCase(
|
||||
|
|
@ -225,9 +207,13 @@ internal object StakingDomainModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetActionRequirementAmountUseCase(
|
||||
stakingRepository: StakingRepository,
|
||||
): GetActionRequirementAmountUseCase {
|
||||
return GetActionRequirementAmountUseCase(stakingRepository)
|
||||
fun provideGetActionRequirementAmountUseCase(): GetActionRequirementAmountUseCase {
|
||||
return GetActionRequirementAmountUseCase()
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideStakingIdFactory(walletManagersFacade: WalletManagersFacade): StakingIdFactory {
|
||||
return StakingIdFactory(walletManagersFacade = walletManagersFacade)
|
||||
}
|
||||
}
|
||||
|
|
@ -11,17 +11,19 @@ import com.tangem.domain.promo.PromoRepository
|
|||
import com.tangem.domain.quotes.QuotesRepository
|
||||
import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher
|
||||
import com.tangem.domain.quotes.single.SingleQuoteStatusSupplier
|
||||
import com.tangem.domain.staking.StakingIdFactory
|
||||
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
|
||||
import com.tangem.domain.staking.multi.MultiYieldBalanceSupplier
|
||||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
import com.tangem.domain.staking.single.SingleYieldBalanceFetcher
|
||||
import com.tangem.domain.staking.single.SingleYieldBalanceSupplier
|
||||
import com.tangem.domain.tokens.*
|
||||
import com.tangem.domain.tokens.operations.BaseCurrenciesStatusesOperations
|
||||
import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations
|
||||
import com.tangem.domain.tokens.operations.CachedCurrenciesStatusesOperations
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.tokens.repository.CurrencyChecksRepository
|
||||
import com.tangem.domain.tokens.repository.PolkadotAccountHealthCheckRepository
|
||||
import com.tangem.domain.tokens.repository.TokenReceiveWarningsViewedRepository
|
||||
import com.tangem.domain.tokens.wallet.WalletBalanceFetcher
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.tap.domain.tokens.DefaultTokensFeatureToggles
|
||||
|
|
@ -46,6 +48,7 @@ internal object TokensDomainModule {
|
|||
singleYieldBalanceFetcher: SingleYieldBalanceFetcher,
|
||||
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
stakingIdFactory: StakingIdFactory,
|
||||
): AddCryptoCurrenciesUseCase {
|
||||
return AddCryptoCurrenciesUseCase(
|
||||
currenciesRepository = currenciesRepository,
|
||||
|
|
@ -54,6 +57,7 @@ internal object TokensDomainModule {
|
|||
singleYieldBalanceFetcher = singleYieldBalanceFetcher,
|
||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
stakingIdFactory = stakingIdFactory,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -64,12 +68,14 @@ internal object TokensDomainModule {
|
|||
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
||||
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
|
||||
stakingIdFactory: StakingIdFactory,
|
||||
): FetchTokenListUseCase {
|
||||
return FetchTokenListUseCase(
|
||||
currenciesRepository = currenciesRepository,
|
||||
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
||||
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
|
||||
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
|
||||
stakingIdFactory = stakingIdFactory,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -91,11 +97,11 @@ internal object TokensDomainModule {
|
|||
@Singleton
|
||||
fun provideGetTokenListUseCase(
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
baseCurrenciesStatusesOperations: BaseCurrenciesStatusesOperations,
|
||||
currenciesStatusesOperations: BaseCurrencyStatusOperations,
|
||||
): GetTokenListUseCase {
|
||||
return GetTokenListUseCase(
|
||||
currenciesRepository = currenciesRepository,
|
||||
currenciesStatusesOperations = baseCurrenciesStatusesOperations,
|
||||
currenciesStatusesOperations = currenciesStatusesOperations,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -172,6 +178,7 @@ internal object TokensDomainModule {
|
|||
singleYieldBalanceFetcher: SingleYieldBalanceFetcher,
|
||||
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
stakingIdFactory: StakingIdFactory,
|
||||
): FetchCurrencyStatusUseCase {
|
||||
return FetchCurrencyStatusUseCase(
|
||||
currenciesRepository = currenciesRepository,
|
||||
|
|
@ -180,6 +187,7 @@ internal object TokensDomainModule {
|
|||
singleYieldBalanceFetcher = singleYieldBalanceFetcher,
|
||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
stakingIdFactory = stakingIdFactory,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -190,12 +198,14 @@ internal object TokensDomainModule {
|
|||
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
||||
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
|
||||
stakingIdFactory: StakingIdFactory,
|
||||
): FetchCardTokenListUseCase {
|
||||
return FetchCardTokenListUseCase(
|
||||
currenciesRepository = currenciesRepository,
|
||||
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
||||
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
|
||||
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
|
||||
stakingIdFactory = stakingIdFactory,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -359,9 +369,9 @@ internal object TokensDomainModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun provideGetWalletTotalBalanceUseCase(
|
||||
baseCurrenciesStatusesOperations: BaseCurrenciesStatusesOperations,
|
||||
currenciesStatusesOperations: BaseCurrencyStatusOperations,
|
||||
): GetWalletTotalBalanceUseCase {
|
||||
return GetWalletTotalBalanceUseCase(baseCurrenciesStatusesOperations)
|
||||
return GetWalletTotalBalanceUseCase(currenciesStatusesOperations)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
@ -389,47 +399,12 @@ internal object TokensDomainModule {
|
|||
return GetCurrencyCheckUseCase(currencyChecksRepository, dispatchers)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideBaseCurrenciesStatusesOperations(
|
||||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
quotesRepository: QuotesRepository,
|
||||
stakingRepository: StakingRepository,
|
||||
singleNetworkStatusSupplier: SingleNetworkStatusSupplier,
|
||||
multiNetworkStatusSupplier: MultiNetworkStatusSupplier,
|
||||
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||
singleNetworkStatusFetcher: SingleNetworkStatusFetcher,
|
||||
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
||||
singleQuoteStatusSupplier: SingleQuoteStatusSupplier,
|
||||
singleYieldBalanceSupplier: SingleYieldBalanceSupplier,
|
||||
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
|
||||
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
||||
): BaseCurrenciesStatusesOperations {
|
||||
return CachedCurrenciesStatusesOperations(
|
||||
currenciesRepository = currenciesRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
stakingRepository = stakingRepository,
|
||||
singleNetworkStatusSupplier = singleNetworkStatusSupplier,
|
||||
multiNetworkStatusSupplier = multiNetworkStatusSupplier,
|
||||
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
||||
singleNetworkStatusFetcher = singleNetworkStatusFetcher,
|
||||
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
|
||||
singleQuoteStatusSupplier = singleQuoteStatusSupplier,
|
||||
singleYieldBalanceSupplier = singleYieldBalanceSupplier,
|
||||
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideBaseCurrencyStatusOperations(
|
||||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
quotesRepository: QuotesRepository,
|
||||
stakingRepository: StakingRepository,
|
||||
singleNetworkStatusSupplier: SingleNetworkStatusSupplier,
|
||||
multiNetworkStatusSupplier: MultiNetworkStatusSupplier,
|
||||
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||
|
|
@ -437,13 +412,14 @@ internal object TokensDomainModule {
|
|||
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
||||
singleQuoteStatusSupplier: SingleQuoteStatusSupplier,
|
||||
singleYieldBalanceSupplier: SingleYieldBalanceSupplier,
|
||||
multiYieldBalanceSupplier: MultiYieldBalanceSupplier,
|
||||
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
|
||||
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
||||
stakingIdFactory: StakingIdFactory,
|
||||
): BaseCurrencyStatusOperations {
|
||||
return CachedCurrenciesStatusesOperations(
|
||||
currenciesRepository = currenciesRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
stakingRepository = stakingRepository,
|
||||
singleNetworkStatusSupplier = singleNetworkStatusSupplier,
|
||||
multiNetworkStatusSupplier = multiNetworkStatusSupplier,
|
||||
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
||||
|
|
@ -451,9 +427,11 @@ internal object TokensDomainModule {
|
|||
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
|
||||
singleQuoteStatusSupplier = singleQuoteStatusSupplier,
|
||||
singleYieldBalanceSupplier = singleYieldBalanceSupplier,
|
||||
multiYieldBalanceSupplier = multiYieldBalanceSupplier,
|
||||
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
|
||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
stakingIdFactory = stakingIdFactory,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -472,6 +450,7 @@ internal object TokensDomainModule {
|
|||
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
||||
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
|
||||
stakingIdFactory: StakingIdFactory,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): WalletBalanceFetcher {
|
||||
return WalletBalanceFetcher(
|
||||
|
|
@ -481,6 +460,7 @@ internal object TokensDomainModule {
|
|||
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
||||
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
|
||||
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
|
||||
stakingIdFactory = stakingIdFactory,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
|
|
@ -490,4 +470,20 @@ internal object TokensDomainModule {
|
|||
fun provideGetAssetRequirementsUseCase(walletManagersFacade: WalletManagersFacade): GetAssetRequirementsUseCase {
|
||||
return GetAssetRequirementsUseCase(walletManagersFacade)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetViewedTokenReceiveWarningUseCase(
|
||||
tokenReceiveWarningsViewedRepository: TokenReceiveWarningsViewedRepository,
|
||||
): GetViewedTokenReceiveWarningUseCase {
|
||||
return GetViewedTokenReceiveWarningUseCase(tokenReceiveWarningsViewedRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSaveViewedTokenReceiveWarningUseCase(
|
||||
tokenReceiveWarningsViewedRepository: TokenReceiveWarningsViewedRepository,
|
||||
): SaveViewedTokenReceiveWarningUseCase {
|
||||
return SaveViewedTokenReceiveWarningUseCase(tokenReceiveWarningsViewedRepository)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.tap.di.domain
|
||||
|
||||
import com.tangem.data.wallets.hot.TangemHotWalletSigner
|
||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||
import com.tangem.domain.demo.models.DemoConfig
|
||||
import com.tangem.domain.networks.single.SingleNetworkStatusFetcher
|
||||
|
|
@ -9,9 +10,9 @@ import com.tangem.domain.tokens.TokensFeatureToggles
|
|||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.transaction.FeeRepository
|
||||
import com.tangem.domain.transaction.TransactionRepository
|
||||
import com.tangem.domain.transaction.WalletAddressServiceRepository
|
||||
import com.tangem.domain.transaction.usecase.*
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.tap.domain.hot.TangemHotWalletSigner
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -181,8 +182,13 @@ internal object TransactionDomainModule {
|
|||
fun providePrepareForSendUseCase(
|
||||
transactionRepository: TransactionRepository,
|
||||
cardSdkConfigRepository: CardSdkConfigRepository,
|
||||
tangemHotWalletSignerFactory: TangemHotWalletSigner.Factory,
|
||||
): PrepareForSendUseCase {
|
||||
return PrepareForSendUseCase(transactionRepository, cardSdkConfigRepository)
|
||||
return PrepareForSendUseCase(
|
||||
transactionRepository = transactionRepository,
|
||||
cardSdkConfigRepository = cardSdkConfigRepository,
|
||||
getHotTransactionSigner = { tangemHotWalletSignerFactory.create(it) },
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
@ -199,8 +205,13 @@ internal object TransactionDomainModule {
|
|||
fun provideSignUseCase(
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
cardSdkConfigRepository: CardSdkConfigRepository,
|
||||
tangemHotWalletSignerFactory: TangemHotWalletSigner.Factory,
|
||||
): SignUseCase {
|
||||
return SignUseCase(cardSdkConfigRepository, walletManagersFacade)
|
||||
return SignUseCase(
|
||||
cardSdkConfigRepository = cardSdkConfigRepository,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
getHotTransactionSigner = { tangemHotWalletSignerFactory.create(it) },
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
@ -210,4 +221,24 @@ internal object TransactionDomainModule {
|
|||
): CreateNFTTransferTransactionUseCase {
|
||||
return CreateNFTTransferTransactionUseCase(transactionRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetEnsNameUseCase(
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
walletAddressServiceRepository: WalletAddressServiceRepository,
|
||||
): GetEnsNameUseCase {
|
||||
return GetEnsNameUseCase(
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
walletAddressServiceRepository = walletAddressServiceRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetReverseResolvedEnsAddressUseCase(
|
||||
walletAddressServiceRepository: WalletAddressServiceRepository,
|
||||
): GetReverseResolvedEnsAddressUseCase {
|
||||
return GetReverseResolvedEnsAddressUseCase(walletAddressServiceRepository)
|
||||
}
|
||||
}
|
||||
|
|
@ -10,11 +10,13 @@ import com.tangem.domain.walletmanager.WalletManagersFacade
|
|||
import com.tangem.domain.wallets.delegate.DefaultUserWalletsSyncDelegate
|
||||
import com.tangem.domain.wallets.delegate.UserWalletsSyncDelegate
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.core.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.wallets.repository.WalletNamesMigrationRepository
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.domain.wallets.usecase.*
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.IsWalletNFTEnabledSyncUseCase
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletNameMigrationUseCase
|
||||
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
||||
import com.tangem.operations.attestation.CardArtworksProvider
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
|
|
@ -31,18 +33,30 @@ internal object WalletsDomainModule {
|
|||
@Provides
|
||||
fun providesUserWalletsSyncDelegate(
|
||||
userWalletsListManager: UserWalletsListManager,
|
||||
userWalletsListRepository: UserWalletsListRepository,
|
||||
hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): UserWalletsSyncDelegate {
|
||||
return DefaultUserWalletsSyncDelegate(
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
useNewRepository = hotWalletFeatureToggles.isHotWalletEnabled,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesGetWalletsUseCase(userWalletsListManager: UserWalletsListManager): GetWalletsUseCase {
|
||||
return GetWalletsUseCase(userWalletsListManager = userWalletsListManager)
|
||||
fun providesGetWalletsUseCase(
|
||||
userWalletsListManager: UserWalletsListManager,
|
||||
userWalletsListRepository: UserWalletsListRepository,
|
||||
hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
): GetWalletsUseCase {
|
||||
return GetWalletsUseCase(
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
useNewListRepository = hotWalletFeatureToggles.isHotWalletEnabled,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
@ -50,37 +64,79 @@ internal object WalletsDomainModule {
|
|||
fun providesWalletNameMigrationUseCase(
|
||||
userWalletsListManager: UserWalletsListManager,
|
||||
walletNamesMigrationRepository: WalletNamesMigrationRepository,
|
||||
userWalletsListRepository: UserWalletsListRepository,
|
||||
hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
): WalletNameMigrationUseCase {
|
||||
return WalletNameMigrationUseCase(
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
walletNamesMigrationRepository = walletNamesMigrationRepository,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
useNewListRepository = hotWalletFeatureToggles.isHotWalletEnabled,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesGetUserWalletUseCase(userWalletsListManager: UserWalletsListManager): GetUserWalletUseCase {
|
||||
return GetUserWalletUseCase(userWalletsListManager = userWalletsListManager)
|
||||
fun providesGetUserWalletUseCase(
|
||||
userWalletsListManager: UserWalletsListManager,
|
||||
userWalletsListRepository: UserWalletsListRepository,
|
||||
hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
): GetUserWalletUseCase {
|
||||
return GetUserWalletUseCase(
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
useNewListRepository = hotWalletFeatureToggles.isHotWalletEnabled,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesGetSelectedWalletSyncUseCase(
|
||||
userWalletsListManager: UserWalletsListManager,
|
||||
userWalletsListRepository: UserWalletsListRepository,
|
||||
hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
): GetSelectedWalletSyncUseCase {
|
||||
return GetSelectedWalletSyncUseCase(userWalletsListManager = userWalletsListManager)
|
||||
return GetSelectedWalletSyncUseCase(
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
useNewRepository = hotWalletFeatureToggles.isHotWalletEnabled,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesGetSelectedWalletUseCase(userWalletsListManager: UserWalletsListManager): GetSelectedWalletUseCase {
|
||||
return GetSelectedWalletUseCase(userWalletsListManager = userWalletsListManager)
|
||||
fun providesGetSelectedWalletUseCase(
|
||||
userWalletsListManager: UserWalletsListManager,
|
||||
userWalletsListRepository: UserWalletsListRepository,
|
||||
hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
): GetSelectedWalletUseCase {
|
||||
return GetSelectedWalletUseCase(
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
useNewRepository = hotWalletFeatureToggles.isHotWalletEnabled,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesSaveWalletUseCase(userWalletsListManager: UserWalletsListManager): SaveWalletUseCase {
|
||||
return SaveWalletUseCase(userWalletsListManager = userWalletsListManager)
|
||||
fun providesSaveWalletUseCase(
|
||||
userWalletsListManager: UserWalletsListManager,
|
||||
userWalletsListRepository: UserWalletsListRepository,
|
||||
hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
walletsRepository: WalletsRepository,
|
||||
): SaveWalletUseCase {
|
||||
return SaveWalletUseCase(
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
walletsRepository = walletsRepository,
|
||||
useNewRepository = hotWalletFeatureToggles.isHotWalletEnabled,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesOpenBuyTangemCardUseCase(): GenerateBuyTangemCardLinkUseCase {
|
||||
return GenerateBuyTangemCardLinkUseCase()
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
@ -99,15 +155,30 @@ internal object WalletsDomainModule {
|
|||
@Singleton
|
||||
fun providesSelectWalletUseCase(
|
||||
userWalletsListManager: UserWalletsListManager,
|
||||
userWalletsListRepository: UserWalletsListRepository,
|
||||
hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
reduxStateHolder: ReduxStateHolder,
|
||||
): SelectWalletUseCase {
|
||||
return SelectWalletUseCase(userWalletsListManager = userWalletsListManager, reduxStateHolder = reduxStateHolder)
|
||||
return SelectWalletUseCase(
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
useNewRepository = hotWalletFeatureToggles.isHotWalletEnabled,
|
||||
reduxStateHolder = reduxStateHolder,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesUpdateWalletUseCase(userWalletsListManager: UserWalletsListManager): UpdateWalletUseCase {
|
||||
return UpdateWalletUseCase(userWalletsListManager = userWalletsListManager)
|
||||
fun providesUpdateWalletUseCase(
|
||||
userWalletsListManager: UserWalletsListManager,
|
||||
userWalletsListRepository: UserWalletsListRepository,
|
||||
hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
): UpdateWalletUseCase {
|
||||
return UpdateWalletUseCase(
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
useNewRepository = hotWalletFeatureToggles.isHotWalletEnabled,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
@ -124,14 +195,30 @@ internal object WalletsDomainModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesGetWalletsSyncUseCase(userWalletsListManager: UserWalletsListManager): GetWalletNamesUseCase {
|
||||
return GetWalletNamesUseCase(userWalletsListManager = userWalletsListManager)
|
||||
fun providesGetWalletsSyncUseCase(
|
||||
userWalletsListManager: UserWalletsListManager,
|
||||
userWalletsListRepository: UserWalletsListRepository,
|
||||
hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
): GetWalletNamesUseCase {
|
||||
return GetWalletNamesUseCase(
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
useNewRepository = hotWalletFeatureToggles.isHotWalletEnabled,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesDeleteWalletUseCase(userWalletsListManager: UserWalletsListManager): DeleteWalletUseCase {
|
||||
return DeleteWalletUseCase(userWalletsListManager = userWalletsListManager)
|
||||
fun providesDeleteWalletUseCase(
|
||||
userWalletsListManager: UserWalletsListManager,
|
||||
userWalletsListRepository: UserWalletsListRepository,
|
||||
hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
): DeleteWalletUseCase {
|
||||
return DeleteWalletUseCase(
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
useNewRepository = hotWalletFeatureToggles.isHotWalletEnabled,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
@ -214,9 +301,13 @@ internal object WalletsDomainModule {
|
|||
@Singleton
|
||||
fun providesGetSavedWalletChangesIdUseCase(
|
||||
userWalletsListManager: UserWalletsListManager,
|
||||
userWalletsListRepository: UserWalletsListRepository,
|
||||
hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
): GetSavedWalletsCountUseCase {
|
||||
return GetSavedWalletsCountUseCase(
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
useNewRepository = hotWalletFeatureToggles.isHotWalletEnabled,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
package com.tangem.tap.di.hot
|
||||
|
||||
import com.tangem.hot.sdk.TangemHotSdk
|
||||
import com.tangem.tap.domain.hot.HotWalletPasswordRequester
|
||||
import com.tangem.tap.features.hot.DefaultHotWalletPasswordRequester
|
||||
import com.tangem.tap.features.hot.TangemHotSDKProxy
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
|
|
@ -17,8 +15,4 @@ internal interface TangemHotSdkModule {
|
|||
@Binds
|
||||
@Singleton
|
||||
fun bindTangemHotSdk(proxy: TangemHotSDKProxy): TangemHotSdk
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindHotWalletPasswordRequester(impl: DefaultHotWalletPasswordRequester): HotWalletPasswordRequester
|
||||
}
|
||||
|
|
@ -1,113 +0,0 @@
|
|||
package com.tangem.tap.domain.hot
|
||||
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.features.hotwallet.HotWalletPasswordRequester
|
||||
import com.tangem.hot.sdk.TangemHotSdk
|
||||
import com.tangem.hot.sdk.exception.WrongPasswordException
|
||||
import com.tangem.hot.sdk.model.*
|
||||
import javax.inject.Inject
|
||||
|
||||
class HotWalletAccessor @Inject constructor(
|
||||
private val tangemHotSdk: TangemHotSdk,
|
||||
private val hotWalletPasswordRequester: HotWalletPasswordRequester,
|
||||
) {
|
||||
|
||||
suspend fun signHashes(hotWalletId: HotWalletId, dataToSign: List<DataToSign>): List<SignedData> {
|
||||
val auth = when (hotWalletId.authType) {
|
||||
HotWalletId.AuthType.NoPassword -> HotAuth.NoAuth
|
||||
HotWalletId.AuthType.Password -> requestPassword(false)
|
||||
HotWalletId.AuthType.Biometry -> HotAuth.Biometry
|
||||
}
|
||||
|
||||
return runCatchingSdkErrors(hotWalletId, auth) {
|
||||
tangemHotSdk.signHashes(
|
||||
unlockHotWallet = UnlockHotWallet(
|
||||
walletId = hotWalletId,
|
||||
auth = it,
|
||||
),
|
||||
dataToSign = dataToSign,
|
||||
).also {
|
||||
hotWalletPasswordRequester.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun <T> runCatchingSdkErrors(
|
||||
hotWalletId: HotWalletId,
|
||||
auth: HotAuth,
|
||||
block: suspend (auth: HotAuth) -> T,
|
||||
): T {
|
||||
return runCatchingWrongPassInternal(
|
||||
originalAuth = auth,
|
||||
auth = auth,
|
||||
block = { blockAuth ->
|
||||
block(blockAuth).also {
|
||||
// TODO [REDACTED_TASK_KEY] if user has biometry enabled, we set it as the new auth method
|
||||
if (blockAuth is HotAuth.Password /*&& has biometry enabled */) {
|
||||
tangemHotSdk.changeAuth(
|
||||
unlockHotWallet = UnlockHotWallet(
|
||||
walletId = hotWalletId,
|
||||
auth = blockAuth,
|
||||
),
|
||||
auth = HotAuth.Biometry,
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun <T> runCatchingWrongPassInternal(
|
||||
originalAuth: HotAuth,
|
||||
auth: HotAuth,
|
||||
block: suspend (auth: HotAuth) -> T,
|
||||
): T = runCatching {
|
||||
block(auth)
|
||||
}.getOrElse { exception ->
|
||||
if (auth is HotAuth.Biometry && exception.isBiometryError()) {
|
||||
// fallback to password if biometry fails
|
||||
val passAuth = requestPassword(true)
|
||||
|
||||
return@getOrElse runCatchingWrongPassInternal(
|
||||
originalAuth = originalAuth,
|
||||
auth = passAuth,
|
||||
block = block,
|
||||
)
|
||||
}
|
||||
|
||||
if (exception !is WrongPasswordException) {
|
||||
throw exception
|
||||
}
|
||||
|
||||
// If the exception is a wrong password, we need to request the password again
|
||||
|
||||
hotWalletPasswordRequester.wrongPassword()
|
||||
val passResult = requestPassword(originalAuth is HotAuth.Biometry)
|
||||
|
||||
runCatchingWrongPassInternal(
|
||||
originalAuth = originalAuth,
|
||||
auth = passResult,
|
||||
block = block,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun requestPassword(hasBiometry: Boolean): HotAuth {
|
||||
return hotWalletPasswordRequester.requestPassword(hasBiometry).toAuth() ?: throw TangemSdkError.UserCancelled()
|
||||
}
|
||||
|
||||
private fun Throwable.isBiometryError(): Boolean {
|
||||
return this is TangemSdkError.AuthenticationFailed ||
|
||||
this is TangemSdkError.AuthenticationCanceled ||
|
||||
this is TangemSdkError.AuthenticationLockout ||
|
||||
this is TangemSdkError.AuthenticationUnavailable ||
|
||||
this is TangemSdkError.AuthenticationAlreadyInProgress ||
|
||||
this is TangemSdkError.AuthenticationNotInitialized ||
|
||||
this is TangemSdkError.AuthenticationPermanentLockout
|
||||
}
|
||||
|
||||
private fun HotWalletPasswordRequester.Result.toAuth() = when (this) {
|
||||
HotWalletPasswordRequester.Result.UseBiometry -> HotAuth.Biometry
|
||||
HotWalletPasswordRequester.Result.Dismiss -> null
|
||||
is HotWalletPasswordRequester.Result.EnteredPassword -> this.password
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
package com.tangem.tap.domain.hot
|
||||
|
||||
import com.tangem.hot.sdk.model.HotAuth
|
||||
import com.tangem.hot.sdk.model.HotWalletId
|
||||
|
||||
interface HotWalletPasswordRequester {
|
||||
|
||||
suspend fun requestPassword(hotWalletId: HotWalletId): HotAuth.Password
|
||||
}
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
package com.tangem.tap.domain.hot
|
||||
|
||||
import com.tangem.blockchain.common.TransactionSigner
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.map
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.hot.sdk.model.DataToSign
|
||||
import com.tangem.operations.sign.SignData
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
class TangemHotSigner @AssistedInject constructor(
|
||||
@Assisted private val userWallet: UserWallet.Hot,
|
||||
private val hotWalletAccessor: HotWalletAccessor,
|
||||
) : TransactionSigner {
|
||||
|
||||
override suspend fun sign(hash: ByteArray, publicKey: Wallet.PublicKey): CompletionResult<ByteArray> {
|
||||
return sign(listOf(hash), publicKey).map { it.first() }
|
||||
}
|
||||
|
||||
override suspend fun sign(
|
||||
hashes: List<ByteArray>,
|
||||
publicKey: Wallet.PublicKey,
|
||||
): CompletionResult<List<ByteArray>> {
|
||||
val wallet = userWallet.wallets.orEmpty().firstOrNull { it.publicKey == publicKey.seedKey }
|
||||
?: return CompletionResult.Failure(
|
||||
TangemSdkError.ExceptionError(IllegalStateException("wallet is locked")),
|
||||
)
|
||||
|
||||
val result = hotWalletAccessor.signHashes(
|
||||
hotWalletId = userWallet.hotWalletId,
|
||||
dataToSign = listOf(
|
||||
DataToSign(
|
||||
curve = wallet.curve,
|
||||
hashes = hashes,
|
||||
derivationPath = publicKey.derivationPath,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
return CompletionResult.Success(result.map { it.signatures }.flatten())
|
||||
}
|
||||
|
||||
override suspend fun multiSign(
|
||||
dataToSign: List<SignData>,
|
||||
publicKey: Wallet.PublicKey,
|
||||
): CompletionResult<Map<ByteArray, ByteArray>> {
|
||||
val result = hotWalletAccessor.signHashes(
|
||||
hotWalletId = userWallet.hotWalletId,
|
||||
dataToSign = dataToSign.map { signData ->
|
||||
val wallet = userWallet.wallets.orEmpty().firstOrNull { it.publicKey == signData.publicKey }
|
||||
?: return CompletionResult.Failure(
|
||||
TangemSdkError.ExceptionError(IllegalStateException("wallet is locked")),
|
||||
)
|
||||
|
||||
DataToSign(
|
||||
curve = wallet.curve,
|
||||
hashes = listOf(signData.hash),
|
||||
derivationPath = signData.derivationPath,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
return CompletionResult.Success(
|
||||
result.mapIndexed { index, data ->
|
||||
dataToSign[index].publicKey to data.signatures.first()
|
||||
}.toMap(),
|
||||
)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(@Assisted userWallet: UserWallet.Hot): TangemHotSigner
|
||||
}
|
||||
}
|
||||
|
|
@ -20,13 +20,11 @@ import com.tangem.crypto.hdWallet.DerivationPath
|
|||
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
|
||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
import com.tangem.domain.card.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.wallets.derivations.derivationStyleProvider
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.visa.model.VisaActivationInput
|
||||
import com.tangem.domain.visa.model.VisaDataForApprove
|
||||
import com.tangem.domain.visa.model.VisaSignedDataByCustomerWallet
|
||||
import com.tangem.domain.visa.model.*
|
||||
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
|
||||
import com.tangem.operations.ScanTask
|
||||
import com.tangem.operations.derivation.DerivationTaskResponse
|
||||
|
|
@ -501,7 +499,12 @@ internal class DefaultTangemSdkManager(
|
|||
): CompletionResult<VisaSignedDataByCustomerWallet> {
|
||||
return runTaskAsyncReturnOnMain(
|
||||
runnable = VisaCustomerWalletApproveTask(
|
||||
visaDataForApprove = visaDataForApprove,
|
||||
VisaCustomerWalletApproveTask.Input(
|
||||
cardId = visaDataForApprove.customerWalletCardId,
|
||||
targetAddress = visaDataForApprove.targetAddress,
|
||||
hashToSign = visaDataForApprove.dataToSign.hashToSign,
|
||||
sign = visaDataForApprove.dataToSign::sign,
|
||||
),
|
||||
),
|
||||
cardId = visaDataForApprove.customerWalletCardId,
|
||||
initialMessage = Message(resources.getStringSafe(R.string.initial_message_tap_header)),
|
||||
|
|
|
|||
|
|
@ -18,9 +18,7 @@ import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
|
|||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.visa.model.VisaActivationInput
|
||||
import com.tangem.domain.visa.model.VisaDataForApprove
|
||||
import com.tangem.domain.visa.model.VisaSignedDataByCustomerWallet
|
||||
import com.tangem.domain.visa.model.*
|
||||
import com.tangem.operations.derivation.DerivationTaskResponse
|
||||
import com.tangem.operations.preflightread.PreflightReadFilter
|
||||
import com.tangem.operations.wallet.CreateWalletResponse
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import com.tangem.common.map
|
|||
import com.tangem.crypto.bip39.Mnemonic
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.domain.card.CardTypesResolver
|
||||
import com.tangem.domain.card.DerivationStyleProvider
|
||||
import com.tangem.domain.wallets.derivations.DerivationStyleProvider
|
||||
import com.tangem.domain.card.common.TapWorkarounds.isTestCard
|
||||
import com.tangem.domain.card.configs.CardConfig
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import com.tangem.blockchain.common.derivation.DerivationStyle
|
|||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.datasource.local.token.UserTokensResponseStore
|
||||
import com.tangem.domain.card.DerivationStyleProvider
|
||||
import com.tangem.domain.wallets.derivations.DerivationStyleProvider
|
||||
import com.tangem.domain.card.common.TapWorkarounds.useOldStyleDerivation
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
|
|
|
|||
|
|
@ -14,14 +14,14 @@ import com.tangem.common.tlv.Tlv
|
|||
import com.tangem.common.tlv.TlvDecoder
|
||||
import com.tangem.crypto.CryptoUtils
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.domain.card.DerivationStyleProvider
|
||||
import com.tangem.domain.wallets.derivations.DerivationStyleProvider
|
||||
import com.tangem.domain.card.common.TapWorkarounds.isExcluded
|
||||
import com.tangem.domain.card.common.TapWorkarounds.isNotSupportedInThatRelease
|
||||
import com.tangem.domain.card.common.TapWorkarounds.isStart2Coin
|
||||
import com.tangem.domain.card.common.TapWorkarounds.isTangemTwins
|
||||
import com.tangem.domain.card.common.TapWorkarounds.isVisa
|
||||
import com.tangem.domain.common.TwinsHelper
|
||||
import com.tangem.domain.card.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.wallets.derivations.derivationStyleProvider
|
||||
import com.tangem.domain.card.common.visa.VisaUtilities
|
||||
import com.tangem.domain.card.configs.CardConfig
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.tap.domain.tasks.visa
|
||||
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.blockchain.blockchains.ethereum.EthereumUtils.toKeccak
|
||||
import com.tangem.blockchain.common.UnmarshalHelper
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.card.Card
|
||||
|
|
@ -10,27 +11,24 @@ import com.tangem.common.core.CardSession
|
|||
import com.tangem.common.core.CardSessionRunnable
|
||||
import com.tangem.common.core.CompletionCallback
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.common.extensions.toDecompressedPublicKey
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.core.error.ext.tangemError
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
|
||||
import com.tangem.domain.card.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.wallets.derivations.derivationStyleProvider
|
||||
import com.tangem.domain.card.common.visa.VisaUtilities
|
||||
import com.tangem.domain.card.common.visa.VisaWalletPublicKeyUtility
|
||||
import com.tangem.domain.card.common.visa.VisaWalletPublicKeyUtility.findKeyWithoutDerivation
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.visa.error.VisaActivationError
|
||||
import com.tangem.domain.visa.model.VisaDataForApprove
|
||||
import com.tangem.domain.visa.model.VisaSignedDataByCustomerWallet
|
||||
import com.tangem.domain.visa.model.sign
|
||||
import com.tangem.operations.ScanTask
|
||||
import com.tangem.operations.derivation.DeriveWalletPublicKeyTask
|
||||
import com.tangem.operations.sign.SignHashCommand
|
||||
|
||||
class VisaCustomerWalletApproveTask(
|
||||
private val visaDataForApprove: VisaDataForApprove,
|
||||
private val visaDataForApprove: Input,
|
||||
) : CardSessionRunnable<VisaSignedDataByCustomerWallet> {
|
||||
|
||||
override fun run(session: CardSession, callback: CompletionCallback<VisaSignedDataByCustomerWallet>) {
|
||||
|
|
@ -44,7 +42,7 @@ class VisaCustomerWalletApproveTask(
|
|||
return
|
||||
}
|
||||
|
||||
if (visaDataForApprove.customerWalletCardId != null && card.cardId != visaDataForApprove.customerWalletCardId) {
|
||||
if (visaDataForApprove.cardId != null && card.cardId != visaDataForApprove.cardId) {
|
||||
callback(CompletionResult.Failure(VisaActivationError.CardIdNotMatched.tangemError))
|
||||
return
|
||||
}
|
||||
|
|
@ -153,6 +151,12 @@ class VisaCustomerWalletApproveTask(
|
|||
)
|
||||
}
|
||||
|
||||
// TODO: [REDACTED_TASK_KEY] - Get this public function from Blockchain SDK
|
||||
private fun hashPersonalMessage(message: ByteArray): ByteArray {
|
||||
val prefix = "\u0019Ethereum Signed Message:\n${message.size}".toByteArray()
|
||||
return (prefix + message).toKeccak()
|
||||
}
|
||||
|
||||
private fun signApproveData(
|
||||
targetWalletPublicKey: ByteArray,
|
||||
derivationPath: DerivationPath?,
|
||||
|
|
@ -160,10 +164,11 @@ class VisaCustomerWalletApproveTask(
|
|||
session: CardSession,
|
||||
callback: CompletionCallback<VisaSignedDataByCustomerWallet>,
|
||||
) {
|
||||
val hashToSign = visaDataForApprove.dataToSign.hashToSign.hexToBytes()
|
||||
val content = "Tangem Pay wants to sign in with your account. Nonce: ${visaDataForApprove.hashToSign}"
|
||||
val hash = hashPersonalMessage(content.toByteArray(Charsets.UTF_8))
|
||||
|
||||
val signTask = SignHashCommand(
|
||||
hash = hashToSign,
|
||||
hash = hash,
|
||||
walletPublicKey = targetWalletPublicKey,
|
||||
derivationPath = derivationPath,
|
||||
)
|
||||
|
|
@ -173,7 +178,7 @@ class VisaCustomerWalletApproveTask(
|
|||
is CompletionResult.Success -> {
|
||||
val rsvSignature = UnmarshalHelper.unmarshalSignatureExtended(
|
||||
signature = result.data.signature,
|
||||
hash = hashToSign,
|
||||
hash = hash,
|
||||
publicKey = extendedPublicKey?.publicKey?.toDecompressedPublicKey()
|
||||
?: targetWalletPublicKey.toDecompressedPublicKey(),
|
||||
).asRSVLegacyEVM().toHexString().lowercase()
|
||||
|
|
@ -181,10 +186,7 @@ class VisaCustomerWalletApproveTask(
|
|||
scanCard(
|
||||
session = session,
|
||||
callback = callback,
|
||||
signedData = visaDataForApprove.dataToSign.sign(
|
||||
signature = rsvSignature,
|
||||
customerWalletAddress = visaDataForApprove.targetAddress,
|
||||
),
|
||||
signedData = visaDataForApprove.sign(rsvSignature, visaDataForApprove.targetAddress),
|
||||
)
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
|
|
@ -211,4 +213,11 @@ class VisaCustomerWalletApproveTask(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class Input(
|
||||
val cardId: String? = null,
|
||||
val targetAddress: String,
|
||||
val hashToSign: String,
|
||||
val sign: (signature: String, customerWalletAddress: String) -> VisaSignedDataByCustomerWallet,
|
||||
)
|
||||
}
|
||||
|
|
@ -11,14 +11,19 @@ import com.tangem.datasource.local.preferences.AppPreferencesStore
|
|||
import com.tangem.domain.models.scan.serialization.*
|
||||
import com.tangem.domain.visa.model.VisaActivationRemoteState
|
||||
import com.tangem.domain.visa.model.VisaCardActivationStatus
|
||||
import com.tangem.domain.wallets.hot.HotWalletPasswordRequester
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.core.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.wallets.hot.HotWalletAccessCodeAttemptsRepository
|
||||
import com.tangem.sdk.storage.AndroidSecureStorage
|
||||
import com.tangem.sdk.storage.AndroidSecureStorageV2
|
||||
import com.tangem.sdk.storage.createEncryptedSharedPreferences
|
||||
import com.tangem.tap.domain.userWalletList.implementation.BiometricUserWalletsListManager
|
||||
import com.tangem.tap.domain.userWalletList.implementation.GeneralUserWalletsListManager
|
||||
import com.tangem.tap.domain.userWalletList.implementation.RuntimeUserWalletsListManager
|
||||
import com.tangem.tap.domain.userWalletList.repository.DefaultUserWalletsListRepository
|
||||
import com.tangem.tap.domain.userWalletList.repository.DelegatedKeystoreManager
|
||||
import com.tangem.tap.domain.userWalletList.repository.UserWalletEncryptionKeysRepository
|
||||
import com.tangem.tap.domain.userWalletList.repository.UserWalletsKeysStoreDecorator
|
||||
import com.tangem.tap.domain.userWalletList.repository.implementation.BiometricUserWalletsKeysRepository
|
||||
import com.tangem.tap.domain.userWalletList.repository.implementation.DefaultSelectedUserWalletRepository
|
||||
|
|
@ -26,6 +31,7 @@ import com.tangem.tap.domain.userWalletList.repository.implementation.DefaultUse
|
|||
import com.tangem.tap.domain.userWalletList.repository.implementation.DefaultUserWalletsSensitiveInformationRepository
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.ProviderSuspend
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -40,6 +46,7 @@ internal object UserWalletsListManagerModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Deprecated("Use UserWalletsListRepository instead")
|
||||
fun provideGeneralUserWalletsListManager(
|
||||
@ApplicationContext applicationContext: Context,
|
||||
appPreferencesStore: AppPreferencesStore,
|
||||
|
|
@ -58,42 +65,14 @@ internal object UserWalletsListManagerModule {
|
|||
)
|
||||
}
|
||||
|
||||
@Deprecated("Use UserWalletsListRepository instead")
|
||||
private fun createBiometricUserWalletsListManager(
|
||||
applicationContext: Context,
|
||||
analyticsEventHandler: AnalyticsEventHandler,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): UserWalletsListManager {
|
||||
val moshi = Moshi.Builder()
|
||||
.add(WalletDerivedKeysMapAdapter())
|
||||
.add(ScanResponseDerivedKeysMapAdapter())
|
||||
.add(ByteArrayKeyAdapter())
|
||||
.add(ExtendedPublicKeysMapAdapter())
|
||||
.add(CardBackupStatusAdapter())
|
||||
.add(DerivationPathAdapterWithMigration())
|
||||
.add(TangemSdkAdapter.DateAdapter())
|
||||
.add(TangemSdkAdapter.DerivationNodeAdapter())
|
||||
.add(TangemSdkAdapter.FirmwareVersionAdapter()) // For PrimaryCard model
|
||||
.add(VisaActivationRemoteState.jsonAdapter)
|
||||
.add(VisaCardActivationStatus.jsonAdapter)
|
||||
.addLast(KotlinJsonAdapterFactory())
|
||||
.build()
|
||||
|
||||
val secureStorage = AndroidSecureStorage(
|
||||
preferences = SecureStorage.createEncryptedSharedPreferences(
|
||||
context = applicationContext,
|
||||
storageName = "user_wallets_storage",
|
||||
),
|
||||
androidSecureStorageV2 = AndroidSecureStorageV2(
|
||||
appContext = applicationContext,
|
||||
useStrongBox = true,
|
||||
name = "user_wallets_storage2",
|
||||
),
|
||||
androidSecureStorageV3 = AndroidSecureStorageV2(
|
||||
appContext = applicationContext,
|
||||
useStrongBox = false,
|
||||
name = "user_wallets_storage3",
|
||||
),
|
||||
)
|
||||
val moshi = buildMoshi()
|
||||
val secureStorage = buildSecureStorage(applicationContext = applicationContext)
|
||||
|
||||
val authenticatedStorage = AuthenticatedStorage(
|
||||
secureStorage = UserWalletsKeysStoreDecorator(
|
||||
|
|
@ -134,4 +113,97 @@ internal object UserWalletsListManagerModule {
|
|||
selectedUserWalletRepository = selectedUserWalletRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideUserWalletsListRepository(
|
||||
@ApplicationContext applicationContext: Context,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
passwordRequester: HotWalletPasswordRequester,
|
||||
appPreferencesStore: AppPreferencesStore,
|
||||
hotWalletAccessCodeAttemptsRepository: HotWalletAccessCodeAttemptsRepository,
|
||||
): UserWalletsListRepository {
|
||||
val moshi = buildMoshi()
|
||||
val secureStorage = buildSecureStorage(applicationContext = applicationContext)
|
||||
|
||||
val authenticatedStorage = AuthenticatedStorage(
|
||||
secureStorage = UserWalletsKeysStoreDecorator(
|
||||
featureStorage = secureStorage,
|
||||
cardSdkStorageProvider = Provider { tangemSdkManager.secureStorage },
|
||||
),
|
||||
keystoreManager = DelegatedKeystoreManager(
|
||||
keystoreManagerProvider = Provider { tangemSdkManager.keystoreManager },
|
||||
),
|
||||
)
|
||||
|
||||
val publicInformationRepository = DefaultUserWalletsPublicInformationRepository(
|
||||
moshi = moshi,
|
||||
secureStorage = secureStorage,
|
||||
)
|
||||
|
||||
val sensitiveInformationRepository = DefaultUserWalletsSensitiveInformationRepository(
|
||||
moshi = moshi,
|
||||
secureStorage = secureStorage,
|
||||
)
|
||||
|
||||
val selectedUserWalletRepository = DefaultSelectedUserWalletRepository(
|
||||
secureStorage = secureStorage,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
|
||||
val userWalletEncryptionKeysRepository = UserWalletEncryptionKeysRepository(
|
||||
moshi = moshi,
|
||||
authenticatedStorage = authenticatedStorage,
|
||||
dispatchers = dispatchers,
|
||||
secureStorage = secureStorage,
|
||||
)
|
||||
|
||||
return DefaultUserWalletsListRepository(
|
||||
publicInformationRepository = publicInformationRepository,
|
||||
sensitiveInformationRepository = sensitiveInformationRepository,
|
||||
selectedUserWalletRepository = selectedUserWalletRepository,
|
||||
passwordRequester = passwordRequester,
|
||||
userWalletEncryptionKeysRepository = userWalletEncryptionKeysRepository,
|
||||
tangemSdkManagerProvider = Provider { tangemSdkManager },
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
savePersistentInformation = ProviderSuspend { true }, // Always save persistent information for now
|
||||
hotWalletAccessCodeAttemptsRepository = hotWalletAccessCodeAttemptsRepository,
|
||||
)
|
||||
}
|
||||
|
||||
fun buildMoshi(): Moshi {
|
||||
return Moshi.Builder()
|
||||
.add(WalletDerivedKeysMapAdapter())
|
||||
.add(ScanResponseDerivedKeysMapAdapter())
|
||||
.add(ByteArrayKeyAdapter())
|
||||
.add(ExtendedPublicKeysMapAdapter())
|
||||
.add(CardBackupStatusAdapter())
|
||||
.add(DerivationPathAdapterWithMigration())
|
||||
.add(TangemSdkAdapter.DateAdapter())
|
||||
.add(TangemSdkAdapter.DerivationNodeAdapter())
|
||||
.add(TangemSdkAdapter.FirmwareVersionAdapter()) // For PrimaryCard model
|
||||
.add(VisaActivationRemoteState.jsonAdapter)
|
||||
.add(VisaCardActivationStatus.jsonAdapter)
|
||||
.addLast(KotlinJsonAdapterFactory())
|
||||
.build()
|
||||
}
|
||||
|
||||
fun buildSecureStorage(@ApplicationContext applicationContext: Context): SecureStorage {
|
||||
return AndroidSecureStorage(
|
||||
preferences = SecureStorage.createEncryptedSharedPreferences(
|
||||
context = applicationContext,
|
||||
storageName = "user_wallets_storage",
|
||||
),
|
||||
androidSecureStorageV2 = AndroidSecureStorageV2(
|
||||
appContext = applicationContext,
|
||||
useStrongBox = true,
|
||||
name = "user_wallets_storage2",
|
||||
),
|
||||
androidSecureStorageV3 = AndroidSecureStorageV2(
|
||||
appContext = applicationContext,
|
||||
useStrongBox = false,
|
||||
name = "user_wallets_storage3",
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,420 @@
|
|||
package com.tangem.tap.domain.userWalletList.repository
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import arrow.core.raise.either
|
||||
import arrow.core.right
|
||||
import com.tangem.common.doOnFailure
|
||||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.common.flatMap
|
||||
import com.tangem.common.map
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.getSyncOrDefault
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.models.wallet.isLocked
|
||||
import com.tangem.domain.wallets.R
|
||||
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
||||
import com.tangem.domain.wallets.hot.HotWalletPasswordRequester
|
||||
import com.tangem.domain.core.wallets.error.DeleteWalletError
|
||||
import com.tangem.domain.core.wallets.error.LockWalletsError
|
||||
import com.tangem.domain.core.wallets.error.SaveWalletError
|
||||
import com.tangem.domain.core.wallets.error.SelectWalletError
|
||||
import com.tangem.domain.core.wallets.error.SetLockError
|
||||
import com.tangem.domain.core.wallets.error.UnlockWalletError
|
||||
import com.tangem.domain.core.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.core.wallets.UserWalletsListRepository.LockMethod
|
||||
import com.tangem.domain.wallets.hot.HotWalletAccessCodeAttemptsRepository
|
||||
import com.tangem.hot.sdk.model.HotWalletId
|
||||
import com.tangem.sdk.api.TangemSdkManager
|
||||
import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey
|
||||
import com.tangem.tap.domain.userWalletList.utils.encryptionKey
|
||||
import com.tangem.tap.domain.userWalletList.utils.lock
|
||||
import com.tangem.tap.domain.userWalletList.utils.toUserWallets
|
||||
import com.tangem.tap.domain.userWalletList.utils.updateWith
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.ProviderSuspend
|
||||
import com.tangem.utils.extensions.indexOfFirstOrNull
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
|
||||
@Suppress("LongParameterList", "LargeClass")
|
||||
internal class DefaultUserWalletsListRepository(
|
||||
private val publicInformationRepository: UserWalletsPublicInformationRepository,
|
||||
private val sensitiveInformationRepository: UserWalletsSensitiveInformationRepository,
|
||||
private val selectedUserWalletRepository: SelectedUserWalletRepository,
|
||||
private val passwordRequester: HotWalletPasswordRequester,
|
||||
private val userWalletEncryptionKeysRepository: UserWalletEncryptionKeysRepository,
|
||||
private val tangemSdkManagerProvider: Provider<TangemSdkManager>,
|
||||
private val savePersistentInformation: ProviderSuspend<Boolean>,
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val hotWalletAccessCodeAttemptsRepository: HotWalletAccessCodeAttemptsRepository,
|
||||
) : UserWalletsListRepository {
|
||||
|
||||
override val userWallets = MutableStateFlow<List<UserWallet>?>(null)
|
||||
override val selectedUserWallet = MutableStateFlow<UserWallet?>(null)
|
||||
|
||||
override suspend fun load() {
|
||||
if (userWallets.value != null) return
|
||||
|
||||
if (savePersistentInformation().not()) {
|
||||
// If we don't save persistent information, we don't need to load user wallets
|
||||
// and we should clear any existing data
|
||||
clearPersistentData()
|
||||
userWallets.value = emptyList()
|
||||
return
|
||||
}
|
||||
|
||||
val unsecuredEncryptionKeys = userWalletEncryptionKeysRepository.getAllUnsecured()
|
||||
|
||||
publicInformationRepository.getAll()
|
||||
.map { it.toUserWallets() }
|
||||
.flatMap { wallets ->
|
||||
sensitiveInformationRepository.getAll(unsecuredEncryptionKeys)
|
||||
.map { wallets.updateWith(it) }
|
||||
}.doOnSuccess {
|
||||
userWallets.value = it
|
||||
}
|
||||
|
||||
val selectedUserWalletId = selectedUserWalletRepository.get()
|
||||
selectedUserWallet.value = userWallets.value?.firstOrNull { it.walletId == selectedUserWalletId }
|
||||
?: userWallets.value?.firstOrNull()
|
||||
}
|
||||
|
||||
override suspend fun userWalletsSync(): List<UserWallet> {
|
||||
load()
|
||||
return requireNotNull(userWallets.value) {
|
||||
"This should never happen"
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun selectedUserWalletSync(): UserWallet? {
|
||||
load()
|
||||
return selectedUserWallet.value
|
||||
}
|
||||
|
||||
override suspend fun select(userWalletId: UserWalletId): Either<SelectWalletError, UserWallet> = either {
|
||||
val userWallet = userWallets.value?.find { it.walletId == userWalletId }
|
||||
?: raise(SelectWalletError.UnableToSelectUserWallet)
|
||||
selectedUserWalletRepository.set(userWalletId)
|
||||
selectedUserWallet.value = userWallet
|
||||
userWallet
|
||||
}
|
||||
|
||||
override suspend fun saveWithoutLock(
|
||||
userWallet: UserWallet,
|
||||
canOverride: Boolean,
|
||||
): Either<SaveWalletError, UserWallet> = either {
|
||||
if (canOverride.not() && userWallets.value?.any { it.walletId == userWallet.walletId } == true) {
|
||||
raise(SaveWalletError.WalletAlreadySaved(messageId = R.string.user_wallet_list_error_wallet_already_saved))
|
||||
}
|
||||
|
||||
if (savePersistentInformation()) {
|
||||
publicInformationRepository.save(userWallet, canOverride)
|
||||
if (userWallet.isLocked.not()) {
|
||||
sensitiveInformationRepository.save(userWallet, userWallet.encryptionKey)
|
||||
}
|
||||
}
|
||||
|
||||
// update the userWallets state and add if it doesn't exist
|
||||
userWallets.update { currentWallets ->
|
||||
val wallets = currentWallets ?: emptyList()
|
||||
if (wallets.any { it.walletId == userWallet.walletId }) {
|
||||
wallets.map { if (it.walletId == userWallet.walletId) userWallet else it }
|
||||
} else {
|
||||
wallets + userWallet
|
||||
}
|
||||
}
|
||||
|
||||
// update the selectedUserWallet state if it is the only wallet
|
||||
if (userWallets.value?.size == 1) {
|
||||
selectedUserWalletRepository.set(userWallet.walletId)
|
||||
selectedUserWallet.value = userWallet
|
||||
}
|
||||
|
||||
userWallet
|
||||
}
|
||||
|
||||
override suspend fun setLock(
|
||||
userWalletId: UserWalletId,
|
||||
lockMethod: LockMethod,
|
||||
changeUnsecured: Boolean,
|
||||
): Either<SetLockError, Unit> = either {
|
||||
val userWallet = userWallets.value?.find { it.walletId == userWalletId }
|
||||
?: raise(SetLockError.UserWalletNotFound)
|
||||
|
||||
val encryptionKey = userWallet.encryptionKey
|
||||
?: raise(SetLockError.UserWalletLocked)
|
||||
|
||||
runCatching {
|
||||
userWalletEncryptionKeysRepository.save(
|
||||
encryptionKey = UserWalletEncryptionKey(
|
||||
walletId = userWalletId,
|
||||
encryptionKey = encryptionKey,
|
||||
),
|
||||
removeUnsecured = changeUnsecured,
|
||||
method = when (lockMethod) {
|
||||
is LockMethod.AccessCode -> {
|
||||
UserWalletEncryptionKeysRepository.EncryptionMethod.Password(lockMethod.accessCode)
|
||||
}
|
||||
LockMethod.Biometric -> {
|
||||
UserWalletEncryptionKeysRepository.EncryptionMethod.Biometric
|
||||
}
|
||||
LockMethod.NoLock -> {
|
||||
if (userWallet is UserWallet.Cold) {
|
||||
raise(SetLockError.UserWalletNotFound)
|
||||
}
|
||||
|
||||
UserWalletEncryptionKeysRepository.EncryptionMethod.Unsecured
|
||||
}
|
||||
},
|
||||
)
|
||||
}.onFailure { raise(SetLockError.UnableToSetLock(it)) }
|
||||
}
|
||||
|
||||
override suspend fun removeBiometricLock(userWalletId: UserWalletId) {
|
||||
userWalletEncryptionKeysRepository.removeBiometricKey(userWalletId)
|
||||
}
|
||||
|
||||
override suspend fun delete(userWalletIds: List<UserWalletId>): Either<DeleteWalletError, Unit> = either {
|
||||
if (userWalletIds.isEmpty()) return Unit.right()
|
||||
|
||||
publicInformationRepository.delete(userWalletIds)
|
||||
.doOnFailure {
|
||||
raise(DeleteWalletError.UnableToDelete)
|
||||
}
|
||||
sensitiveInformationRepository.delete(userWalletIds)
|
||||
.doOnFailure {
|
||||
raise(DeleteWalletError.UnableToDelete)
|
||||
}
|
||||
|
||||
userWalletEncryptionKeysRepository.delete(userWalletIds)
|
||||
|
||||
val userWalletsBeforeDelete = userWallets.value ?: return@either
|
||||
|
||||
userWallets.update { currentWallets ->
|
||||
currentWallets?.filterNot { it.walletId in userWalletIds }
|
||||
}
|
||||
|
||||
selectedUserWallet.update { currentSelected ->
|
||||
if (currentSelected == null) return@update null
|
||||
|
||||
userWallets.value?.findAvailableUserWallet(
|
||||
userWalletsBeforeDelete.indexOfFirstOrNull { it.walletId == currentSelected.walletId } ?: 0,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun unlock(
|
||||
userWalletId: UserWalletId,
|
||||
unlockMethod: UserWalletsListRepository.UnlockMethod,
|
||||
): Either<UnlockWalletError, Unit> = either {
|
||||
val userWallet = userWallets.value?.find { it.walletId == userWalletId }
|
||||
?: raise(UnlockWalletError.UserWalletNotFound)
|
||||
|
||||
if (userWallet.isLocked.not()) {
|
||||
raise(UnlockWalletError.AlreadyUnlocked)
|
||||
}
|
||||
|
||||
when (unlockMethod) {
|
||||
UserWalletsListRepository.UnlockMethod.Biometric -> {
|
||||
unlockAllWallets().bind()
|
||||
select(userWalletId)
|
||||
}
|
||||
UserWalletsListRepository.UnlockMethod.AccessCode -> {
|
||||
if (userWallet !is UserWallet.Hot) {
|
||||
raise(UnlockWalletError.UnableToUnlock)
|
||||
}
|
||||
|
||||
val encryptionKey = requestPasswordRecursive(
|
||||
hotWalletId = userWallet.hotWalletId,
|
||||
block = { password ->
|
||||
runCatching {
|
||||
userWalletEncryptionKeysRepository.getEncryptedWithPassword(userWalletId, password)
|
||||
}.onFailure {
|
||||
raise(UnlockWalletError.UnableToUnlock)
|
||||
}.getOrNull()
|
||||
},
|
||||
biometryFallback = {
|
||||
unlock(userWalletId, UserWalletsListRepository.UnlockMethod.Biometric)
|
||||
},
|
||||
).bind()
|
||||
|
||||
if (encryptionKey == null) {
|
||||
return@either
|
||||
}
|
||||
|
||||
removePasswordAttempts(userWallet)
|
||||
|
||||
sensitiveInformationRepository.getAll(listOf(encryptionKey))
|
||||
.doOnSuccess { sensitiveInfo -> userWallets.update { it?.updateWith(sensitiveInfo) } }
|
||||
.doOnFailure { error ->
|
||||
raise(UnlockWalletError.UnableToUnlock)
|
||||
}
|
||||
}
|
||||
UserWalletsListRepository.UnlockMethod.Scan -> {
|
||||
if (userWallet !is UserWallet.Cold) {
|
||||
raise(UnlockWalletError.UnableToUnlock)
|
||||
}
|
||||
|
||||
tangemSdkManagerProvider().scanProduct()
|
||||
.doOnSuccess { scanResponse ->
|
||||
val expectedId = UserWalletIdBuilder.scanResponse(scanResponse).build()
|
||||
|
||||
if (expectedId != userWallet.walletId) {
|
||||
raise(UnlockWalletError.ScannedCardWalletNotMatched)
|
||||
}
|
||||
|
||||
saveWithoutLock(userWallet.copy(scanResponse = scanResponse), canOverride = true)
|
||||
.mapLeft { UnlockWalletError.UnableToUnlock }
|
||||
.bind()
|
||||
}
|
||||
.doOnFailure {
|
||||
raise(UnlockWalletError.UserCancelled)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun unlockAllWallets(): Either<UnlockWalletError, Unit> = either {
|
||||
val userWalletIds = userWalletsSync().map { it.walletId }.toSet()
|
||||
val biometricKeys = runCatching {
|
||||
userWalletEncryptionKeysRepository.getAllBiometric()
|
||||
}.getOrElse {
|
||||
// TODO handle error properly [REDACTED_TASK_KEY]
|
||||
raise(UnlockWalletError.UserCancelled)
|
||||
}
|
||||
|
||||
val unsecuredKeys = userWalletEncryptionKeysRepository.getAllUnsecured()
|
||||
val allKeys = (biometricKeys + unsecuredKeys).distinct()
|
||||
val unlockedWalletsIds = allKeys.map { it.walletId }
|
||||
|
||||
val unlockedWallets = unlockedWalletsIds.mapNotNull { id ->
|
||||
userWalletsSync().firstOrNull { it.walletId == id }
|
||||
}
|
||||
|
||||
// Remove all password attempts for unlocked hot wallets
|
||||
unlockedWallets.forEach {
|
||||
removePasswordAttempts(it)
|
||||
}
|
||||
|
||||
// if we cant unlock all wallets
|
||||
if (userWalletIds.all { it in unlockedWalletsIds }.not()) {
|
||||
raise(UnlockWalletError.UnableToUnlock)
|
||||
}
|
||||
|
||||
sensitiveInformationRepository.getAll(allKeys)
|
||||
.doOnSuccess { sensitiveInfo ->
|
||||
userWallets.update { it?.updateWith(sensitiveInfo) }
|
||||
}
|
||||
.doOnFailure { raise(UnlockWalletError.UnableToUnlock) }
|
||||
}
|
||||
|
||||
override suspend fun lockAllWallets(): Either<LockWalletsError, Unit> = either {
|
||||
val unsecuredWalletIds = userWalletEncryptionKeysRepository.getAllUnsecured().map { it.walletId }.toSet()
|
||||
|
||||
if (unsecuredWalletIds.size == userWallets.value?.size) {
|
||||
raise(LockWalletsError.NothingToLock)
|
||||
}
|
||||
|
||||
userWallets.update {
|
||||
it?.map {
|
||||
if (it.walletId !in unsecuredWalletIds) {
|
||||
it.lock()
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun clearPersistentData() {
|
||||
publicInformationRepository.clear()
|
||||
sensitiveInformationRepository.clear()
|
||||
userWalletEncryptionKeysRepository.clear()
|
||||
}
|
||||
|
||||
private suspend fun requestPasswordRecursive(
|
||||
hotWalletId: HotWalletId,
|
||||
block: suspend (CharArray) -> UserWalletEncryptionKey?,
|
||||
biometryFallback: suspend () -> Either<UnlockWalletError, Unit>,
|
||||
): Either<UnlockWalletError, UserWalletEncryptionKey?> {
|
||||
val attemptRequest = HotWalletPasswordRequester.AttemptRequest(
|
||||
hotWalletId = hotWalletId,
|
||||
authMode = true, // In auth mode user wallet can be deleted after 30 failed attempts
|
||||
hasBiometry = hasBiometry(),
|
||||
)
|
||||
val result = passwordRequester.requestPassword(attemptRequest)
|
||||
|
||||
return when (result) {
|
||||
HotWalletPasswordRequester.Result.Dismiss -> {
|
||||
passwordRequester.dismiss()
|
||||
UnlockWalletError.UserCancelled.left()
|
||||
}
|
||||
is HotWalletPasswordRequester.Result.EnteredPassword -> {
|
||||
val decrypted = block(result.password.value)
|
||||
if (decrypted == null) {
|
||||
passwordRequester.wrongPassword()
|
||||
requestPasswordRecursive(hotWalletId, block, biometryFallback)
|
||||
} else {
|
||||
passwordRequester.successfulAuthentication()
|
||||
passwordRequester.dismiss()
|
||||
decrypted.right()
|
||||
}
|
||||
}
|
||||
HotWalletPasswordRequester.Result.UseBiometry -> {
|
||||
biometryFallback()
|
||||
.onRight {
|
||||
passwordRequester.successfulAuthentication()
|
||||
passwordRequester.dismiss()
|
||||
}
|
||||
.map { null }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun removePasswordAttempts(userWallet: UserWallet) {
|
||||
if (userWallet is UserWallet.Hot) {
|
||||
hotWalletAccessCodeAttemptsRepository.resetAttempts(userWallet.hotWalletId)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun hasBiometry(): Boolean {
|
||||
val useBiometricAuthentication = appPreferencesStore.getSyncOrDefault(
|
||||
key = PreferencesKeys.USE_BIOMETRIC_AUTHENTICATION_KEY,
|
||||
default = false,
|
||||
)
|
||||
|
||||
return tangemSdkManagerProvider.invoke().canUseBiometry && useBiometricAuthentication
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the nearest available wallet that can be selected
|
||||
*
|
||||
* Example:
|
||||
* Number with *n* is previous selected wallet with index [prevSelectedIndex].
|
||||
*
|
||||
* 1. [*1*, 2, 3, 4] => delete 1 => [2, 3, 4] => find and select => [*2*, 3, 4]
|
||||
* 2. [1, *2*, 3, 4] => delete 2 => [1, 3, 4] => find and select => [1, *3*, 4]
|
||||
* 3. [1, 2, *3*, 4] => delete 3 => [1, 2, 4] => find and select => [1, 2, *4*]
|
||||
* 4. [1, 2, 3, *4*] => delete 4 => [1, 2, 3] => find and select => [1, 2, *3*]
|
||||
*
|
||||
* @receiver list of user wallets without deleted wallet
|
||||
*/
|
||||
private fun List<UserWallet>.findAvailableUserWallet(prevSelectedIndex: Int): UserWallet? {
|
||||
if (prevSelectedIndex == 0) return firstOrNull { !it.isLocked } ?: firstOrNull()
|
||||
|
||||
if (prevSelectedIndex in indices && !this[prevSelectedIndex].isLocked) return this[prevSelectedIndex]
|
||||
|
||||
for (offset in 1..size) {
|
||||
val rightIndex = prevSelectedIndex + offset
|
||||
if (rightIndex in indices && !this[rightIndex].isLocked) return this[rightIndex]
|
||||
|
||||
val leftIndex = prevSelectedIndex - offset
|
||||
if (leftIndex in indices && !this[leftIndex].isLocked) return this[leftIndex]
|
||||
}
|
||||
|
||||
return lastOrNull()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,196 @@
|
|||
package com.tangem.tap.domain.userWalletList.repository
|
||||
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.Types
|
||||
import com.tangem.common.authentication.storage.AuthenticatedStorage
|
||||
import com.tangem.common.services.secure.SecureStorage
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.hot.sdk.android.crypto.AESEncryptionProtocol
|
||||
import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
internal class UserWalletEncryptionKeysRepository(
|
||||
moshi: Moshi,
|
||||
private val authenticatedStorage: AuthenticatedStorage,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val secureStorage: SecureStorage,
|
||||
) {
|
||||
|
||||
private val encryptionKeyAdapter: JsonAdapter<UserWalletEncryptionKey> = moshi.adapter(
|
||||
UserWalletEncryptionKey::class.java,
|
||||
)
|
||||
private val userWalletsIdsListAdapter: JsonAdapter<List<UserWalletId>> = moshi.adapter(
|
||||
Types.newParameterizedType(List::class.java, UserWalletId::class.java),
|
||||
)
|
||||
|
||||
suspend fun save(
|
||||
encryptionKey: UserWalletEncryptionKey,
|
||||
removeUnsecured: Boolean = true,
|
||||
method: EncryptionMethod,
|
||||
) = withContext(dispatchers.io) {
|
||||
if (removeUnsecured) {
|
||||
secureStorage.delete(StorageKey.UserWalletEncryptionKeyUnsecured(encryptionKey.walletId).name)
|
||||
}
|
||||
|
||||
when (method) {
|
||||
EncryptionMethod.Unsecured -> {
|
||||
secureStorage.store(
|
||||
account = StorageKey.UserWalletEncryptionKeyUnsecured(encryptionKey.walletId).name,
|
||||
data = encryptionKey.encode(),
|
||||
)
|
||||
}
|
||||
is EncryptionMethod.Password -> {
|
||||
val encodedWithPass = AESEncryptionProtocol.encryptWithPassword(
|
||||
password = method.password,
|
||||
content = encryptionKey.encode(),
|
||||
)
|
||||
secureStorage.store(
|
||||
account = StorageKey.UserWalletEncryptionKeyEncrypted(encryptionKey.walletId).name,
|
||||
data = encodedWithPass,
|
||||
)
|
||||
}
|
||||
EncryptionMethod.Biometric -> {
|
||||
authenticatedStorage.store(
|
||||
keyAlias = StorageKey.UserWalletEncryptionKey(encryptionKey.walletId).name,
|
||||
data = encryptionKey.encode(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
storeUserWalletId(userWalletId = encryptionKey.walletId)
|
||||
}
|
||||
|
||||
fun removeBiometricKey(userWalletId: UserWalletId) {
|
||||
authenticatedStorage.delete(StorageKey.UserWalletEncryptionKey(userWalletId).name)
|
||||
}
|
||||
|
||||
suspend fun getAllUnsecured(): List<UserWalletEncryptionKey> = withContext(dispatchers.io) {
|
||||
getUserWalletsIds().mapNotNull { userWalletId ->
|
||||
secureStorage.get(account = StorageKey.UserWalletEncryptionKeyUnsecured(userWalletId).name).decodeToKey()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getEncryptedWithPassword(userWalletId: UserWalletId, password: CharArray): UserWalletEncryptionKey? =
|
||||
withContext(dispatchers.io) {
|
||||
val encrypted = secureStorage.get(
|
||||
account = StorageKey.UserWalletEncryptionKeyEncrypted(userWalletId).name,
|
||||
) ?: return@withContext null
|
||||
|
||||
withContext(dispatchers.default) {
|
||||
AESEncryptionProtocol.decryptWithPassword(password, encrypted).decodeToKey()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getAllBiometric(): List<UserWalletEncryptionKey> = withContext(dispatchers.io) {
|
||||
val keys = getUserWalletsIds().map { userWalletId ->
|
||||
StorageKey.UserWalletEncryptionKey(userWalletId).name
|
||||
}
|
||||
|
||||
authenticatedStorage.get(keys).mapNotNull {
|
||||
it.value.decodeToKey()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun delete(userWalletIds: List<UserWalletId>) {
|
||||
if (userWalletIds.isEmpty()) return
|
||||
|
||||
withContext(dispatchers.io) {
|
||||
userWalletIds.forEach { userWalletId ->
|
||||
secureStorage.delete(StorageKey.UserWalletEncryptionKeyUnsecured(userWalletId).name)
|
||||
secureStorage.delete(StorageKey.UserWalletEncryptionKeyEncrypted(userWalletId).name)
|
||||
authenticatedStorage.delete(StorageKey.UserWalletEncryptionKey(userWalletId).name)
|
||||
}
|
||||
|
||||
val userWalletsIds = getUserWalletsIds().filterNot { it in userWalletIds }
|
||||
secureStorage.store(userWalletsIds.encode(), StorageKey.UserWalletIds.name)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun clear() {
|
||||
withContext(dispatchers.io) {
|
||||
val userWalletsIds = getUserWalletsIds()
|
||||
userWalletsIds.forEach { userWalletId ->
|
||||
secureStorage.delete(StorageKey.UserWalletEncryptionKeyUnsecured(userWalletId).name)
|
||||
secureStorage.delete(StorageKey.UserWalletEncryptionKeyEncrypted(userWalletId).name)
|
||||
authenticatedStorage.delete(StorageKey.UserWalletEncryptionKey(userWalletId).name)
|
||||
}
|
||||
secureStorage.delete(StorageKey.UserWalletIds.name)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getUserWalletsIds(): List<UserWalletId> {
|
||||
return withContext(dispatchers.io) {
|
||||
secureStorage.get(StorageKey.UserWalletIds.name)
|
||||
.decodeToUserWalletsIds()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun storeUserWalletId(userWalletId: UserWalletId) {
|
||||
val userWalletIds = (getUserWalletsIds() + userWalletId).distinct()
|
||||
|
||||
withContext(dispatchers.io) {
|
||||
secureStorage.store(userWalletIds.encode(), StorageKey.UserWalletIds.name)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun UserWalletEncryptionKey.encode(): ByteArray {
|
||||
return withContext(dispatchers.default) {
|
||||
this@encode
|
||||
.let(encryptionKeyAdapter::toJson)
|
||||
.encodeToByteArray(throwOnInvalidSequence = true)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun ByteArray?.decodeToKey(): UserWalletEncryptionKey? {
|
||||
return withContext(dispatchers.default) {
|
||||
this@decodeToKey
|
||||
?.decodeToString(throwOnInvalidSequence = true)
|
||||
?.let(encryptionKeyAdapter::fromJson)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun List<UserWalletId>.encode(): ByteArray {
|
||||
return withContext(dispatchers.default) {
|
||||
this@encode
|
||||
.let(userWalletsIdsListAdapter::toJson)
|
||||
.encodeToByteArray(throwOnInvalidSequence = true)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun ByteArray?.decodeToUserWalletsIds(): List<UserWalletId> {
|
||||
return withContext(dispatchers.default) {
|
||||
this@decodeToUserWalletsIds
|
||||
?.decodeToString(throwOnInvalidSequence = true)
|
||||
?.let(userWalletsIdsListAdapter::fromJson)
|
||||
.orEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
sealed class EncryptionMethod {
|
||||
data object Unsecured : EncryptionMethod()
|
||||
data object Biometric : EncryptionMethod()
|
||||
class Password(val password: CharArray) : EncryptionMethod()
|
||||
}
|
||||
|
||||
private sealed interface StorageKey {
|
||||
val name: String
|
||||
|
||||
class UserWalletEncryptionKeyUnsecured(userWalletId: UserWalletId) : StorageKey {
|
||||
override val name: String = "user_wallet_encryption_key_unsecured_${userWalletId.stringValue}"
|
||||
}
|
||||
|
||||
class UserWalletEncryptionKey(userWalletId: UserWalletId) : StorageKey {
|
||||
override val name: String = "user_wallet_encryption_key_${userWalletId.stringValue}"
|
||||
}
|
||||
|
||||
class UserWalletEncryptionKeyEncrypted(userWalletId: UserWalletId) : StorageKey {
|
||||
override val name: String = "user_wallet_encryption_key_encrypted_${userWalletId.stringValue}"
|
||||
}
|
||||
|
||||
object UserWalletIds : StorageKey {
|
||||
override val name: String = "user_wallets_ids_with_saved_keys"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11,7 +11,7 @@ import com.tangem.domain.walletconnect.WcPairService
|
|||
import com.tangem.domain.walletconnect.model.legacy.WalletConnectSessionsRepository
|
||||
import com.tangem.domain.walletconnect.usecase.initialize.WcInitializeUseCase
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase
|
||||
import com.tangem.features.walletconnect.components.WalletConnectFeatureToggles
|
||||
import com.tangem.tap.domain.walletconnect.WalletConnectSdkHelper
|
||||
import com.tangem.tap.domain.walletconnect2.app.TangemWcBlockchainHelper
|
||||
|
|
@ -42,9 +42,9 @@ internal object WalletConnectInteractorModule {
|
|||
wcSessionsRepository: WalletConnectSessionsRepository,
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
userWalletsListManager: UserWalletsListManager,
|
||||
walletConnectFeatureToggles: WalletConnectFeatureToggles,
|
||||
coroutineDispatcherProvider: CoroutineDispatcherProvider,
|
||||
getSelectedWalletUseCase: GetSelectedWalletUseCase,
|
||||
): WalletConnectInteractor {
|
||||
return WalletConnectInteractor(
|
||||
handler = WalletConnectEventsHandlerImpl(),
|
||||
|
|
@ -54,7 +54,7 @@ internal object WalletConnectInteractorModule {
|
|||
blockchainHelper = TangemWcBlockchainHelper(),
|
||||
currenciesRepository = currenciesRepository,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
getSelectedWalletUseCase = getSelectedWalletUseCase,
|
||||
dispatchers = coroutineDispatcherProvider,
|
||||
walletConnectFeatureToggles = walletConnectFeatureToggles,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import com.tangem.domain.walletconnect.model.legacy.Account
|
|||
import com.tangem.domain.walletconnect.model.legacy.Session
|
||||
import com.tangem.domain.walletconnect.model.legacy.WalletConnectSessionsRepository
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase
|
||||
import com.tangem.features.walletconnect.components.WalletConnectFeatureToggles
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
|
|
@ -38,18 +37,14 @@ class WalletConnectInteractor(
|
|||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val userWalletsListManager: UserWalletsListManager,
|
||||
private val walletConnectFeatureToggles: WalletConnectFeatureToggles,
|
||||
private val getSelectedWalletUseCase: GetSelectedWalletUseCase,
|
||||
val blockchainHelper: WcBlockchainHelper,
|
||||
) {
|
||||
private val isNewWc by lazy { walletConnectFeatureToggles.isRedesignedWalletConnectEnabled }
|
||||
|
||||
private var isWalletConnectReadyForDeepLinks = false
|
||||
|
||||
private val getSelectedWalletUseCase by lazy(LazyThreadSafetyMode.NONE) {
|
||||
GetSelectedWalletUseCase(userWalletsListManager)
|
||||
}
|
||||
|
||||
private val wcScope = CoroutineScope(
|
||||
SupervisorJob() + dispatchers.io + CoroutineExceptionHandler { _, throwable ->
|
||||
Timber.e("CoroutineException: from: LISTENER SCOPE, exception: $throwable")
|
||||
|
|
|
|||
|
|
@ -6,7 +6,9 @@ import com.tangem.common.doOnSuccess
|
|||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.domain.apptheme.model.AppThemeMode
|
||||
import com.tangem.domain.core.wallets.UserWalletsListRepository.LockMethod
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Settings
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
|
|
@ -64,6 +66,14 @@ class DetailsMiddleware {
|
|||
when (action.setting) {
|
||||
AppSetting.SaveWallets -> toggleSaveWallets(state, enable = action.enable)
|
||||
AppSetting.SaveAccessCode -> toggleSaveAccessCodes(state, enable = action.enable)
|
||||
AppSetting.RequireAccessCode -> toggleRequireAccessCode(
|
||||
state = state,
|
||||
enable = action.enable,
|
||||
)
|
||||
AppSetting.BiometricAuthentication -> toggleBiometricsAuthentication(
|
||||
state = state,
|
||||
enable = action.enable,
|
||||
)
|
||||
}
|
||||
}
|
||||
is DetailsAction.AppSettings.CheckBiometricsStatus -> {
|
||||
|
|
@ -90,6 +100,91 @@ class DetailsMiddleware {
|
|||
}
|
||||
}
|
||||
|
||||
private fun toggleBiometricsAuthentication(state: DetailsState, enable: Boolean) {
|
||||
scope.launch {
|
||||
val walletsRepository = store.inject(DaggerGraphState::walletsRepository)
|
||||
|
||||
// Nothing to change
|
||||
if (walletsRepository.useBiometricAuthentication() == enable) {
|
||||
store.dispatchWithMain(DetailsAction.AppSettings.SwitchPrivacySetting.Success)
|
||||
return@launch
|
||||
}
|
||||
|
||||
toggleRequireAccessCode(
|
||||
state = state,
|
||||
enable = true,
|
||||
)
|
||||
|
||||
if (enable) {
|
||||
setBiometricLockForAllWallets()
|
||||
} else {
|
||||
// Remove all biometric-related data
|
||||
removeAllBiometricData()
|
||||
}
|
||||
|
||||
walletsRepository.setUseBiometricAuthentication(value = enable)
|
||||
store.dispatchWithMain(DetailsAction.AppSettings.SwitchPrivacySetting.Success)
|
||||
}
|
||||
}
|
||||
|
||||
private fun toggleRequireAccessCode(state: DetailsState, enable: Boolean) {
|
||||
scope.launch {
|
||||
val walletsRepository = store.inject(DaggerGraphState::walletsRepository)
|
||||
|
||||
// Nothing to change
|
||||
if (walletsRepository.requireAccessCode() == enable) {
|
||||
store.dispatchWithMain(DetailsAction.AppSettings.SwitchPrivacySetting.Success)
|
||||
return@launch
|
||||
}
|
||||
|
||||
if (enable) {
|
||||
// Remove all biometric sign data
|
||||
removeAllBiometricSingData()
|
||||
toggleSaveAccessCodes(state, enable = false)
|
||||
} else {
|
||||
toggleSaveAccessCodes(state, enable = true)
|
||||
}
|
||||
|
||||
walletsRepository.setRequireAccessCode(value = enable)
|
||||
store.dispatchWithMain(DetailsAction.AppSettings.SwitchPrivacySetting.Success)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun setBiometricLockForAllWallets() {
|
||||
val userWalletsListRepository = store.inject(DaggerGraphState::userWalletsListRepository)
|
||||
val userWallets = userWalletsListRepository.userWalletsSync()
|
||||
userWallets.forEach {
|
||||
userWalletsListRepository.setLock(
|
||||
userWalletId = it.walletId,
|
||||
lockMethod = LockMethod.Biometric,
|
||||
changeUnsecured = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun removeAllBiometricData() {
|
||||
val userWalletsListRepository = store.inject(DaggerGraphState::userWalletsListRepository)
|
||||
userWalletsListRepository.userWalletsSync().forEach {
|
||||
userWalletsListRepository.removeBiometricLock(it.walletId)
|
||||
}
|
||||
removeAllBiometricSingData()
|
||||
}
|
||||
|
||||
private suspend fun removeAllBiometricSingData() {
|
||||
deleteSavedAccessCodes()
|
||||
val userWalletsListRepository = store.inject(DaggerGraphState::userWalletsListRepository)
|
||||
val tangemHotSdk = store.inject(DaggerGraphState::tangemHotSdk)
|
||||
userWalletsListRepository.userWalletsSync().forEach {
|
||||
if (it is UserWallet.Hot) {
|
||||
userWalletsListRepository.saveWithoutLock(
|
||||
userWallet = it.copy(
|
||||
hotWalletId = tangemHotSdk.removeBiometryAuthIfPresented(it.hotWalletId),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeBiometricsStatusChanges(scope: CoroutineScope) {
|
||||
val needEnrollBiometricsFlow = flow {
|
||||
do {
|
||||
|
|
@ -233,7 +328,7 @@ class DetailsMiddleware {
|
|||
deleteSavedAccessCodes()
|
||||
store.inject(DaggerGraphState::walletsRepository).saveShouldSaveUserWallets(item = false)
|
||||
|
||||
store.dispatchNavigationAction { replaceAll(AppRoute.Home) }
|
||||
store.dispatchNavigationAction { replaceAll(AppRoute.Home()) }
|
||||
|
||||
return CompletionResult.Success(Unit)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ private fun handlePrepareScreen(action: DetailsAction.PrepareScreen): DetailsSta
|
|||
)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
private fun handlePrivacyAction(action: DetailsAction.AppSettings, state: DetailsState): DetailsState {
|
||||
return when (action) {
|
||||
is DetailsAction.AppSettings.SwitchPrivacySetting -> state.copy(
|
||||
|
|
@ -46,6 +47,14 @@ private fun handlePrivacyAction(action: DetailsAction.AppSettings, state: Detail
|
|||
saveWallets = true, // User can't enable access codes saving without wallets saving
|
||||
saveAccessCodes = action.enable,
|
||||
)
|
||||
AppSetting.RequireAccessCode -> state.appSettingsState.copy(
|
||||
isInProgress = true,
|
||||
requireAccessCode = action.enable,
|
||||
)
|
||||
AppSetting.BiometricAuthentication -> state.appSettingsState.copy(
|
||||
isInProgress = true,
|
||||
useBiometricAuthentication = action.enable,
|
||||
)
|
||||
},
|
||||
)
|
||||
is DetailsAction.AppSettings.SwitchPrivacySetting.Success -> state.copy(
|
||||
|
|
@ -63,6 +72,14 @@ private fun handlePrivacyAction(action: DetailsAction.AppSettings, state: Detail
|
|||
isInProgress = false,
|
||||
saveAccessCodes = action.prevState,
|
||||
)
|
||||
AppSetting.RequireAccessCode -> state.appSettingsState.copy(
|
||||
isInProgress = false,
|
||||
requireAccessCode = action.prevState,
|
||||
)
|
||||
AppSetting.BiometricAuthentication -> state.appSettingsState.copy(
|
||||
isInProgress = false,
|
||||
needEnrollBiometrics = action.prevState,
|
||||
)
|
||||
},
|
||||
)
|
||||
is DetailsAction.AppSettings.BiometricsStatusChanged -> state.copy(
|
||||
|
|
|
|||
|
|
@ -12,9 +12,14 @@ data class DetailsState(
|
|||
) : StateType
|
||||
|
||||
data class AppSettingsState(
|
||||
@Deprecated("Delete after hot wallet release")
|
||||
val saveWallets: Boolean = false,
|
||||
@Deprecated("Delete after hot wallet release")
|
||||
val saveAccessCodes: Boolean = false,
|
||||
@Deprecated("Delete after hot wallet release")
|
||||
val isBiometricsAvailable: Boolean = false,
|
||||
val requireAccessCode: Boolean = false,
|
||||
val useBiometricAuthentication: Boolean = false,
|
||||
val needEnrollBiometrics: Boolean = false,
|
||||
val isHidingEnabled: Boolean = false,
|
||||
val isInProgress: Boolean = false,
|
||||
|
|
@ -25,5 +30,5 @@ data class AppSettingsState(
|
|||
enum class SecurityOption { LongTap, PassCode, AccessCode }
|
||||
|
||||
enum class AppSetting {
|
||||
SaveWallets, SaveAccessCode
|
||||
SaveWallets, SaveAccessCode, RequireAccessCode, BiometricAuthentication,
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.tap.features.details.ui.appsettings
|
||||
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.domain.apptheme.model.AppThemeMode
|
||||
import com.tangem.tap.features.details.ui.appsettings.AppSettingsScreenState.Dialog
|
||||
import com.tangem.wallet.R
|
||||
|
|
@ -55,4 +56,37 @@ internal class AppSettingsDialogsFactory {
|
|||
onDismiss = onDismiss,
|
||||
)
|
||||
}
|
||||
|
||||
fun createDisableBiometricAuthenticationAlert(onDisable: () -> Unit, onDismiss: () -> Unit): Dialog.Alert {
|
||||
return Dialog.Alert(
|
||||
title = resourceReference(R.string.common_attention),
|
||||
description = resourceReference(
|
||||
R.string.app_settings_off_biometrics_alert_message,
|
||||
wrappedList(resourceReference(R.string.common_biometrics)),
|
||||
),
|
||||
confirmText = resourceReference(R.string.common_disable),
|
||||
onConfirm = onDisable,
|
||||
onDismiss = onDismiss,
|
||||
)
|
||||
}
|
||||
|
||||
fun createEnableRequireAccessCodeAlert(onEnable: () -> Unit, onDismiss: () -> Unit): Dialog.Alert {
|
||||
return Dialog.Alert(
|
||||
title = resourceReference(R.string.common_attention),
|
||||
description = resourceReference(R.string.app_settings_on_require_access_code_alert_message),
|
||||
confirmText = resourceReference(R.string.common_enable),
|
||||
onConfirm = { onEnable() },
|
||||
onDismiss = onDismiss,
|
||||
)
|
||||
}
|
||||
|
||||
fun createDisableRequireAccessCodeAlert(onDisable: () -> Unit, onDismiss: () -> Unit): Dialog.Alert {
|
||||
return Dialog.Alert(
|
||||
title = resourceReference(R.string.common_attention),
|
||||
description = resourceReference(R.string.app_settings_off_require_access_code_alert_message),
|
||||
confirmText = resourceReference(R.string.common_disable),
|
||||
onConfirm = { onDisable() },
|
||||
onDismiss = onDismiss,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.tangem.tap.features.details.ui.appsettings
|
|||
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.domain.apptheme.model.AppThemeMode
|
||||
import com.tangem.tap.features.details.ui.appsettings.AppSettingsScreenState.Item
|
||||
import com.tangem.wallet.R
|
||||
|
|
@ -33,6 +34,39 @@ internal class AppSettingsItemsFactory {
|
|||
)
|
||||
}
|
||||
|
||||
fun createUseBiometricsSwitch(
|
||||
isChecked: Boolean,
|
||||
isEnabled: Boolean,
|
||||
onCheckedChange: (Boolean) -> Unit,
|
||||
): Item.Switch {
|
||||
return Item.Switch(
|
||||
id = ID_USE_BIOMETRICS_SWITCH,
|
||||
title = resourceReference(R.string.app_settings_enable_biometrics_title),
|
||||
description = resourceReference(
|
||||
R.string.app_settings_biometrics_footer,
|
||||
wrappedList(resourceReference(R.string.common_biometrics)),
|
||||
),
|
||||
isEnabled = isEnabled,
|
||||
isChecked = isChecked,
|
||||
onCheckedChange = onCheckedChange,
|
||||
)
|
||||
}
|
||||
|
||||
fun createRequireAccessCodeSwitch(
|
||||
isChecked: Boolean,
|
||||
isEnabled: Boolean,
|
||||
onCheckedChange: (Boolean) -> Unit,
|
||||
): Item.Switch {
|
||||
return Item.Switch(
|
||||
id = ID_REQUIRE_ACCESS_CODE_SWITCH,
|
||||
title = resourceReference(R.string.app_settings_require_access_code),
|
||||
description = resourceReference(R.string.app_settings_require_access_code_footer),
|
||||
isEnabled = isEnabled,
|
||||
isChecked = isChecked,
|
||||
onCheckedChange = onCheckedChange,
|
||||
)
|
||||
}
|
||||
|
||||
fun createSaveAccessCodeSwitch(
|
||||
isChecked: Boolean,
|
||||
isEnabled: Boolean,
|
||||
|
|
@ -96,5 +130,7 @@ internal class AppSettingsItemsFactory {
|
|||
const val ID_FLIP_TO_HIDE_BALANCE_SWITCH = "flip_to_hide_balance_switch"
|
||||
const val ID_SELECT_APP_CURRENCY_BUTTON = "select_app_currency_button"
|
||||
const val ID_SELECT_THEME_MODE_BUTTON = "select_theme_mode_button"
|
||||
const val ID_USE_BIOMETRICS_SWITCH = "use_biometrics_switch"
|
||||
const val ID_REQUIRE_ACCESS_CODE_SWITCH = "require_access_code_switch"
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository
|
|||
import com.tangem.domain.settings.CanUseBiometryUseCase
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Settings
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
|
|
@ -51,6 +52,7 @@ internal class AppSettingsModel @Inject constructor(
|
|||
private val appThemeModeRepository: AppThemeModeRepository,
|
||||
private val settingsRepository: SettingsRepository,
|
||||
private val appSettingsItemsAnalyticsSender: AppSettingsItemsAnalyticsSender,
|
||||
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
) : Model(), StoreSubscriber<DetailsState> {
|
||||
|
||||
private val itemsFactory = AppSettingsItemsFactory()
|
||||
|
|
@ -109,20 +111,36 @@ internal class AppSettingsModel @Inject constructor(
|
|||
onClick = ::showAppCurrencySelector,
|
||||
).let(::add)
|
||||
|
||||
if (state.isBiometricsAvailable) {
|
||||
if (hotWalletFeatureToggles.isHotWalletEnabled) {
|
||||
val canUseBiometrics = !state.needEnrollBiometrics && !state.isInProgress
|
||||
|
||||
itemsFactory.createSaveWalletsSwitch(
|
||||
isChecked = state.saveWallets,
|
||||
itemsFactory.createUseBiometricsSwitch(
|
||||
isChecked = state.useBiometricAuthentication,
|
||||
isEnabled = canUseBiometrics,
|
||||
onCheckedChange = ::onSaveWalletsToggled,
|
||||
onCheckedChange = ::onBiometricAuthenticationToggled,
|
||||
).let(::add)
|
||||
|
||||
itemsFactory.createSaveAccessCodeSwitch(
|
||||
isChecked = state.saveAccessCodes,
|
||||
isEnabled = canUseBiometrics,
|
||||
onCheckedChange = ::onSaveAccessCodesToggled,
|
||||
itemsFactory.createRequireAccessCodeSwitch(
|
||||
isChecked = state.requireAccessCode,
|
||||
isEnabled = canUseBiometrics && state.useBiometricAuthentication,
|
||||
onCheckedChange = ::onRequireAccessCodeToggled,
|
||||
).let(::add)
|
||||
} else {
|
||||
if (state.isBiometricsAvailable) {
|
||||
val canUseBiometrics = !state.needEnrollBiometrics && !state.isInProgress
|
||||
|
||||
itemsFactory.createSaveWalletsSwitch(
|
||||
isChecked = state.saveWallets,
|
||||
isEnabled = canUseBiometrics,
|
||||
onCheckedChange = ::onSaveWalletsToggled,
|
||||
).let(::add)
|
||||
|
||||
itemsFactory.createSaveAccessCodeSwitch(
|
||||
isChecked = state.saveAccessCodes,
|
||||
isEnabled = canUseBiometrics,
|
||||
onCheckedChange = ::onSaveAccessCodesToggled,
|
||||
).let(::add)
|
||||
}
|
||||
}
|
||||
|
||||
itemsFactory.createFlipToHideBalanceSwitch(
|
||||
|
|
@ -168,6 +186,56 @@ internal class AppSettingsModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun onBiometricAuthenticationToggled(isChecked: Boolean) {
|
||||
// TODO : Uncomment and implement analytics event when ready
|
||||
// val param = AnalyticsParam.OnOffState(isChecked)
|
||||
// analyticsEventHandler.send(Settings.AppSettings.BiometricAuthenticationChanged(param))
|
||||
if (isChecked) {
|
||||
onSettingsToggled(AppSetting.BiometricAuthentication, enable = true)
|
||||
onSettingsToggled(AppSetting.RequireAccessCode, enable = true)
|
||||
} else {
|
||||
updateContentState {
|
||||
copy(
|
||||
dialog = dialogsFactory.createDisableBiometricAuthenticationAlert(
|
||||
onDisable = {
|
||||
onSettingsToggled(AppSetting.BiometricAuthentication, enable = false)
|
||||
onSettingsToggled(AppSetting.RequireAccessCode, enable = true)
|
||||
dismissDialog()
|
||||
},
|
||||
onDismiss = ::dismissDialog,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onRequireAccessCodeToggled(isChecked: Boolean) {
|
||||
// TODO : Uncomment and implement analytics event when ready
|
||||
// val param = AnalyticsParam.OnOffState(isChecked)
|
||||
// analyticsEventHandler.send(Settings.AppSettings.RequireAccessCodeChanged(param))
|
||||
updateContentState {
|
||||
copy(
|
||||
dialog = if (isChecked) {
|
||||
dialogsFactory.createEnableRequireAccessCodeAlert(
|
||||
onEnable = {
|
||||
onSettingsToggled(AppSetting.RequireAccessCode, enable = true)
|
||||
dismissDialog()
|
||||
},
|
||||
onDismiss = ::dismissDialog,
|
||||
)
|
||||
} else {
|
||||
dialogsFactory.createDisableRequireAccessCodeAlert(
|
||||
onDisable = {
|
||||
onSettingsToggled(AppSetting.RequireAccessCode, enable = false)
|
||||
dismissDialog()
|
||||
},
|
||||
onDismiss = ::dismissDialog,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onSaveWalletsToggled(isChecked: Boolean) {
|
||||
if (isChecked) {
|
||||
onSettingsToggled(AppSetting.SaveWallets, enable = true)
|
||||
|
|
@ -236,6 +304,8 @@ internal class AppSettingsModel @Inject constructor(
|
|||
saveWallets = walletsRepository.shouldSaveUserWalletsSync(),
|
||||
saveAccessCodes = settingsRepository.shouldSaveAccessCodes(),
|
||||
isBiometricsAvailable = canUseBiometryUseCase(),
|
||||
useBiometricAuthentication = walletsRepository.useBiometricAuthentication(),
|
||||
requireAccessCode = walletsRepository.requireAccessCode(),
|
||||
isHidingEnabled = balanceHidingRepository.getBalanceHidingSettings().isHidingEnabledInSettings,
|
||||
selectedAppCurrency = appCurrencyRepository.getSelectedAppCurrency().firstOrNull() ?: AppCurrency.Default,
|
||||
selectedThemeMode = appThemeModeRepository.getAppThemeMode().firstOrNull() ?: AppThemeMode.DEFAULT,
|
||||
|
|
|
|||
|
|
@ -87,9 +87,10 @@ internal class CardSettingsModel @Inject constructor(
|
|||
|
||||
val userWallet = getUserWalletUseCase(userWalletId)
|
||||
.getOrElse { error("User wallet $userWalletId not found") }
|
||||
.requireColdWallet()
|
||||
|
||||
cardSdkConfigRepository.isBiometricsRequestPolicy =
|
||||
userWallet.requireColdWallet().scanResponse.card.isAccessCodeSet && // TODO [REDACTED_TASK_KEY]
|
||||
userWallet.scanResponse.card.isAccessCodeSet &&
|
||||
settingsRepository.shouldSaveAccessCodes()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -268,7 +268,7 @@ internal class ResetCardModel @Inject constructor(
|
|||
if (isLocked && userWalletsListManager.hasUserWallets) {
|
||||
store.dispatchNavigationAction { popTo<AppRoute.Welcome>() }
|
||||
} else {
|
||||
store.dispatchNavigationAction { replaceAll(AppRoute.Home) }
|
||||
store.dispatchNavigationAction { replaceAll(AppRoute.Home()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,90 +0,0 @@
|
|||
package com.tangem.tap.features.home
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import com.arkivanov.essenty.lifecycle.subscribe
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.ui.components.SystemBarsIconsDisposable
|
||||
import com.tangem.core.ui.utils.ChangeRootBackgroundColorEffect
|
||||
import com.tangem.core.ui.utils.findActivity
|
||||
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.features.home.api.HomeComponent
|
||||
import com.tangem.tap.features.home.compose.StoriesScreen
|
||||
import com.tangem.tap.features.home.compose.StoriesScreenV2
|
||||
import com.tangem.tap.features.home.redux.HomeAction
|
||||
import com.tangem.tap.features.home.redux.HomeState
|
||||
import com.tangem.tap.store
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import org.rekotlin.StoreSubscriber
|
||||
|
||||
@Suppress("UnusedPrivateMember")
|
||||
internal class DefaultHomeComponent @AssistedInject constructor(
|
||||
@Assisted appComponentContext: AppComponentContext,
|
||||
@Assisted params: Unit,
|
||||
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
) : HomeComponent, AppComponentContext by appComponentContext, StoreSubscriber<HomeState> {
|
||||
|
||||
private val model: HomeModel = getOrCreateModel()
|
||||
|
||||
private var homeState: MutableState<HomeState> = mutableStateOf(store.state.homeState)
|
||||
|
||||
init {
|
||||
lifecycle.subscribe(
|
||||
onCreate = {
|
||||
store.dispatch(HomeAction.OnCreate)
|
||||
},
|
||||
onStart = {
|
||||
store.subscribe(subscriber = this) { state ->
|
||||
state
|
||||
.skipRepeats { oldState, newState -> oldState.homeState == newState.homeState }
|
||||
.select(AppState::homeState)
|
||||
}
|
||||
},
|
||||
onStop = {
|
||||
store.unsubscribe(this)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val activity = LocalContext.current.findActivity()
|
||||
BackHandler(onBack = activity::finish)
|
||||
SystemBarsIconsDisposable(darkIcons = false)
|
||||
if (hotWalletFeatureToggles.isHotWalletEnabled) {
|
||||
StoriesScreenV2(
|
||||
homeState = homeState,
|
||||
onCreateNewWalletButtonClick = model::onCreateNewWalletScreen,
|
||||
onAddExistingWalletButtonClick = model::onAddExistingWalletScreen,
|
||||
onScanButtonClick = model::onScanClick,
|
||||
)
|
||||
} else {
|
||||
StoriesScreen(
|
||||
homeState = homeState,
|
||||
onScanButtonClick = model::onScanClick,
|
||||
onShopButtonClick = model::onShopClick,
|
||||
onSearchTokensClick = model::onSearchClick,
|
||||
)
|
||||
}
|
||||
|
||||
ChangeRootBackgroundColorEffect(Color(color = 0xFF010101))
|
||||
}
|
||||
|
||||
override fun newState(state: HomeState) {
|
||||
homeState.value = state
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : HomeComponent.Factory {
|
||||
override fun create(context: AppComponentContext, params: Unit): DefaultHomeComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -1,166 +0,0 @@
|
|||
package com.tangem.tap.features.home
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.google.firebase.analytics.ktx.analytics
|
||||
import com.google.firebase.ktx.Firebase
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.AppRoute.ManageTokens.Source
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.Basic
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.core.navigation.url.UrlOpener
|
||||
import com.tangem.domain.card.ScanCardProcessor
|
||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
|
||||
import com.tangem.domain.settings.usercountry.models.UserCountry
|
||||
import com.tangem.domain.tokens.TokensAction
|
||||
import com.tangem.domain.wallets.builder.ColdUserWalletBuilder
|
||||
import com.tangem.domain.wallets.usecase.SaveWalletUseCase
|
||||
import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter
|
||||
import com.tangem.tap.common.analytics.events.IntroductionProcess
|
||||
import com.tangem.tap.common.analytics.events.Shop
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.dispatchWithMain
|
||||
import com.tangem.tap.common.extensions.onUserWalletSelected
|
||||
import com.tangem.tap.features.home.redux.HIDE_PROGRESS_DELAY
|
||||
import com.tangem.tap.features.home.redux.HomeAction
|
||||
import com.tangem.tap.features.home.redux.HomeMiddleware.NEW_BUY_WALLET_URL
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Stable
|
||||
@ModelScoped
|
||||
internal class HomeModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val scanCardProcessor: ScanCardProcessor,
|
||||
private val saveWalletUseCase: SaveWalletUseCase,
|
||||
private val cardSdkConfigRepository: CardSdkConfigRepository,
|
||||
private val settingsRepository: SettingsRepository,
|
||||
private val urlOpener: UrlOpener,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val coldUserWalletBuilderFactory: ColdUserWalletBuilder.Factory,
|
||||
private val router: Router,
|
||||
getUserCountryUseCase: GetUserCountryUseCase,
|
||||
) : Model() {
|
||||
|
||||
private val tangemErrorHandler = TangemTangemErrorsHandler(store)
|
||||
|
||||
init {
|
||||
getUserCountryUseCase.invoke()
|
||||
.distinctUntilChanged()
|
||||
.filterNotNull()
|
||||
.onEach {
|
||||
val userCountry = it.getOrNull() ?: UserCountry.Other(Locale.getDefault().country)
|
||||
store.dispatchOnMain(HomeAction.UserCountryLoaded(userCountry))
|
||||
}
|
||||
.flowOn(dispatchers.io)
|
||||
.launchIn(modelScope)
|
||||
}
|
||||
|
||||
fun onCreateNewWalletScreen() {
|
||||
router.push(AppRoute.CreateWalletSelection)
|
||||
}
|
||||
|
||||
fun onAddExistingWalletScreen() {
|
||||
router.push(AppRoute.AddExistingWallet)
|
||||
}
|
||||
|
||||
fun onScanClick() {
|
||||
analyticsEventHandler.send(IntroductionProcess.ButtonScanCard())
|
||||
scanCard()
|
||||
}
|
||||
|
||||
fun onShopClick() {
|
||||
analyticsEventHandler.send(IntroductionProcess.ButtonBuyCards())
|
||||
analyticsEventHandler.send(Shop.ScreenOpened())
|
||||
|
||||
Firebase.analytics.appInstanceId
|
||||
.addOnSuccessListener { urlOpener.openUrl(url = "$NEW_BUY_WALLET_URL&app_instance_id=$it") }
|
||||
.addOnFailureListener { urlOpener.openUrl(url = NEW_BUY_WALLET_URL) }
|
||||
}
|
||||
|
||||
fun onSearchClick() {
|
||||
analyticsEventHandler.send(IntroductionProcess.ButtonTokensList())
|
||||
|
||||
store.dispatch(TokensAction.SetArgs.ReadAccess)
|
||||
store.dispatchNavigationAction { push(AppRoute.ManageTokens(Source.STORIES)) }
|
||||
}
|
||||
|
||||
private fun scanCard() {
|
||||
modelScope.launch {
|
||||
cardSdkConfigRepository.isBiometricsRequestPolicy = settingsRepository.shouldSaveAccessCodes()
|
||||
|
||||
scanCardProcessor.scan(
|
||||
analyticsSource = AnalyticsParam.ScreensSources.Intro,
|
||||
onProgressStateChange = { showProgress ->
|
||||
if (showProgress) {
|
||||
store.dispatch(HomeAction.ScanInProgress(scanInProgress = true))
|
||||
} else {
|
||||
delay(HIDE_PROGRESS_DELAY)
|
||||
store.dispatch(HomeAction.ScanInProgress(scanInProgress = false))
|
||||
}
|
||||
},
|
||||
onFailure = {
|
||||
tangemErrorHandler.onErrorReceived(error = it)
|
||||
delay(HIDE_PROGRESS_DELAY)
|
||||
store.dispatch(HomeAction.ScanInProgress(scanInProgress = false))
|
||||
},
|
||||
onSuccess = ::proceedWithScanResponse,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun proceedWithScanResponse(scanResponse: ScanResponse) {
|
||||
val userWallet = coldUserWalletBuilderFactory.create(scanResponse = scanResponse).build()
|
||||
|
||||
if (userWallet == null) {
|
||||
Timber.e("User wallet not created")
|
||||
return
|
||||
}
|
||||
|
||||
saveWalletUseCase(userWallet).fold(
|
||||
ifLeft = { Timber.e(it.toString(), "Unable to save user wallet") },
|
||||
ifRight = {
|
||||
sendSignedInCardAnalyticsEvent(scanResponse)
|
||||
coroutineScope { store.onUserWalletSelected(userWallet = userWallet) }
|
||||
},
|
||||
)
|
||||
|
||||
store.dispatchWithMain(HomeAction.ScanInProgress(scanInProgress = false))
|
||||
delay(HIDE_PROGRESS_DELAY)
|
||||
|
||||
store.dispatchNavigationAction { replaceAll(AppRoute.Wallet) }
|
||||
}
|
||||
|
||||
private fun sendSignedInCardAnalyticsEvent(scanResponse: ScanResponse) {
|
||||
val currency = ParamCardCurrencyConverter().convert(value = scanResponse.cardTypesResolver)
|
||||
|
||||
if (currency != null) {
|
||||
Analytics.send(
|
||||
event = Basic.SignedIn(
|
||||
currency = currency,
|
||||
batch = scanResponse.card.batchId,
|
||||
signInType = Basic.SignedIn.SignInType.Card,
|
||||
walletsCount = "1",
|
||||
hasBackup = scanResponse.card.backupStatus?.isActive,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
package com.tangem.tap.features.home
|
||||
|
||||
import androidx.compose.ui.text.intl.Locale
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface RegionProvider {
|
||||
fun getRegion(): String?
|
||||
}
|
||||
|
||||
class LocaleRegionProvider : RegionProvider {
|
||||
override fun getRegion(): String = Locale.current.region
|
||||
}
|
||||
|
||||
const val RUSSIA_COUNTRY_CODE = "ru"
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
package com.tangem.tap.features.home
|
||||
|
||||
import com.tangem.blockchain.common.BlockchainError
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.domain.redux.StateDialog
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.home.errors.TangemSdkErrorHandler
|
||||
import org.rekotlin.Store
|
||||
import timber.log.Timber
|
||||
|
||||
class TangemTangemErrorsHandler(val store: Store<AppState>) : TangemSdkErrorHandler {
|
||||
|
||||
override fun onErrorReceived(error: TangemError) {
|
||||
when (error) {
|
||||
is TangemSdkError -> {
|
||||
handleCardSdkError(error)
|
||||
}
|
||||
is BlockchainError -> {
|
||||
handleBlockchainSdkError(error)
|
||||
}
|
||||
else -> {
|
||||
Timber.e("Error happened", error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleCardSdkError(error: TangemSdkError) {
|
||||
when (error) {
|
||||
is TangemSdkError.NfcFeatureIsUnavailable -> {
|
||||
store.dispatchOnMain(GlobalAction.ShowDialog(StateDialog.NfcFeatureIsUnavailable))
|
||||
}
|
||||
else -> {
|
||||
Timber.e(error, "Unable to scan card")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleBlockchainSdkError(error: TangemError) {
|
||||
Timber.e("Sdk error happened", error)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
package com.tangem.tap.features.home.api
|
||||
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
|
||||
interface HomeComponent : ComposableContentComponent {
|
||||
|
||||
interface Factory : ComponentFactory<Unit, HomeComponent>
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
package com.tangem.tap.features.home.di
|
||||
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.tap.features.home.DefaultHomeComponent
|
||||
import com.tangem.tap.features.home.HomeModel
|
||||
import com.tangem.tap.features.home.api.HomeComponent
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import dagger.multibindings.ClassKey
|
||||
import dagger.multibindings.IntoMap
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal interface HomeFeatureModule {
|
||||
|
||||
@Binds
|
||||
fun bindFactory(impl: DefaultHomeComponent.Factory): HomeComponent.Factory
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(HomeModel::class)
|
||||
fun bindModel(model: HomeModel): Model
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
package com.tangem.tap.features.home.errors
|
||||
|
||||
import com.tangem.common.core.TangemError
|
||||
|
||||
interface TangemSdkErrorHandler {
|
||||
|
||||
fun onErrorReceived(error: TangemError)
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
package com.tangem.tap.features.home.redux
|
||||
|
||||
import com.tangem.domain.settings.usercountry.models.UserCountry
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import org.rekotlin.Action
|
||||
|
||||
sealed class HomeAction : Action {
|
||||
|
||||
data object OnCreate : HomeAction()
|
||||
|
||||
/**
|
||||
* Action for scanning card
|
||||
*
|
||||
* @property scope lifecycle scope. It will be canceled when lifecycle-aware component is destroyed
|
||||
*/
|
||||
data class ReadCard(val scope: CoroutineScope) : HomeAction()
|
||||
|
||||
data class ScanInProgress(val scanInProgress: Boolean) : HomeAction()
|
||||
|
||||
data class UserCountryLoaded(val userCountry: UserCountry) : HomeAction()
|
||||
}
|
||||
|
|
@ -1,147 +0,0 @@
|
|||
package com.tangem.tap.features.home.redux
|
||||
|
||||
import android.content.res.Resources
|
||||
import com.tangem.common.doOnFailure
|
||||
import com.tangem.common.doOnResult
|
||||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.Basic
|
||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter
|
||||
import com.tangem.tap.common.analytics.events.IntroductionProcess
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
import com.tangem.tap.common.extensions.eraseContext
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.common.extensions.onUserWalletSelected
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Action
|
||||
import org.rekotlin.Middleware
|
||||
import timber.log.Timber
|
||||
import java.util.Locale
|
||||
|
||||
internal const val HIDE_PROGRESS_DELAY = 400L
|
||||
|
||||
object HomeMiddleware {
|
||||
val handler = homeMiddleware
|
||||
|
||||
private val SYSTEM_LANGUAGE =
|
||||
runCatching { Resources.getSystem().configuration.locales[0].language }.getOrElse { "" }
|
||||
private val APP_LANGUAGE = Locale.getDefault().language
|
||||
private val UTM_MARKS = "utm_source=tangem-app" +
|
||||
"&utm_medium=app" +
|
||||
"&utm_campaign=prospect-$SYSTEM_LANGUAGE" +
|
||||
"&utm_content=devicelang-$APP_LANGUAGE"
|
||||
|
||||
val NEW_BUY_WALLET_URL = "https://buy.tangem.com/?$UTM_MARKS"
|
||||
}
|
||||
|
||||
private val homeMiddleware: Middleware<AppState> = { _, _ ->
|
||||
{ next ->
|
||||
{ action ->
|
||||
handleHomeAction(action)
|
||||
next(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleHomeAction(action: Action) {
|
||||
when (action) {
|
||||
is HomeAction.OnCreate -> {
|
||||
Analytics.eraseContext()
|
||||
Analytics.send(IntroductionProcess.ScreenOpened())
|
||||
|
||||
store.dispatch(GlobalAction.RestoreAppCurrency)
|
||||
}
|
||||
is HomeAction.ReadCard -> {
|
||||
action.scope.launch {
|
||||
readCard()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun readCard() {
|
||||
val shouldSaveAccessCodes = store.inject(DaggerGraphState::settingsRepository).shouldSaveAccessCodes()
|
||||
|
||||
store.inject(DaggerGraphState::cardSdkConfigRepository).setAccessCodeRequestPolicy(
|
||||
isBiometricsRequestPolicy = shouldSaveAccessCodes,
|
||||
)
|
||||
|
||||
store.inject(DaggerGraphState::scanCardProcessor).scan(
|
||||
analyticsSource = AnalyticsParam.ScreensSources.Intro,
|
||||
onProgressStateChange = { showProgress ->
|
||||
if (showProgress) {
|
||||
store.dispatch(HomeAction.ScanInProgress(scanInProgress = true))
|
||||
} else {
|
||||
delay(HIDE_PROGRESS_DELAY)
|
||||
store.dispatch(HomeAction.ScanInProgress(scanInProgress = false))
|
||||
}
|
||||
},
|
||||
onFailure = {
|
||||
Timber.e(it, "Unable to scan card")
|
||||
delay(HIDE_PROGRESS_DELAY)
|
||||
store.dispatch(HomeAction.ScanInProgress(scanInProgress = false))
|
||||
},
|
||||
onSuccess = { scanResponse ->
|
||||
proceedWithScanResponse(scanResponse)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun proceedWithScanResponse(scanResponse: ScanResponse) = scope.launch {
|
||||
val userWalletBuilder = store.inject(DaggerGraphState::coldUserWalletBuilderFactory).create(scanResponse)
|
||||
|
||||
val userWallet = userWalletBuilder.build().guard {
|
||||
Timber.e("User wallet not created")
|
||||
return@launch
|
||||
}
|
||||
|
||||
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
|
||||
userWalletsListManager.save(userWallet)
|
||||
.doOnFailure { error ->
|
||||
Timber.e(error, "Unable to save user wallet")
|
||||
}
|
||||
.doOnSuccess {
|
||||
sendSignedInCardAnalyticsEvent(scanResponse)
|
||||
store.onUserWalletSelected(userWallet = userWallet)
|
||||
}
|
||||
.doOnResult {
|
||||
navigateTo(AppRoute.Wallet)
|
||||
}
|
||||
}
|
||||
|
||||
private fun sendSignedInCardAnalyticsEvent(scanResponse: ScanResponse) {
|
||||
val currency = ParamCardCurrencyConverter().convert(
|
||||
value = scanResponse.cardTypesResolver,
|
||||
)
|
||||
|
||||
if (currency != null) {
|
||||
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
|
||||
|
||||
Analytics.send(
|
||||
event = Basic.SignedIn(
|
||||
currency = currency,
|
||||
batch = scanResponse.card.batchId,
|
||||
signInType = Basic.SignedIn.SignInType.Card,
|
||||
walletsCount = userWalletsListManager.walletsCount.toString(),
|
||||
hasBackup = scanResponse.card.backupStatus?.isActive,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun navigateTo(route: AppRoute) {
|
||||
store.dispatchNavigationAction { push(route) }
|
||||
delay(HIDE_PROGRESS_DELAY)
|
||||
store.dispatch(HomeAction.ScanInProgress(scanInProgress = false))
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
package com.tangem.tap.features.home.redux
|
||||
|
||||
import com.tangem.domain.settings.usercountry.models.needApplyFCARestrictions
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import org.rekotlin.Action
|
||||
|
||||
object HomeReducer {
|
||||
fun reduce(action: Action, state: AppState): HomeState = internalReduce(action, state)
|
||||
}
|
||||
|
||||
private fun internalReduce(action: Action, appState: AppState): HomeState {
|
||||
if (action !is HomeAction) return appState.homeState
|
||||
|
||||
return when (action) {
|
||||
is HomeAction.ScanInProgress -> {
|
||||
appState.homeState.copy(scanInProgress = action.scanInProgress)
|
||||
}
|
||||
is HomeAction.UserCountryLoaded -> {
|
||||
val stories = if (action.userCountry.needApplyFCARestrictions()) {
|
||||
getRestrictedStories()
|
||||
} else {
|
||||
Stories.entries
|
||||
}
|
||||
appState.homeState.copy(
|
||||
stories = stories.toImmutableList(),
|
||||
)
|
||||
}
|
||||
else -> appState.homeState
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
package com.tangem.tap.features.hot
|
||||
|
||||
import com.tangem.hot.sdk.model.HotAuth
|
||||
import com.tangem.hot.sdk.model.HotWalletId
|
||||
import com.tangem.tap.domain.hot.HotWalletPasswordRequester
|
||||
import javax.inject.Inject
|
||||
|
||||
class DefaultHotWalletPasswordRequester @Inject constructor() : HotWalletPasswordRequester {
|
||||
|
||||
override suspend fun requestPassword(hotWalletId: HotWalletId): HotAuth.Password {
|
||||
return HotAuth.Password("TODO [REDACTED_TASK_KEY]".toCharArray()) // TODO [REDACTED_TASK_KEY]
|
||||
}
|
||||
}
|
||||
|
|
@ -37,6 +37,9 @@ class TangemHotSDKProxy @Inject constructor() : TangemHotSdk {
|
|||
override suspend fun changeAuth(unlockHotWallet: UnlockHotWallet, auth: HotAuth): HotWalletId =
|
||||
callSdk { changeAuth(unlockHotWallet, auth) }
|
||||
|
||||
override suspend fun removeBiometryAuthIfPresented(id: HotWalletId): HotWalletId =
|
||||
callSdk { removeBiometryAuthIfPresented(id) }
|
||||
|
||||
override suspend fun derivePublicKey(
|
||||
unlockHotWallet: UnlockHotWallet,
|
||||
request: DeriveWalletRequest,
|
||||
|
|
|
|||
|
|
@ -4,21 +4,12 @@ import android.content.Intent
|
|||
import android.nfc.NfcAdapter
|
||||
import android.nfc.Tag
|
||||
import android.os.Build
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.features.home.redux.HomeAction
|
||||
import com.tangem.tap.features.intentHandler.IntentHandler
|
||||
import com.tangem.tap.features.intentHandler.AffectsNavigation
|
||||
import com.tangem.tap.features.welcome.redux.WelcomeAction
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import com.tangem.common.routing.entity.InitScreenLaunchMode
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class BackgroundScanIntentHandler(
|
||||
private val hasSavedUserWalletsProvider: () -> Boolean,
|
||||
private val scope: CoroutineScope,
|
||||
) : IntentHandler, AffectsNavigation {
|
||||
class BackgroundScanIntentHandler {
|
||||
|
||||
private val nfcActions = arrayOf(
|
||||
NfcAdapter.ACTION_NDEF_DISCOVERED,
|
||||
|
|
@ -26,8 +17,15 @@ class BackgroundScanIntentHandler(
|
|||
NfcAdapter.ACTION_TAG_DISCOVERED,
|
||||
)
|
||||
|
||||
override fun handleIntent(intent: Intent?, isFromForeground: Boolean): Boolean {
|
||||
if (isFromForeground) return true
|
||||
fun getInitScreenLaunchMode(intent: Intent?): InitScreenLaunchMode {
|
||||
return if (shouldOpenScanCard(intent)) {
|
||||
InitScreenLaunchMode.WithCardScan
|
||||
} else {
|
||||
InitScreenLaunchMode.Standard
|
||||
}
|
||||
}
|
||||
|
||||
private fun shouldOpenScanCard(intent: Intent?): Boolean {
|
||||
if (intent == null || intent.action !in nfcActions) return false
|
||||
|
||||
val tag: Tag? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
|
|
@ -36,15 +34,9 @@ class BackgroundScanIntentHandler(
|
|||
@Suppress("DEPRECATION")
|
||||
intent.getParcelableExtra(NfcAdapter.EXTRA_TAG)
|
||||
}
|
||||
if (tag == null) return false
|
||||
|
||||
intent.action = null
|
||||
if (hasSavedUserWalletsProvider.invoke()) {
|
||||
store.dispatchOnMain(WelcomeAction.ProceedWithCard)
|
||||
} else {
|
||||
store.dispatchOnMain(HomeAction.ReadCard(scope = scope))
|
||||
}
|
||||
|
||||
return true
|
||||
return tag != null
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue