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 cfebe28ed5..70a08b1976 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 @@ -17,7 +17,8 @@ internal data class BlockchainProvidersUM( val blockchainSymbol: String, val providers: ImmutableList, val isExpanded: Boolean, - val onDrop: (id: String, prev: Int, current: Int) -> Unit, + val onDrop: (prev: Int, current: Int) -> Unit, + val addPublicProviderDialog: AddPublicProviderDialogUM, ) @Immutable @@ -28,4 +29,10 @@ internal data class BlockchainProvidersUM( else -> type::class.java.simpleName } } + + data class AddPublicProviderDialogUM( + val hasError: Boolean, + val onValueChange: (String) -> Unit, + val onSaveClick: (String) -> Unit, + ) } \ 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 ce048035c0..2623f47666 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 @@ -5,10 +5,7 @@ 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.* import androidx.compose.foundation.draganddrop.dragAndDropSource import androidx.compose.foundation.draganddrop.dragAndDropTarget import androidx.compose.foundation.gestures.detectTapGestures @@ -22,14 +19,20 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draganddrop.* +import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.rotate import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.input.TextFieldValue 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.AdditionalTextInputDialogUM +import com.tangem.core.ui.components.DialogButtonUM +import com.tangem.core.ui.components.TextInputDialog import com.tangem.core.ui.components.rows.RowText import com.tangem.core.ui.extensions.getActiveIconRes import com.tangem.core.ui.extensions.resourceReference +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.common.components.appbar.TopBarWithRefresh @@ -165,10 +168,12 @@ private fun Providers(state: ProvidersUM, isExpanded: Boolean) { ProviderItem( index = index, state = provider, - onDrop = { prev, new -> state.onDrop(state.blockchainId, prev, new) }, + onDrop = { prev, new -> state.onDrop(prev, new) }, ) } } + + AddProviderButton(state = state.addPublicProviderDialog) } } } @@ -194,18 +199,8 @@ private fun ProviderItem(index: Int, state: ProviderUM, onDrop: (Int, Int) -> Un ) } - 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, - ) + ProviderItemContainer { + ProviderIndex(index = index) Box( modifier = Modifier @@ -243,6 +238,99 @@ private fun ProviderItem(index: Int, state: ProviderUM, onDrop: (Int, Int) -> Un } } +@Composable +private fun ProviderIndex(index: Int) { + Text( + text = "${index + 1}.", + color = TangemTheme.colors.text.accent, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + style = TangemTheme.typography.body2, + ) +} + +@Composable +private fun AddProviderButton(state: BlockchainProvidersUM.AddPublicProviderDialogUM) { + var isDialogVisible by remember { mutableStateOf(value = false) } + + ProviderItemContainer { + Box( + modifier = Modifier + .clip(shape = RoundedCornerShape(16.dp)) + .clickable(onClick = { isDialogVisible = true }) + .weight(1f) + .border( + border = BorderStroke(width = 2.dp, color = TangemTheme.colors.icon.accent), + shape = RoundedCornerShape(16.dp), + ) + .padding(horizontal = 8.dp, vertical = 2.dp), + contentAlignment = Alignment.Center, + ) { + Icon( + painter = painterResource(id = R.drawable.ic_plus_24), + contentDescription = null, + tint = TangemTheme.colors.icon.accent, + ) + } + } + + if (isDialogVisible) { + AddPublicProviderDialog( + state = state, + onDismiss = { isDialogVisible = false }, + ) + } +} + +@Composable +private fun AddPublicProviderDialog(state: BlockchainProvidersUM.AddPublicProviderDialogUM, onDismiss: () -> Unit) { + var value by remember { mutableStateOf(value = TextFieldValue()) } + + TextInputDialog( + fieldValue = value, + confirmButton = DialogButtonUM( + title = stringResourceSafe(R.string.common_save), + enabled = value.text.isNotBlank() && !state.hasError, + onClick = { + state.onSaveClick( + buildString { + append(value.text.trim()) + + if (!endsWith("/")) append("/") + }, + ) + onDismiss() + }, + ), + onDismissDialog = onDismiss, + onValueChange = { + value = it + state.onValueChange(it.text) + }, + textFieldParams = AdditionalTextInputDialogUM( + label = "Url", + placeholder = "https://...", + isError = state.hasError, + errorText = "Invalid url", + ), + title = "Public provider", + dismissButton = DialogButtonUM( + title = stringResourceSafe(id = R.string.common_cancel), + onClick = onDismiss, + ), + ) +} + +@Composable +private fun ProviderItemContainer(content: @Composable RowScope.() -> Unit) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically, + content = content, + ) +} + private class ProvidersDnDTarget( private val index: Int, private val onActiveStateChange: (Boolean) -> Unit, 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 f6ba96ab7d..e1157cac8b 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 @@ -1,5 +1,6 @@ package com.tangem.feature.tester.presentation.providers.viewmodel +import androidx.core.util.PatternsCompat import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.tangem.blockchain.common.Blockchain @@ -10,8 +11,7 @@ import com.tangem.feature.tester.impl.R import com.tangem.feature.tester.presentation.common.components.appbar.TopBarWithRefreshUM 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 com.tangem.feature.tester.presentation.providers.entity.BlockchainProvidersUM.* import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -78,8 +78,13 @@ internal class BlockchainProvidersViewModel @Inject constructor( blockchainName = blockchain.fullName, blockchainSymbol = blockchain.currency, providers = providers.map(BlockchainProvidersUM::ProviderUM).toImmutableList(), - onDrop = ::onDrop, isExpanded = false, + onDrop = { prev, current -> onDrop(blockchain.id, prev, current) }, + addPublicProviderDialog = AddPublicProviderDialogUM( + hasError = false, + onValueChange = { onPublicProviderUrlChange(id = blockchain.id, url = it) }, + onSaveClick = { onAddProviderClick(id = blockchain.id, url = it) }, + ), ) } .toImmutableList() @@ -98,16 +103,9 @@ internal class BlockchainProvidersViewModel @Inject constructor( viewModelScope.launch { _state.update { - it.copy( - blockchainProviders = it.blockchainProviders.map { block -> - if (block.blockchainId == id) { - block.copy(providers = providers.toImmutableList()) - } else { - block - } - } - .toImmutableList(), - ) + it.copyProvidersUM(blockchainId = id) { + copy(providers = providers.toImmutableList()) + } } manager.update( @@ -160,4 +158,45 @@ internal class BlockchainProvidersViewModel @Inject constructor( set(current, this[prev]) } } + + private fun onPublicProviderUrlChange(id: String, url: String) { + _state.update { + it.copyProvidersUM(blockchainId = id) { + copy( + addPublicProviderDialog = addPublicProviderDialog.copy( + hasError = !PatternsCompat.WEB_URL.matcher(url).matches(), + ), + ) + } + } + } + + private fun onAddProviderClick(id: String, url: String) { + val providers = _state.value + .blockchainProviders.first { it.blockchainId == id } + .providers + + viewModelScope.launch { + manager.update( + blockchain = Blockchain.fromId(id), + providers = listOf(ProviderType.Public(url = url)) + providers.map(ProviderUM::type), + ) + } + } + + private fun BlockchainProvidersUM.copyProvidersUM( + blockchainId: String, + update: ProvidersUM.() -> ProvidersUM, + ): BlockchainProvidersUM { + return copy( + blockchainProviders = _state.value.blockchainProviders.map { + if (it.blockchainId == blockchainId) { + it.update() + } else { + it + } + } + .toImmutableList(), + ) + } } \ No newline at end of file