diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml
index 215563541d..b54a4ae79a 100644
--- a/core/res/src/main/res/values/strings.xml
+++ b/core/res/src/main/res/values/strings.xml
@@ -105,6 +105,7 @@
- %d addresses
Choose address
+ Clear All
Contact
Contact name
Copy address
@@ -134,6 +135,7 @@
Save to Wallet
This contact will be linked to this wallet’s address book.
No results found.\nTry another name
+ Select All
Select network
Address book
Unsaved changes
diff --git a/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/analytics/AddressBookEvents.kt b/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/analytics/AddressBookEvents.kt
index 9a1448a028..e11a6468fe 100644
--- a/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/analytics/AddressBookEvents.kt
+++ b/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/analytics/AddressBookEvents.kt
@@ -11,6 +11,7 @@ private const val WALLET_ID = "Wallet Id"
private const val CONTACT_ID = "Contact Id"
private const val MODE = "Mode"
private const val CONTACTS_COUNT = "Contacts Count"
+private const val ACTION = "Action"
sealed class AddressBookEvents(
event: String,
@@ -162,6 +163,24 @@ sealed class AddressBookEvents(
)
// endregion
+ // region Choose network
+ class SelectAllNetworksTapped(
+ walletId: UserWalletId,
+ action: Action,
+ ) : AddressBookEvents(
+ event = "Select All Networks Tapped",
+ params = mapOf(
+ WALLET_ID to walletId.stringValue,
+ ACTION to action.value,
+ ),
+ ) {
+ enum class Action(val value: String) {
+ SelectAll("Select All"),
+ ClearAll("Clear All"),
+ }
+ }
+ // endregion
+
// region Deletion
class AddressRemoved(
walletId: UserWalletId,
diff --git a/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/common/AddressBookAnalyticsSender.kt b/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/common/AddressBookAnalyticsSender.kt
index 591d502920..e43c1a4813 100644
--- a/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/common/AddressBookAnalyticsSender.kt
+++ b/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/common/AddressBookAnalyticsSender.kt
@@ -88,6 +88,14 @@ internal class AddressBookAnalyticsSender @Inject constructor(
)
}
+ fun sendSelectAllNetworksTapped(action: AddressBookEvents.SelectAllNetworksTapped.Action, scope: CoroutineScope) {
+ scope.launch(dispatcherProvider.default) {
+ analyticsEventHandler.send(
+ AddressBookEvents.SelectAllNetworksTapped(walletId = selectedWalletId(), action = action),
+ )
+ }
+ }
+
fun sendAddContactTapped(fromSendSuccess: Boolean, scope: CoroutineScope) {
scope.launch(dispatcherProvider.default) {
analyticsEventHandler.send(
diff --git a/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/selectnetworks/model/SelectNetworksModel.kt b/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/selectnetworks/model/SelectNetworksModel.kt
index c91befa6e9..3c235410f0 100644
--- a/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/selectnetworks/model/SelectNetworksModel.kt
+++ b/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/selectnetworks/model/SelectNetworksModel.kt
@@ -6,6 +6,8 @@ import com.tangem.blockchainsdk.utils.toNetworkId
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
+import com.tangem.features.addressbook.analytics.AddressBookEvents
+import com.tangem.features.addressbook.common.AddressBookAnalyticsSender
import com.tangem.features.addressbook.selectnetworks.DefaultSelectNetworksComponent
import com.tangem.features.addressbook.selectnetworks.state.SelectNetworksStateController
import com.tangem.features.addressbook.selectnetworks.state.transformers.UpdateNetworksContentTransformer
@@ -22,6 +24,7 @@ internal class SelectNetworksModel @Inject constructor(
paramsContainer: ParamsContainer,
override val dispatchers: CoroutineDispatcherProvider,
private val stateController: SelectNetworksStateController,
+ private val analyticsSender: AddressBookAnalyticsSender,
) : Model() {
private val params: DefaultSelectNetworksComponent.Params = paramsContainer.require()
@@ -50,16 +53,18 @@ internal class SelectNetworksModel @Inject constructor(
onActiveChange = ::onActiveChange,
onBackClick = params.onBackClick,
onDoneClick = ::onDoneClick,
+ onSelectAllClick = ::onSelectAllClick,
),
)
}
private fun subscribeToContent() {
- combine(query, selectedNetworks) { query, selection ->
+ combine(query, selectedNetworks, isSearchActive) { query, selection, isSearchActive ->
UpdateNetworksContentTransformer(
matchedBlockchains = matchedBlockchains,
query = query,
selectedNetworkIds = selection,
+ isSearchActive = isSearchActive,
onToggle = ::onToggle,
)
}
@@ -88,6 +93,22 @@ internal class SelectNetworksModel @Inject constructor(
selectedNetworks.value = if (networkId in current) current - networkId else current + networkId
}
+ private fun onSelectAllClick() {
+ val allNetworkIds = matchedBlockchains.map { blockchain -> blockchain.toNetworkId() }.toSet()
+ val isSelectingAll = allNetworkIds.isEmpty() || !selectedNetworks.value.containsAll(allNetworkIds)
+
+ analyticsSender.sendSelectAllNetworksTapped(
+ action = if (isSelectingAll) {
+ AddressBookEvents.SelectAllNetworksTapped.Action.SelectAll
+ } else {
+ AddressBookEvents.SelectAllNetworksTapped.Action.ClearAll
+ },
+ scope = modelScope,
+ )
+
+ selectedNetworks.value = if (isSelectingAll) allNetworkIds else emptySet()
+ }
+
private fun onDoneClick() {
val selected = selectedNetworks.value
if (selected.isEmpty()) return
diff --git a/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/selectnetworks/state/SelectNetworksStateController.kt b/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/selectnetworks/state/SelectNetworksStateController.kt
index 5b7275aeb0..23f08c09d7 100644
--- a/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/selectnetworks/state/SelectNetworksStateController.kt
+++ b/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/selectnetworks/state/SelectNetworksStateController.kt
@@ -36,6 +36,7 @@ internal class SelectNetworksStateController @Inject constructor() {
onCloseClick = {},
),
networks = persistentListOf(),
+ selectAllButton = SelectNetworksUM.SelectAllButtonUM.Empty,
doneButton = TangemButtonUM(
text = TextReference.Res(R.string.common_done),
type = TangemButtonType.Primary,
@@ -43,5 +44,6 @@ internal class SelectNetworksStateController @Inject constructor() {
onClick = {},
),
onBackClick = {},
+ onSelectAllClick = {},
)
}
\ No newline at end of file
diff --git a/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/selectnetworks/state/transformers/UpdateNetworksContentTransformer.kt b/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/selectnetworks/state/transformers/UpdateNetworksContentTransformer.kt
index 63837c244a..e621a309a6 100644
--- a/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/selectnetworks/state/transformers/UpdateNetworksContentTransformer.kt
+++ b/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/selectnetworks/state/transformers/UpdateNetworksContentTransformer.kt
@@ -11,6 +11,7 @@ internal class UpdateNetworksContentTransformer(
private val matchedBlockchains: List,
private val query: String,
private val selectedNetworkIds: Set,
+ private val isSearchActive: Boolean,
private val onToggle: (networkId: String) -> Unit,
) : Transformer {
@@ -36,9 +37,18 @@ internal class UpdateNetworksContentTransformer(
}
.toImmutableList()
+ val isAllSelected = matchedBlockchains.isNotEmpty() &&
+ selectedNetworkIds.size == matchedBlockchains.size
+ val selectAllButton = when {
+ isSearchActive -> SelectNetworksUM.SelectAllButtonUM.Empty
+ isAllSelected -> SelectNetworksUM.SelectAllButtonUM.ClearAll
+ else -> SelectNetworksUM.SelectAllButtonUM.SelectAll
+ }
+
// Search field is owned by UpdateSelectNetworksSearchBarTransformer and intentionally left untouched here.
return prevState.copy(
networks = networks,
+ selectAllButton = selectAllButton,
doneButton = prevState.doneButton.copy(isEnabled = selectedNetworkIds.isNotEmpty()),
)
}
diff --git a/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/selectnetworks/state/transformers/UpdateSelectNetworksInitialStateTransformer.kt b/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/selectnetworks/state/transformers/UpdateSelectNetworksInitialStateTransformer.kt
index 2421d856d8..1eec6bc86e 100644
--- a/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/selectnetworks/state/transformers/UpdateSelectNetworksInitialStateTransformer.kt
+++ b/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/selectnetworks/state/transformers/UpdateSelectNetworksInitialStateTransformer.kt
@@ -8,6 +8,7 @@ internal class UpdateSelectNetworksInitialStateTransformer(
private val onActiveChange: (Boolean) -> Unit,
private val onBackClick: () -> Unit,
private val onDoneClick: () -> Unit,
+ private val onSelectAllClick: () -> Unit,
) : Transformer {
override fun transform(prevState: SelectNetworksUM): SelectNetworksUM {
@@ -20,6 +21,7 @@ internal class UpdateSelectNetworksInitialStateTransformer(
),
doneButton = prevState.doneButton.copy(onClick = onDoneClick),
onBackClick = onBackClick,
+ onSelectAllClick = onSelectAllClick,
)
}
}
\ No newline at end of file
diff --git a/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/selectnetworks/ui/SelectNetworksContent.kt b/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/selectnetworks/ui/SelectNetworksContent.kt
index efa891b007..fbbb0f0675 100644
--- a/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/selectnetworks/ui/SelectNetworksContent.kt
+++ b/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/selectnetworks/ui/SelectNetworksContent.kt
@@ -1,8 +1,15 @@
package com.tangem.features.addressbook.selectnetworks.ui
import android.content.res.Configuration
+import androidx.compose.animation.AnimatedContent
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.animation.togetherWith
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
@@ -10,6 +17,7 @@ import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
@@ -25,10 +33,9 @@ import com.tangem.core.ui.ds.topbar.TangemTopBar
import com.tangem.core.ui.ds2.button.TangemButton
import com.tangem.core.ui.ds2.checkbox.TangemCheckmark
import com.tangem.core.ui.ds2.search.TangemSearch
-import com.tangem.core.ui.extensions.TextReference
-import com.tangem.core.ui.extensions.clickableSingle
-import com.tangem.core.ui.extensions.resourceReference
-import com.tangem.core.ui.extensions.stringResourceSafe
+import com.tangem.core.ui.extensions.*
+import com.tangem.core.ui.haptic.TangemHapticEffect
+import com.tangem.core.ui.res.LocalHapticManager
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.features.addressbook.selectnetworks.ui.state.SelectNetworksUM
@@ -79,11 +86,9 @@ internal fun SelectNetworksContent(state: SelectNetworksUM, modifier: Modifier =
contentPadding = PaddingValues(horizontal = 16.dp),
) {
item {
- Text(
- modifier = Modifier.padding(top = 16.dp, bottom = 4.dp),
- text = stringResourceSafe(R.string.common_available_networks),
- style = TangemTheme.typography3.caption.medium,
- color = TangemTheme.colors3.text.secondary,
+ NetworksHeader(
+ selectAllButton = state.selectAllButton,
+ onSelectAllClick = state.onSelectAllClick,
)
}
items(items = state.networks, key = NetworkItemUM::id) { item ->
@@ -105,12 +110,64 @@ internal fun SelectNetworksContent(state: SelectNetworksUM, modifier: Modifier =
}
}
+@Composable
+private fun NetworksHeader(
+ selectAllButton: SelectNetworksUM.SelectAllButtonUM,
+ onSelectAllClick: () -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ val hapticManager = LocalHapticManager.current
+ Row(
+ modifier = modifier.fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Text(
+ modifier = Modifier
+ .weight(1f)
+ .padding(top = 16.dp, bottom = 4.dp),
+ text = stringResourceSafe(R.string.common_available_networks),
+ style = TangemTheme.typography3.caption.medium,
+ color = TangemTheme.colors3.text.secondary,
+ )
+ AnimatedContent(
+ targetState = selectAllButton,
+ transitionSpec = {
+ fadeIn(animationSpec = tween(durationMillis = 400)) togetherWith
+ fadeOut(animationSpec = tween(durationMillis = 400))
+ },
+ label = "SelectAllButton",
+ ) { button ->
+ if (!button.text.isNullOrEmpty()) {
+ Text(
+ modifier = Modifier
+ .clickable(
+ interactionSource = remember { MutableInteractionSource() },
+ indication = null,
+ onClick = {
+ hapticManager.perform(TangemHapticEffect.View.SegmentTick)
+ onSelectAllClick()
+ },
+ )
+ .padding(start = 4.dp, top = 16.dp, bottom = 4.dp),
+ text = button.text.resolveReference(),
+ style = TangemTheme.typography3.caption.medium,
+ color = TangemTheme.colors3.text.brand,
+ )
+ }
+ }
+ }
+}
+
@Composable
private fun NetworkRow(item: NetworkItemUM) {
Row(
modifier = Modifier
.fillMaxWidth()
- .clickableSingle(onClick = item.onCheckedChange)
+ .clickable(
+ interactionSource = remember { MutableInteractionSource() },
+ indication = null,
+ onClick = item.onCheckedChange,
+ )
.padding(vertical = 16.dp),
verticalAlignment = Alignment.CenterVertically,
) {
@@ -182,6 +239,7 @@ private fun Preview_SelectNetworksContent() {
onCheckedChange = {},
),
),
+ selectAllButton = SelectNetworksUM.SelectAllButtonUM.SelectAll,
doneButton = TangemButtonUM(
text = TextReference.Res(R.string.common_done),
type = TangemButtonType.Primary,
@@ -189,6 +247,7 @@ private fun Preview_SelectNetworksContent() {
onClick = {},
),
onBackClick = {},
+ onSelectAllClick = {},
),
)
}
diff --git a/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/selectnetworks/ui/state/SelectNetworksUM.kt b/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/selectnetworks/ui/state/SelectNetworksUM.kt
index eb565d9c15..5b73615fa6 100644
--- a/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/selectnetworks/ui/state/SelectNetworksUM.kt
+++ b/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/selectnetworks/ui/state/SelectNetworksUM.kt
@@ -2,16 +2,21 @@ package com.tangem.features.addressbook.selectnetworks.ui.state
import androidx.annotation.DrawableRes
import androidx.compose.runtime.Immutable
+import com.tangem.core.ui.R
import com.tangem.core.ui.ds.button.TangemButtonUM
import com.tangem.core.ui.ds2.search.TangemSearch
+import com.tangem.core.ui.extensions.TextReference
+import com.tangem.core.ui.extensions.resourceReference
import kotlinx.collections.immutable.ImmutableList
@Immutable
internal data class SelectNetworksUM(
val searchBar: TangemSearch.State,
val networks: ImmutableList,
+ val selectAllButton: SelectAllButtonUM,
val doneButton: TangemButtonUM,
val onBackClick: () -> Unit,
+ val onSelectAllClick: () -> Unit,
) {
@Immutable
@@ -23,4 +28,10 @@ internal data class SelectNetworksUM(
val isSelected: Boolean,
val onCheckedChange: () -> Unit,
)
+
+ enum class SelectAllButtonUM(val text: TextReference) {
+ SelectAll(text = resourceReference(R.string.address_book_select_all_networks)),
+ ClearAll(text = resourceReference(R.string.address_book_clear_all_networks)),
+ Empty(text = TextReference.EMPTY),
+ }
}
\ No newline at end of file
diff --git a/features/address-book/impl/src/test/kotlin/com/tangem/features/addressbook/selectnetworks/model/SelectNetworksModelTest.kt b/features/address-book/impl/src/test/kotlin/com/tangem/features/addressbook/selectnetworks/model/SelectNetworksModelTest.kt
index 11ace5d9b1..ab4695ed82 100644
--- a/features/address-book/impl/src/test/kotlin/com/tangem/features/addressbook/selectnetworks/model/SelectNetworksModelTest.kt
+++ b/features/address-book/impl/src/test/kotlin/com/tangem/features/addressbook/selectnetworks/model/SelectNetworksModelTest.kt
@@ -5,15 +5,19 @@ import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchainsdk.utils.toNetworkId
import com.tangem.core.decompose.model.MutableParamsContainer
import com.tangem.core.decompose.model.ParamsContainer
+import com.tangem.features.addressbook.common.AddressBookAnalyticsSender
import com.tangem.features.addressbook.selectnetworks.DefaultSelectNetworksComponent
import com.tangem.features.addressbook.selectnetworks.state.SelectNetworksStateController
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
+import io.mockk.mockk
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
-import org.junit.jupiter.api.*
+import org.junit.jupiter.api.AfterEach
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.TestInstance
@OptIn(ExperimentalCoroutinesApi::class)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@@ -24,6 +28,8 @@ internal class SelectNetworksModelTest {
private var model: SelectNetworksModel? = null
+ private val analyticsSender: AddressBookAnalyticsSender = mockk(relaxed = true)
+
@AfterEach
fun tearDown() {
model?.onDestroy()
@@ -147,6 +153,7 @@ internal class SelectNetworksModelTest {
paramsContainer = paramsContainer,
dispatchers = testScope.createTestingCoroutineDispatcherProvider(),
stateController = SelectNetworksStateController(),
+ analyticsSender = analyticsSender,
).also { model = it }
}
diff --git a/features/address-book/impl/src/test/kotlin/com/tangem/features/addressbook/selectnetworks/state/transformers/UpdateNetworksContentTransformerTest.kt b/features/address-book/impl/src/test/kotlin/com/tangem/features/addressbook/selectnetworks/state/transformers/UpdateNetworksContentTransformerTest.kt
new file mode 100644
index 0000000000..fd721eedbc
--- /dev/null
+++ b/features/address-book/impl/src/test/kotlin/com/tangem/features/addressbook/selectnetworks/state/transformers/UpdateNetworksContentTransformerTest.kt
@@ -0,0 +1,251 @@
+package com.tangem.features.addressbook.selectnetworks.state.transformers
+
+import com.google.common.truth.Truth.assertThat
+import com.tangem.blockchain.common.Blockchain
+import com.tangem.blockchainsdk.utils.toNetworkId
+import com.tangem.core.ui.R
+import com.tangem.core.ui.ds.button.TangemButtonType
+import com.tangem.core.ui.ds.button.TangemButtonUM
+import com.tangem.core.ui.ds2.search.TangemSearch
+import com.tangem.core.ui.extensions.TextReference
+import com.tangem.core.ui.extensions.resourceReference
+import com.tangem.features.addressbook.selectnetworks.ui.state.SelectNetworksUM
+import com.tangem.features.addressbook.selectnetworks.ui.state.SelectNetworksUM.SelectAllButtonUM
+import com.tangem.test.core.ProvideTestModels
+import kotlinx.collections.immutable.persistentListOf
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.TestInstance
+import org.junit.jupiter.params.ParameterizedTest
+
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+internal class UpdateNetworksContentTransformerTest {
+
+ private val ethereum = Blockchain.Ethereum
+ private val bitcoin = Blockchain.Bitcoin
+ private val polygon = Blockchain.Polygon
+ private val allBlockchains = listOf(ethereum, bitcoin, polygon)
+
+ @Test
+ fun `GIVEN blank query WHEN transform THEN all matched networks shown in order`() {
+ // Arrange
+ val transformer = createTransformer(matchedBlockchains = allBlockchains, query = "")
+
+ // Act
+ val result = transformer.transform(createPrevState())
+
+ // Assert
+ assertThat(result.networks.map { it.id })
+ .containsExactly(ethereum.toNetworkId(), bitcoin.toNetworkId(), polygon.toNetworkId())
+ .inOrder()
+ }
+
+ @Test
+ fun `GIVEN network item WHEN transform THEN mapped fields match blockchain`() {
+ // Arrange
+ val transformer = createTransformer(matchedBlockchains = listOf(ethereum), query = "")
+
+ // Act
+ val item = transformer.transform(createPrevState()).networks.single()
+
+ // Assert
+ assertThat(item.id).isEqualTo(ethereum.toNetworkId())
+ assertThat(item.name).isEqualTo(ethereum.fullName)
+ assertThat(item.symbol).isEqualTo(ethereum.currency)
+ }
+
+ @Test
+ fun `GIVEN non-blank query WHEN transform THEN only matching networks shown`() {
+ // Arrange
+ val transformer = createTransformer(matchedBlockchains = allBlockchains, query = "bitcoin")
+
+ // Act
+ val result = transformer.transform(createPrevState())
+
+ // Assert
+ assertThat(result.networks.map { it.id }).containsExactly(bitcoin.toNetworkId())
+ }
+
+ @Test
+ fun `GIVEN query matching nothing WHEN transform THEN no networks shown`() {
+ // Arrange
+ val transformer = createTransformer(matchedBlockchains = allBlockchains, query = "no-such-network")
+
+ // Act
+ val result = transformer.transform(createPrevState())
+
+ // Assert
+ assertThat(result.networks).isEmpty()
+ }
+
+ @Test
+ fun `GIVEN selected network ids WHEN transform THEN isSelected reflects selection`() {
+ // Arrange
+ val transformer = createTransformer(
+ matchedBlockchains = allBlockchains,
+ selectedNetworkIds = setOf(ethereum.toNetworkId(), polygon.toNetworkId()),
+ )
+
+ // Act
+ val selectionById = transformer.transform(createPrevState()).networks.associate { it.id to it.isSelected }
+
+ // Assert
+ assertThat(selectionById).containsExactly(
+ ethereum.toNetworkId(), true,
+ bitcoin.toNetworkId(), false,
+ polygon.toNetworkId(), true,
+ )
+ }
+
+ @Test
+ fun `GIVEN item WHEN onCheckedChange invoked THEN onToggle called with network id`() {
+ // Arrange
+ val toggled = mutableListOf()
+ val transformer = createTransformer(
+ matchedBlockchains = listOf(ethereum),
+ onToggle = toggled::add,
+ )
+ val item = transformer.transform(createPrevState()).networks.single()
+
+ // Act
+ item.onCheckedChange()
+
+ // Assert
+ assertThat(toggled).containsExactly(ethereum.toNetworkId())
+ }
+
+ @Test
+ fun `GIVEN non-empty selection WHEN transform THEN done button enabled`() {
+ // Arrange
+ val transformer = createTransformer(
+ matchedBlockchains = allBlockchains,
+ selectedNetworkIds = setOf(ethereum.toNetworkId()),
+ )
+
+ // Act
+ val result = transformer.transform(createPrevState(isDoneEnabled = false))
+
+ // Assert
+ assertThat(result.doneButton.isEnabled).isTrue()
+ }
+
+ @Test
+ fun `GIVEN empty selection WHEN transform THEN done button disabled`() {
+ // Arrange
+ val transformer = createTransformer(
+ matchedBlockchains = allBlockchains,
+ selectedNetworkIds = emptySet(),
+ )
+
+ // Act
+ val result = transformer.transform(createPrevState(isDoneEnabled = true))
+
+ // Assert
+ assertThat(result.doneButton.isEnabled).isFalse()
+ }
+
+ @ParameterizedTest
+ @ProvideTestModels
+ fun `WHEN transform THEN select-all button state matches selection and search`(model: SelectAllButtonModel) {
+ // Arrange
+ val transformer = createTransformer(
+ matchedBlockchains = model.matchedBlockchains,
+ selectedNetworkIds = model.selectedNetworkIds,
+ isSearchActive = model.isSearchActive,
+ )
+
+ // Act
+ val result = transformer.transform(createPrevState())
+
+ // Assert
+ assertThat(result.selectAllButton).isEqualTo(model.expected)
+ }
+
+ private fun provideTestModels() = listOf(
+ SelectAllButtonModel(
+ description = "search active hides the button even when all selected",
+ matchedBlockchains = allBlockchains,
+ selectedNetworkIds = allBlockchains.map { it.toNetworkId() }.toSet(),
+ isSearchActive = true,
+ expected = SelectAllButtonUM.Empty,
+ ),
+ SelectAllButtonModel(
+ description = "search active hides the button when none selected",
+ matchedBlockchains = allBlockchains,
+ selectedNetworkIds = emptySet(),
+ isSearchActive = true,
+ expected = SelectAllButtonUM.Empty,
+ ),
+ SelectAllButtonModel(
+ description = "all selected shows Clear All",
+ matchedBlockchains = allBlockchains,
+ selectedNetworkIds = allBlockchains.map { it.toNetworkId() }.toSet(),
+ isSearchActive = false,
+ expected = SelectAllButtonUM.ClearAll,
+ ),
+ SelectAllButtonModel(
+ description = "partial selection shows Select All",
+ matchedBlockchains = allBlockchains,
+ selectedNetworkIds = setOf(ethereum.toNetworkId()),
+ isSearchActive = false,
+ expected = SelectAllButtonUM.SelectAll,
+ ),
+ SelectAllButtonModel(
+ description = "no selection shows Select All",
+ matchedBlockchains = allBlockchains,
+ selectedNetworkIds = emptySet(),
+ isSearchActive = false,
+ expected = SelectAllButtonUM.SelectAll,
+ ),
+ SelectAllButtonModel(
+ description = "no matched networks shows Select All",
+ matchedBlockchains = emptyList(),
+ selectedNetworkIds = emptySet(),
+ isSearchActive = false,
+ expected = SelectAllButtonUM.SelectAll,
+ ),
+ )
+
+ private fun createTransformer(
+ matchedBlockchains: List,
+ query: String = "",
+ selectedNetworkIds: Set = emptySet(),
+ isSearchActive: Boolean = false,
+ onToggle: (String) -> Unit = {},
+ ) = UpdateNetworksContentTransformer(
+ matchedBlockchains = matchedBlockchains,
+ query = query,
+ selectedNetworkIds = selectedNetworkIds,
+ isSearchActive = isSearchActive,
+ onToggle = onToggle,
+ )
+
+ private fun createPrevState(isDoneEnabled: Boolean = false) = SelectNetworksUM(
+ searchBar = TangemSearch.State(
+ placeholderText = resourceReference(R.string.common_search),
+ query = "",
+ onQueryChange = {},
+ isActive = false,
+ onActiveChange = {},
+ ),
+ networks = persistentListOf(),
+ selectAllButton = SelectAllButtonUM.Empty,
+ doneButton = TangemButtonUM(
+ text = TextReference.Res(R.string.common_done),
+ type = TangemButtonType.Primary,
+ isEnabled = isDoneEnabled,
+ onClick = {},
+ ),
+ onBackClick = {},
+ onSelectAllClick = {},
+ )
+
+ internal data class SelectAllButtonModel(
+ val description: String,
+ val matchedBlockchains: List,
+ val selectedNetworkIds: Set,
+ val isSearchActive: Boolean,
+ val expected: SelectAllButtonUM,
+ ) {
+ override fun toString(): String = description
+ }
+}
\ No newline at end of file