diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/TesterActivity.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/TesterActivity.kt index c96aac013a..5a9fbd3fe3 100644 --- a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/TesterActivity.kt +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/TesterActivity.kt @@ -28,6 +28,8 @@ import com.tangem.feature.tester.presentation.menu.state.TesterMenuUM.ButtonUM import com.tangem.feature.tester.presentation.menu.ui.TesterMenuScreen import com.tangem.feature.tester.presentation.navigation.InnerTesterRouter import com.tangem.feature.tester.presentation.navigation.TesterScreen +import com.tangem.feature.tester.presentation.providers.ui.BlockchainProvidersScreen +import com.tangem.feature.tester.presentation.providers.viewmodel.BlockchainProvidersViewModel import com.tangem.features.tester.api.TesterRouter import dagger.hilt.android.AndroidEntryPoint import kotlinx.collections.immutable.persistentSetOf @@ -130,6 +132,15 @@ internal class TesterActivity : ComposeActivity() { val state by viewModel.state.collectAsStateWithLifecycle() ExcludedBlockchainsScreen(state = state) } + + composable(route = TesterScreen.BLOCKCHAIN_PROVIDERS.name) { + val viewModel = hiltViewModel().apply { + setupNavigation(innerTesterRouter) + } + + val state by viewModel.state.collectAsStateWithLifecycle() + BlockchainProvidersScreen(state) + } } } } \ No newline at end of file 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 new file mode 100644 index 0000000000..b42f676fde --- /dev/null +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/providers/entity/BlockchainProvidersUM.kt @@ -0,0 +1,19 @@ +package com.tangem.feature.tester.presentation.providers.entity + +import kotlinx.collections.immutable.ImmutableList + +internal data class BlockchainProvidersUM( + val onBackClick: () -> Unit, + val blockchainProviders: ImmutableList, +) { + + data class ProvidersUM( + val blockchainId: String, + val blockchainName: String, + val blockchainSymbol: String, + val providers: ImmutableList, + val isExpanded: Boolean, + ) + + data class ProviderUM(val name: String) +} \ 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 new file mode 100644 index 0000000000..feb1f05406 --- /dev/null +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/providers/ui/BlockchainProvidersScreen.kt @@ -0,0 +1,180 @@ +package com.tangem.feature.tester.presentation.providers.ui + +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.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +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.draw.rotate +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextOverflow +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 +import com.tangem.core.ui.res.TangemTheme +import com.tangem.feature.tester.impl.R +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 + +private const val CHEVRON_ROTATION_EXPANDED = 180f +private const val CHEVRON_ROTATION_COLLAPSED = 0f + +@OptIn(ExperimentalFoundationApi::class) +@Composable +internal fun BlockchainProvidersScreen(state: BlockchainProvidersUM) { + Box( + modifier = Modifier + .fillMaxSize() + .background(TangemTheme.colors.background.primary), + ) { + LazyColumn { + stickyHeader { + AppBarWithBackButton( + onBackClick = state.onBackClick, + text = stringResourceSafe(id = R.string.blockchain_providers), + containerColor = TangemTheme.colors.background.primary, + ) + } + + items( + items = state.blockchainProviders, + key = ProvidersUM::blockchainId, + contentType = { "BlockchainProviders" }, + itemContent = { BlockchainProvidersItem(it) }, + ) + } + } +} + +@Composable +private fun BlockchainProvidersItem(state: ProvidersUM) { + var isExpanded by remember { mutableStateOf(state.isExpanded) } + + ExpandableBlockchainRow( + state = state, + modifier = Modifier.clickable { isExpanded = !isExpanded }, + content = { Providers(state = state, isExpanded = isExpanded) }, + ) +} + +@Composable +private fun ExpandableBlockchainRow( + state: ProvidersUM, + content: @Composable () -> Unit, + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) { + BlockchainRow(state) + + content() + } +} + +@Composable +private fun BlockchainRow(state: ProvidersUM) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 16.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + Image( + painter = painterResource(id = getActiveIconRes(state.blockchainId)), + contentDescription = null, + modifier = Modifier.size(TangemTheme.dimens.size36), + ) + + RowText( + mainText = state.blockchainName, + secondText = state.blockchainSymbol, + accentMainText = true, + accentSecondText = false, + modifier = Modifier.padding(horizontal = 8.dp), + ) + } + + val rotation by animateFloatAsState( + targetValue = if (state.isExpanded) { + CHEVRON_ROTATION_EXPANDED + } else { + CHEVRON_ROTATION_COLLAPSED + }, + label = "chevron_rotation", + ) + + Icon( + modifier = Modifier + .rotate(rotation) + .size(TangemTheme.dimens.size24), + painter = painterResource(id = R.drawable.ic_chevron_24), + tint = TangemTheme.colors.icon.informative, + contentDescription = null, + ) + } +} + +@Composable +private fun Providers(state: ProvidersUM, isExpanded: Boolean) { + AnimatedVisibility( + visible = isExpanded, + enter = fadeIn() + expandVertically(), + exit = fadeOut() + shrinkVertically(), + label = "providers_visibility", + ) { + Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { + state.providers.fastForEachIndexed { index, provider -> + key(provider.name) { + ProviderItem(index = index, state = provider) + + if (index == state.providers.lastIndex) SpacerH8() + } + } + } + } +} + +@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, + ) + }, + text = { + Text( + text = state.name, + color = TangemTheme.colors.text.primary1, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + style = TangemTheme.typography.body2, + ) + }, + action = {}, + ) +} \ 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 new file mode 100644 index 0000000000..20a8439a19 --- /dev/null +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/providers/viewmodel/BlockchainProvidersViewModel.kt @@ -0,0 +1,66 @@ +package com.tangem.feature.tester.presentation.providers.viewmodel + +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.feature.tester.presentation.navigation.InnerTesterRouter +import com.tangem.feature.tester.presentation.providers.entity.BlockchainProvidersUM +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 javax.inject.Inject + +@HiltViewModel +internal class BlockchainProvidersViewModel @Inject constructor( + blockchainProviderTypesStore: BlockchainProviderTypesStore, +) : ViewModel() { + + val state: StateFlow get() = _state.asStateFlow() + + private val _state = MutableStateFlow( + value = BlockchainProvidersUM(onBackClick = {}, blockchainProviders = persistentListOf()), + ) + + init { + blockchainProviderTypesStore.get() + .onEach { blockchainProviderTypes -> + _state.update { state -> + state.copy(blockchainProviders = blockchainProviderTypes.toProvidersUM()) + } + } + .launchIn(viewModelScope) + } + + fun setupNavigation(router: InnerTesterRouter) { + _state.update { state -> + state.copy(onBackClick = router::back) + } + } + + private fun Map>.toProvidersUM(): ImmutableList { + return map { + val (blockchain, providers) = it + + BlockchainProvidersUM.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(), + isExpanded = false, + ) + } + .toImmutableList() + } +} \ No newline at end of file