From 5d892618a86cbe14c7a08b1f58ac155fd5c4fdc0 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 6 Jan 2025 15:37:52 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../DefaultWalletManagersFacade.kt | 3 +- .../providers/entity/BlockchainProvidersUM.kt | 13 +- .../providers/ui/BlockchainProvidersScreen.kt | 124 +++++++++++++++--- .../viewmodel/BlockchainProvidersViewModel.kt | 113 +++++++++++++--- .../di/BlockchainSDKFactoryModule.kt | 10 +- .../DevBlockchainProvidersTypesManager.kt | 45 +++++++ .../MutableBlockchainProvidersTypesManager.kt | 18 +++ 7 files changed, 284 insertions(+), 42 deletions(-) create mode 100644 libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/providers/DevBlockchainProvidersTypesManager.kt create mode 100644 libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/providers/MutableBlockchainProvidersTypesManager.kt diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt index 440e818d8f..7c2147cd08 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt @@ -22,6 +22,7 @@ import com.tangem.datasource.asset.loader.AssetLoader import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.datasource.local.walletmanager.WalletManagersStore import com.tangem.domain.common.util.hasDerivation +import com.tangem.domain.demo.BuildConfig import com.tangem.domain.demo.DemoConfig import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.Network @@ -369,7 +370,7 @@ class DefaultWalletManagersFacade( blockchain = blockchain, derivationPath = derivationPath, ) - if (walletManager == null) { + if (BuildConfig.DEBUG || walletManager == null) { walletManager = walletManagerFactory.createWalletManager( scanResponse = userWallet.scanResponse, blockchain = blockchain, diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/providers/entity/BlockchainProvidersUM.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/providers/entity/BlockchainProvidersUM.kt index b42f676fde..6f280851dc 100644 --- a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/providers/entity/BlockchainProvidersUM.kt +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/providers/entity/BlockchainProvidersUM.kt @@ -1,5 +1,7 @@ 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 internal data class BlockchainProvidersUM( @@ -7,13 +9,22 @@ internal data class BlockchainProvidersUM( val blockchainProviders: ImmutableList, ) { + @Immutable data class ProvidersUM( val blockchainId: String, val blockchainName: String, val blockchainSymbol: String, val providers: ImmutableList, 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 + } + } } \ No newline at end of file diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/providers/ui/BlockchainProvidersScreen.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/providers/ui/BlockchainProvidersScreen.kt index feb1f05406..32691867a6 100644 --- a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/providers/ui/BlockchainProvidersScreen.kt +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/providers/ui/BlockchainProvidersScreen.kt @@ -1,19 +1,27 @@ 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.core.animateFloatAsState import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image import androidx.compose.foundation.background 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.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draganddrop.* import androidx.compose.ui.draw.rotate import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextOverflow @@ -21,7 +29,6 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastForEachIndexed import com.tangem.core.ui.components.SpacerH8 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.extensions.getActiveIconRes import com.tangem.core.ui.extensions.stringResourceSafe @@ -142,10 +149,14 @@ private fun Providers(state: ProvidersUM, isExpanded: Boolean) { exit = fadeOut() + shrinkVertically(), label = "providers_visibility", ) { - Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { + Column(verticalArrangement = Arrangement.spacedBy(12.dp)) { state.providers.fastForEachIndexed { index, provider -> - key(provider.name) { - ProviderItem(index = index, state = provider) + key("${provider.name}_$index") { + ProviderItem( + index = index, + state = provider, + onDrop = { prev, new -> state.onDrop(state.blockchainId, prev, new) }, + ) if (index == state.providers.lastIndex) SpacerH8() } @@ -154,19 +165,65 @@ private fun Providers(state: ProvidersUM, isExpanded: Boolean) { } } +@OptIn(ExperimentalFoundationApi::class) @Composable -private fun ProviderItem(index: Int, state: ProviderUM) { - RowContentContainer( - icon = { - Text( - text = "${index + 1}.", - color = TangemTheme.colors.text.accent, - overflow = TextOverflow.Ellipsis, - maxLines = 1, - style = TangemTheme.typography.body2, - ) +private fun ProviderItem(index: Int, state: ProviderUM, onDrop: (Int, Int) -> Unit) { + var isActive by remember(index) { mutableStateOf(false) } + val color by animateColorAsState( + targetValue = if (isActive) { + TangemTheme.colors.icon.accent + } else { + TangemTheme.colors.icon.inactive }, - text = { + 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 = "${index + 1}.", + color = TangemTheme.colors.text.accent, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + 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, + ), + ) + }, + ) + } + .dragAndDropTarget( + shouldStartDragAndDrop = { event -> + event + .mimeTypes() + .contains(ClipDescription.MIMETYPE_TEXT_PLAIN) + }, + target = target, + ), + ) { Text( text = state.name, color = TangemTheme.colors.text.primary1, @@ -174,7 +231,38 @@ private fun ProviderItem(index: Int, state: ProviderUM) { maxLines = 1, 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 + } } \ No newline at end of file diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/providers/viewmodel/BlockchainProvidersViewModel.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/providers/viewmodel/BlockchainProvidersViewModel.kt index 20a8439a19..f8f6981bc8 100644 --- a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/providers/viewmodel/BlockchainProvidersViewModel.kt +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/providers/viewmodel/BlockchainProvidersViewModel.kt @@ -4,19 +4,23 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.tangem.blockchain.common.Blockchain 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.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 kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.flow.* +import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel internal class BlockchainProvidersViewModel @Inject constructor( - blockchainProviderTypesStore: BlockchainProviderTypesStore, + blockchainProvidersTypesManager: BlockchainProvidersTypesManager, ) : ViewModel() { val state: StateFlow get() = _state.asStateFlow() @@ -25,14 +29,10 @@ internal class BlockchainProvidersViewModel @Inject constructor( value = BlockchainProvidersUM(onBackClick = {}, blockchainProviders = persistentListOf()), ) + private val manager = blockchainProvidersTypesManager as MutableBlockchainProvidersTypesManager + init { - blockchainProviderTypesStore.get() - .onEach { blockchainProviderTypes -> - _state.update { state -> - state.copy(blockchainProviders = blockchainProviderTypes.toProvidersUM()) - } - } - .launchIn(viewModelScope) + subscribeOnBlockchainProviderTypesUpdates() } fun setupNavigation(router: InnerTesterRouter) { @@ -41,26 +41,99 @@ internal class BlockchainProvidersViewModel @Inject constructor( } } - private fun Map>.toProvidersUM(): ImmutableList { + private fun subscribeOnBlockchainProviderTypesUpdates() { + manager.get() + .onEach { blockchainProviderTypes -> + _state.update { state -> + state.copy(blockchainProviders = blockchainProviderTypes.toProvidersUM()) + } + } + .launchIn(viewModelScope) + } + + private fun Map>.toProvidersUM(): ImmutableList { return map { val (blockchain, providers) = it - BlockchainProvidersUM.ProvidersUM( + ProvidersUM( blockchainId = blockchain.id, blockchainName = blockchain.fullName, blockchainSymbol = blockchain.currency, - providers = providers.map { providerType -> - BlockchainProvidersUM.ProviderUM( - name = when (providerType) { - is ProviderType.Public -> providerType.url - else -> providerType::class.java.simpleName - }, - ) - } - .toImmutableList(), + providers = providers.map(BlockchainProvidersUM::ProviderUM).toImmutableList(), + onDrop = ::onDrop, isExpanded = false, ) } .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.changeItemOrder(prev: Int, current: Int): ImmutableList { + 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.moveUpOneElement(prev: Int, current: Int): List { + 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.moveDownOneElement(prev: Int, current: Int): List { + 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.replaceElements(prev: Int, current: Int): List { + return toMutableList().apply { + set(prev, this[current]) + set(current, this[prev]) + } + } } \ No newline at end of file diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/di/BlockchainSDKFactoryModule.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/di/BlockchainSDKFactoryModule.kt index 3d1145c79d..ec39268431 100644 --- a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/di/BlockchainSDKFactoryModule.kt +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/di/BlockchainSDKFactoryModule.kt @@ -8,11 +8,13 @@ import com.tangem.blockchainsdk.accountcreator.DefaultAccountCreator import com.tangem.blockchainsdk.datastorage.DefaultBlockchainDataStorage import com.tangem.blockchainsdk.featuretoggles.DefaultBlockchainSDKFeatureToggles import com.tangem.blockchainsdk.providers.BlockchainProvidersTypesManager +import com.tangem.blockchainsdk.providers.DevBlockchainProvidersTypesManager import com.tangem.blockchainsdk.providers.ProdBlockchainProvidersTypesManager import com.tangem.core.configtoggle.feature.FeatureTogglesManager import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage import com.tangem.datasource.local.preferences.AppPreferencesStore +import com.tangem.libs.blockchain_sdk.BuildConfig import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module import dagger.Provides @@ -43,10 +45,14 @@ internal object BlockchainSDKFactoryModule { @Provides @Singleton fun provideBlockchainProvidersTypesManager( + devBlockchainProvidersTypesManager: DevBlockchainProvidersTypesManager, prodBlockchainProvidersTypesManager: ProdBlockchainProvidersTypesManager, ): BlockchainProvidersTypesManager { - // In the future, we will use different types of providers for different environments - return prodBlockchainProvidersTypesManager + return if (BuildConfig.DEBUG) { + devBlockchainProvidersTypesManager + } else { + prodBlockchainProvidersTypesManager + } } @Provides diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/providers/DevBlockchainProvidersTypesManager.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/providers/DevBlockchainProvidersTypesManager.kt new file mode 100644 index 0000000000..479c90f1d2 --- /dev/null +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/providers/DevBlockchainProvidersTypesManager.kt @@ -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) { + 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 + } +} \ No newline at end of file diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/providers/MutableBlockchainProvidersTypesManager.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/providers/MutableBlockchainProvidersTypesManager.kt new file mode 100644 index 0000000000..148a2dfc23 --- /dev/null +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/providers/MutableBlockchainProvidersTypesManager.kt @@ -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) + + /** Checks that current [BlockchainProviderTypes] was changed */ + suspend fun isMatchWithMerged(): Boolean +} \ No newline at end of file