Updated on 2026-08-14

This commit is contained in:
Tangem 2025-01-06 15:37:52 +03:00
parent e8af92b599
commit 5d892618a8
7 changed files with 284 additions and 42 deletions

View file

@ -22,6 +22,7 @@ import com.tangem.datasource.asset.loader.AssetLoader
import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.datasource.local.walletmanager.WalletManagersStore import com.tangem.datasource.local.walletmanager.WalletManagersStore
import com.tangem.domain.common.util.hasDerivation import com.tangem.domain.common.util.hasDerivation
import com.tangem.domain.demo.BuildConfig
import com.tangem.domain.demo.DemoConfig import com.tangem.domain.demo.DemoConfig
import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.model.Network
@ -369,7 +370,7 @@ class DefaultWalletManagersFacade(
blockchain = blockchain, blockchain = blockchain,
derivationPath = derivationPath, derivationPath = derivationPath,
) )
if (walletManager == null) { if (BuildConfig.DEBUG || walletManager == null) {
walletManager = walletManagerFactory.createWalletManager( walletManager = walletManagerFactory.createWalletManager(
scanResponse = userWallet.scanResponse, scanResponse = userWallet.scanResponse,
blockchain = blockchain, blockchain = blockchain,

View file

@ -1,5 +1,7 @@
package com.tangem.feature.tester.presentation.providers.entity package com.tangem.feature.tester.presentation.providers.entity
import androidx.compose.runtime.Immutable
import com.tangem.blockchain.common.network.providers.ProviderType
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
internal data class BlockchainProvidersUM( internal data class BlockchainProvidersUM(
@ -7,13 +9,22 @@ internal data class BlockchainProvidersUM(
val blockchainProviders: ImmutableList<ProvidersUM>, val blockchainProviders: ImmutableList<ProvidersUM>,
) { ) {
@Immutable
data class ProvidersUM( data class ProvidersUM(
val blockchainId: String, val blockchainId: String,
val blockchainName: String, val blockchainName: String,
val blockchainSymbol: String, val blockchainSymbol: String,
val providers: ImmutableList<ProviderUM>, val providers: ImmutableList<ProviderUM>,
val isExpanded: Boolean, val isExpanded: Boolean,
val onDrop: (id: String, prev: Int, current: Int) -> Unit,
) )
data class ProviderUM(val name: String) @Immutable
data class ProviderUM(val type: ProviderType) {
val name: String = when (type) {
is ProviderType.Public -> type.url
else -> type::class.java.simpleName
}
}
} }

View file

@ -1,19 +1,27 @@
package com.tangem.feature.tester.presentation.providers.ui package com.tangem.feature.tester.presentation.providers.ui
import android.content.ClipData
import android.content.ClipDescription
import android.view.View
import androidx.compose.animation.* import androidx.compose.animation.*
import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.draganddrop.dragAndDropSource
import androidx.compose.foundation.draganddrop.dragAndDropTarget
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draganddrop.*
import androidx.compose.ui.draw.rotate import androidx.compose.ui.draw.rotate
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
@ -21,7 +29,6 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEachIndexed import androidx.compose.ui.util.fastForEachIndexed
import com.tangem.core.ui.components.SpacerH8 import com.tangem.core.ui.components.SpacerH8
import com.tangem.core.ui.components.appbar.AppBarWithBackButton import com.tangem.core.ui.components.appbar.AppBarWithBackButton
import com.tangem.core.ui.components.rows.RowContentContainer
import com.tangem.core.ui.components.rows.RowText import com.tangem.core.ui.components.rows.RowText
import com.tangem.core.ui.extensions.getActiveIconRes import com.tangem.core.ui.extensions.getActiveIconRes
import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.extensions.stringResourceSafe
@ -142,10 +149,14 @@ private fun Providers(state: ProvidersUM, isExpanded: Boolean) {
exit = fadeOut() + shrinkVertically(), exit = fadeOut() + shrinkVertically(),
label = "providers_visibility", label = "providers_visibility",
) { ) {
Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
state.providers.fastForEachIndexed { index, provider -> state.providers.fastForEachIndexed { index, provider ->
key(provider.name) { key("${provider.name}_$index") {
ProviderItem(index = index, state = provider) ProviderItem(
index = index,
state = provider,
onDrop = { prev, new -> state.onDrop(state.blockchainId, prev, new) },
)
if (index == state.providers.lastIndex) SpacerH8() if (index == state.providers.lastIndex) SpacerH8()
} }
@ -154,10 +165,32 @@ private fun Providers(state: ProvidersUM, isExpanded: Boolean) {
} }
} }
@OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
private fun ProviderItem(index: Int, state: ProviderUM) { private fun ProviderItem(index: Int, state: ProviderUM, onDrop: (Int, Int) -> Unit) {
RowContentContainer( var isActive by remember(index) { mutableStateOf(false) }
icon = { val color by animateColorAsState(
targetValue = if (isActive) {
TangemTheme.colors.icon.accent
} else {
TangemTheme.colors.icon.inactive
},
label = "provider_name_background_color",
)
val target = remember {
ProvidersDnDTarget(
index = index,
onActiveStateChange = { isActive = it },
onDrop = onDrop,
)
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Text( Text(
text = "${index + 1}.", text = "${index + 1}.",
color = TangemTheme.colors.text.accent, color = TangemTheme.colors.text.accent,
@ -165,8 +198,32 @@ private fun ProviderItem(index: Int, state: ProviderUM) {
maxLines = 1, maxLines = 1,
style = TangemTheme.typography.body2, style = TangemTheme.typography.body2,
) )
Box(
modifier = Modifier
.background(color = color, shape = RoundedCornerShape(16.dp))
.padding(vertical = 4.dp, horizontal = 8.dp)
.dragAndDropSource {
detectTapGestures(
onLongPress = {
startTransfer(
DragAndDropTransferData(
clipData = ClipData.newPlainText("provider index", index.toString()),
flags = View.DRAG_FLAG_GLOBAL,
),
)
}, },
text = { )
}
.dragAndDropTarget(
shouldStartDragAndDrop = { event ->
event
.mimeTypes()
.contains(ClipDescription.MIMETYPE_TEXT_PLAIN)
},
target = target,
),
) {
Text( Text(
text = state.name, text = state.name,
color = TangemTheme.colors.text.primary1, color = TangemTheme.colors.text.primary1,
@ -174,7 +231,38 @@ private fun ProviderItem(index: Int, state: ProviderUM) {
maxLines = 1, maxLines = 1,
style = TangemTheme.typography.body2, style = TangemTheme.typography.body2,
) )
}, }
action = {}, }
) }
private class ProvidersDnDTarget(
private val index: Int,
private val onActiveStateChange: (Boolean) -> Unit,
private val onDrop: (Int, Int) -> Unit,
) : DragAndDropTarget {
override fun onEntered(event: DragAndDropEvent) {
super.onEntered(event)
onActiveStateChange(true)
}
override fun onExited(event: DragAndDropEvent) {
super.onExited(event)
onActiveStateChange(false)
}
override fun onEnded(event: DragAndDropEvent) {
super.onEnded(event)
onActiveStateChange(false)
}
override fun onDrop(event: DragAndDropEvent): Boolean {
val new = event.toAndroidDragEvent().clipData.getItemAt(0).text.toString().toInt()
if (index == new) return false
onDrop(new, index)
return true
}
} }

View file

@ -4,19 +4,23 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.network.providers.ProviderType import com.tangem.blockchain.common.network.providers.ProviderType
import com.tangem.blockchainsdk.providers.BlockchainProviderTypesStore import com.tangem.blockchainsdk.providers.BlockchainProvidersTypesManager
import com.tangem.blockchainsdk.providers.MutableBlockchainProvidersTypesManager
import com.tangem.feature.tester.presentation.navigation.InnerTesterRouter import com.tangem.feature.tester.presentation.navigation.InnerTesterRouter
import com.tangem.feature.tester.presentation.providers.entity.BlockchainProvidersUM import com.tangem.feature.tester.presentation.providers.entity.BlockchainProvidersUM
import com.tangem.feature.tester.presentation.providers.entity.BlockchainProvidersUM.ProviderUM
import com.tangem.feature.tester.presentation.providers.entity.BlockchainProvidersUM.ProvidersUM
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
internal class BlockchainProvidersViewModel @Inject constructor( internal class BlockchainProvidersViewModel @Inject constructor(
blockchainProviderTypesStore: BlockchainProviderTypesStore, blockchainProvidersTypesManager: BlockchainProvidersTypesManager,
) : ViewModel() { ) : ViewModel() {
val state: StateFlow<BlockchainProvidersUM> get() = _state.asStateFlow() val state: StateFlow<BlockchainProvidersUM> get() = _state.asStateFlow()
@ -25,14 +29,10 @@ internal class BlockchainProvidersViewModel @Inject constructor(
value = BlockchainProvidersUM(onBackClick = {}, blockchainProviders = persistentListOf()), value = BlockchainProvidersUM(onBackClick = {}, blockchainProviders = persistentListOf()),
) )
private val manager = blockchainProvidersTypesManager as MutableBlockchainProvidersTypesManager
init { init {
blockchainProviderTypesStore.get() subscribeOnBlockchainProviderTypesUpdates()
.onEach { blockchainProviderTypes ->
_state.update { state ->
state.copy(blockchainProviders = blockchainProviderTypes.toProvidersUM())
}
}
.launchIn(viewModelScope)
} }
fun setupNavigation(router: InnerTesterRouter) { fun setupNavigation(router: InnerTesterRouter) {
@ -41,26 +41,99 @@ internal class BlockchainProvidersViewModel @Inject constructor(
} }
} }
private fun Map<Blockchain, List<ProviderType>>.toProvidersUM(): ImmutableList<BlockchainProvidersUM.ProvidersUM> { private fun subscribeOnBlockchainProviderTypesUpdates() {
manager.get()
.onEach { blockchainProviderTypes ->
_state.update { state ->
state.copy(blockchainProviders = blockchainProviderTypes.toProvidersUM())
}
}
.launchIn(viewModelScope)
}
private fun Map<Blockchain, List<ProviderType>>.toProvidersUM(): ImmutableList<ProvidersUM> {
return map { return map {
val (blockchain, providers) = it val (blockchain, providers) = it
BlockchainProvidersUM.ProvidersUM( ProvidersUM(
blockchainId = blockchain.id, blockchainId = blockchain.id,
blockchainName = blockchain.fullName, blockchainName = blockchain.fullName,
blockchainSymbol = blockchain.currency, blockchainSymbol = blockchain.currency,
providers = providers.map { providerType -> providers = providers.map(BlockchainProvidersUM::ProviderUM).toImmutableList(),
BlockchainProvidersUM.ProviderUM( onDrop = ::onDrop,
name = when (providerType) {
is ProviderType.Public -> providerType.url
else -> providerType::class.java.simpleName
},
)
}
.toImmutableList(),
isExpanded = false, isExpanded = false,
) )
} }
.toImmutableList() .toImmutableList()
} }
private fun onDrop(id: String, prev: Int, current: Int) {
val providers = _state.value
.blockchainProviders.first { it.blockchainId == id }
.providers.changeItemOrder(prev, current)
viewModelScope.launch {
_state.update {
it.copy(
blockchainProviders = it.blockchainProviders.map { block ->
if (block.blockchainId == id) {
block.copy(providers = providers.toImmutableList())
} else {
block
}
}
.toImmutableList(),
)
}
manager.update(
blockchain = Blockchain.fromId(id),
providers = providers.map(ProviderUM::type),
)
}
}
private fun ImmutableList<ProviderUM>.changeItemOrder(prev: Int, current: Int): ImmutableList<ProviderUM> {
return when {
prev > current && prev - current == 1 -> moveUpOneElement(prev, current)
current > prev && current - prev == 1 -> moveDownOneElement(prev, current)
else -> replaceElements(prev, current)
}
.toImmutableList()
}
// Before: [a, b, c, d, e]; previous = 2 (c), current = 1 (b)
// After: [a, c, b, d, e]
// Calculation:
// 1) [0..current) = [0..1) = [a]
// 2) [list[prev], list[current]] = [list[2], list[1]] = [c, b]
// 3) [(prev+1)..list.size) = [3..5) = [d, e]
// 4) [a] + [c, b] + [d, e] = [a, c, b, d, e]
private fun List<ProviderUM>.moveUpOneElement(prev: Int, current: Int): List<ProviderUM> {
return subList(fromIndex = 0, toIndex = current) +
listOf(this[prev], this[current]) +
subList(fromIndex = prev + 1, toIndex = this.size)
}
// Before: [a, b, c, d, e]; previous = 2 (c), current = 3 (d)
// After: [a, b, d, c, e]
// Calculation:
// 1) [0..prev) = [0..2) = [a, b]
// 2) [list[current], list[prev]] = [list[3], list[2]] = [d, c]
// 3) [(current+1)..list.size) = [4..5) = [e]
// 4) [a, b] + [d, c] + [e] = [a, b, d, c, e]
private fun List<ProviderUM>.moveDownOneElement(prev: Int, current: Int): List<ProviderUM> {
return subList(fromIndex = 0, toIndex = prev) +
listOf(this[current], this[prev]) +
subList(fromIndex = current + 1, toIndex = size)
}
// Before: [a, b, c, d, e]; previous = 0 (a), current = 4 (e)
// After: [e, b, c, d, a]
private fun List<ProviderUM>.replaceElements(prev: Int, current: Int): List<ProviderUM> {
return toMutableList().apply {
set(prev, this[current])
set(current, this[prev])
}
}
} }

View file

@ -8,11 +8,13 @@ import com.tangem.blockchainsdk.accountcreator.DefaultAccountCreator
import com.tangem.blockchainsdk.datastorage.DefaultBlockchainDataStorage import com.tangem.blockchainsdk.datastorage.DefaultBlockchainDataStorage
import com.tangem.blockchainsdk.featuretoggles.DefaultBlockchainSDKFeatureToggles import com.tangem.blockchainsdk.featuretoggles.DefaultBlockchainSDKFeatureToggles
import com.tangem.blockchainsdk.providers.BlockchainProvidersTypesManager import com.tangem.blockchainsdk.providers.BlockchainProvidersTypesManager
import com.tangem.blockchainsdk.providers.DevBlockchainProvidersTypesManager
import com.tangem.blockchainsdk.providers.ProdBlockchainProvidersTypesManager import com.tangem.blockchainsdk.providers.ProdBlockchainProvidersTypesManager
import com.tangem.core.configtoggle.feature.FeatureTogglesManager import com.tangem.core.configtoggle.feature.FeatureTogglesManager
import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.libs.blockchain_sdk.BuildConfig
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
@ -43,10 +45,14 @@ internal object BlockchainSDKFactoryModule {
@Provides @Provides
@Singleton @Singleton
fun provideBlockchainProvidersTypesManager( fun provideBlockchainProvidersTypesManager(
devBlockchainProvidersTypesManager: DevBlockchainProvidersTypesManager,
prodBlockchainProvidersTypesManager: ProdBlockchainProvidersTypesManager, prodBlockchainProvidersTypesManager: ProdBlockchainProvidersTypesManager,
): BlockchainProvidersTypesManager { ): BlockchainProvidersTypesManager {
// In the future, we will use different types of providers for different environments return if (BuildConfig.DEBUG) {
return prodBlockchainProvidersTypesManager devBlockchainProvidersTypesManager
} else {
prodBlockchainProvidersTypesManager
}
} }
@Provides @Provides

View file

@ -0,0 +1,45 @@
package com.tangem.blockchainsdk.providers
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.network.providers.ProviderType
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.properties.Delegates.notNull
/**
* Implementation of [BlockchainProvidersTypesManager] in DEV environment
*
* @property prodBlockchainProvidersTypesManager prod manager
* @property blockchainProviderTypesStore blockchain provider types store
*
[REDACTED_AUTHOR]
*/
@Singleton
internal class DevBlockchainProvidersTypesManager @Inject constructor(
private val prodBlockchainProvidersTypesManager: ProdBlockchainProvidersTypesManager,
private val blockchainProviderTypesStore: BlockchainProviderTypesStore,
) : BlockchainProvidersTypesManager by prodBlockchainProvidersTypesManager,
MutableBlockchainProvidersTypesManager {
private var primaryResponse: BlockchainProviderTypes by notNull()
override suspend fun update() {
prodBlockchainProvidersTypesManager.update()
primaryResponse = blockchainProviderTypesStore.get().value
}
override suspend fun update(blockchain: Blockchain, providers: List<ProviderType>) {
val currentTypes = blockchainProviderTypesStore.get().value
val updatedTypes = currentTypes + (blockchain to providers)
blockchainProviderTypesStore.store(value = updatedTypes)
}
override suspend fun isMatchWithMerged(): Boolean {
val current = blockchainProviderTypesStore.get().value
return current == primaryResponse
}
}

View file

@ -0,0 +1,18 @@
package com.tangem.blockchainsdk.providers
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.network.providers.ProviderType
/**
* Mutable blockchain providers types manager
*
[REDACTED_AUTHOR]
*/
interface MutableBlockchainProvidersTypesManager : BlockchainProvidersTypesManager {
/** Update [providers] for [blockchain] */
suspend fun update(blockchain: Blockchain, providers: List<ProviderType>)
/** Checks that current [BlockchainProviderTypes] was changed */
suspend fun isMatchWithMerged(): Boolean
}