diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml
index 57b95dc184..0c188411c0 100644
--- a/core/res/src/main/res/values/strings.xml
+++ b/core/res/src/main/res/values/strings.xml
@@ -102,6 +102,7 @@
- %d address
- %d addresses
+ Contact
Contact name
Copy address
We couldn’t create contact. Please try again later.
diff --git a/features/address-book/impl/build.gradle.kts b/features/address-book/impl/build.gradle.kts
index 0e9ca8c98a..4711602801 100644
--- a/features/address-book/impl/build.gradle.kts
+++ b/features/address-book/impl/build.gradle.kts
@@ -19,6 +19,9 @@ dependencies {
implementation(projects.domain.models)
implementation(projects.domain.addressBook)
+ /** Common */
+ implementation(projects.common.ui)
+
/** Core modules */
implementation(projects.core.configToggles)
implementation(projects.core.decompose)
@@ -41,4 +44,7 @@ dependencies {
/** Other */
implementation(deps.kotlin.immutable.collections)
+
+ /** Tests */
+ testImplementation(projects.test.core)
}
\ No newline at end of file
diff --git a/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/component/AddressBookRoute.kt b/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/component/AddressBookRoute.kt
index baa9ee7021..49e12ab00f 100644
--- a/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/component/AddressBookRoute.kt
+++ b/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/component/AddressBookRoute.kt
@@ -7,4 +7,12 @@ internal sealed class AddressBookRoute {
@Serializable
data object List : AddressBookRoute()
+
+ /**
+ * if [contactId] is not null we should fetch existing contact
+ */
+ @Serializable
+ data class EditContact(
+ val contactId: String? = null,
+ ) : AddressBookRoute()
}
\ No newline at end of file
diff --git a/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/component/DefaultAddressBookComponent.kt b/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/component/DefaultAddressBookComponent.kt
index 78c5312faa..4b4ee9140a 100644
--- a/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/component/DefaultAddressBookComponent.kt
+++ b/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/component/DefaultAddressBookComponent.kt
@@ -9,11 +9,15 @@ import com.arkivanov.decompose.extensions.compose.stack.animation.stackAnimation
import com.arkivanov.decompose.extensions.compose.subscribeAsState
import com.arkivanov.decompose.router.stack.StackNavigation
import com.arkivanov.decompose.router.stack.childStack
+import com.arkivanov.decompose.router.stack.pop
+import com.arkivanov.decompose.router.stack.pushNew
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.childByContext
import com.tangem.core.ui.decompose.ComposableContentComponent
+import com.tangem.domain.addressbook.model.ContactId
import com.tangem.features.addressbook.AddressBookComponent
import com.tangem.features.addressbook.list.AddressBookListComponent
+import com.tangem.features.addressbook.editcontact.EditContactComponent
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@@ -22,6 +26,7 @@ internal class DefaultAddressBookComponent @AssistedInject constructor(
@Assisted context: AppComponentContext,
@Assisted private val params: AddressBookComponent.Params,
private val addressBookListComponentFactory: AddressBookListComponent.Factory,
+ private val editContactComponentFactory: EditContactComponent.Factory,
) : AddressBookComponent, AppComponentContext by context {
private val navigation = StackNavigation()
@@ -51,11 +56,16 @@ internal class DefaultAddressBookComponent @AssistedInject constructor(
context = childByContext(componentContext),
params = AddressBookListComponent.Params(
onContactClick = { contactId ->
- // TODO [REDACTED_TASK_KEY] router.push(EditContact(contactId))
- },
- onAddContactClick = {
- // TODO [REDACTED_TASK_KEY] router.push(AddContact)
+ navigation.pushNew(AddressBookRoute.EditContact(contactId))
},
+ onAddContactClick = { navigation.pushNew(AddressBookRoute.EditContact()) },
+ ),
+ )
+ is AddressBookRoute.EditContact -> editContactComponentFactory.create(
+ context = childByContext(componentContext),
+ params = EditContactComponent.Params(
+ contactId = config.contactId?.let(::ContactId),
+ onBackClick = { navigation.pop() },
),
)
}
diff --git a/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/di/AddressBookComponentModule.kt b/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/di/AddressBookComponentModule.kt
index 00e407405f..188884bab3 100644
--- a/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/di/AddressBookComponentModule.kt
+++ b/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/di/AddressBookComponentModule.kt
@@ -4,6 +4,8 @@ import com.tangem.features.addressbook.AddressBookComponent
import com.tangem.features.addressbook.component.DefaultAddressBookComponent
import com.tangem.features.addressbook.list.AddressBookListComponent
import com.tangem.features.addressbook.list.DefaultAddressBookListComponent
+import com.tangem.features.addressbook.editcontact.DefaultEditContactComponent
+import com.tangem.features.addressbook.editcontact.EditContactComponent
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
@@ -23,4 +25,8 @@ internal interface AddressBookComponentModule {
fun bindAddressBookListComponentFactory(
factory: DefaultAddressBookListComponent.Factory,
): AddressBookListComponent.Factory
+
+ @Binds
+ @Singleton
+ fun bindEditContactComponentFactory(factory: DefaultEditContactComponent.Factory): EditContactComponent.Factory
}
\ No newline at end of file
diff --git a/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/di/AddressBookModelModule.kt b/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/di/AddressBookModelModule.kt
index 7c666b0b6d..0fb085f06d 100644
--- a/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/di/AddressBookModelModule.kt
+++ b/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/di/AddressBookModelModule.kt
@@ -3,6 +3,7 @@ package com.tangem.features.addressbook.di
import com.tangem.core.decompose.di.ModelComponent
import com.tangem.core.decompose.model.Model
import com.tangem.features.addressbook.list.model.AddressBookListModel
+import com.tangem.features.addressbook.editcontact.model.EditContactModel
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
@@ -17,4 +18,9 @@ internal interface AddressBookModelModule {
@IntoMap
@ClassKey(AddressBookListModel::class)
fun bindAddressBookModel(model: AddressBookListModel): Model
+
+ @Binds
+ @IntoMap
+ @ClassKey(EditContactModel::class)
+ fun bindEditContactModel(model: EditContactModel): Model
}
\ No newline at end of file
diff --git a/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/editcontact/DefaultEditContactComponent.kt b/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/editcontact/DefaultEditContactComponent.kt
new file mode 100644
index 0000000000..8f83105d52
--- /dev/null
+++ b/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/editcontact/DefaultEditContactComponent.kt
@@ -0,0 +1,40 @@
+package com.tangem.features.addressbook.editcontact
+
+import androidx.activity.compose.BackHandler
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.tangem.core.decompose.context.AppComponentContext
+import com.tangem.core.decompose.model.getOrCreateModel
+import com.tangem.features.addressbook.editcontact.model.EditContactModel
+import com.tangem.features.addressbook.editcontact.ui.EditContactContent
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+internal class DefaultEditContactComponent @AssistedInject constructor(
+ @Assisted context: AppComponentContext,
+ @Assisted params: EditContactComponent.Params,
+) : EditContactComponent, AppComponentContext by context {
+
+ private val model: EditContactModel = getOrCreateModel(params)
+
+ @Composable
+ override fun Content(modifier: Modifier) {
+ val state by model.state.collectAsStateWithLifecycle()
+ EditContactContent(
+ state = state,
+ modifier = modifier,
+ )
+ BackHandler(onBack = state.onCloseClick)
+ }
+
+ @AssistedFactory
+ interface Factory : EditContactComponent.Factory {
+ override fun create(
+ context: AppComponentContext,
+ params: EditContactComponent.Params,
+ ): DefaultEditContactComponent
+ }
+}
\ No newline at end of file
diff --git a/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/editcontact/EditContactComponent.kt b/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/editcontact/EditContactComponent.kt
new file mode 100644
index 0000000000..ede28263b7
--- /dev/null
+++ b/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/editcontact/EditContactComponent.kt
@@ -0,0 +1,15 @@
+package com.tangem.features.addressbook.editcontact
+
+import com.tangem.core.decompose.factory.ComponentFactory
+import com.tangem.core.ui.decompose.ComposableContentComponent
+import com.tangem.domain.addressbook.model.ContactId
+
+internal interface EditContactComponent : ComposableContentComponent {
+
+ interface Factory : ComponentFactory
+
+ data class Params(
+ val contactId: ContactId?,
+ val onBackClick: () -> Unit,
+ )
+}
\ No newline at end of file
diff --git a/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/editcontact/contract/EditContactUM.kt b/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/editcontact/contract/EditContactUM.kt
new file mode 100644
index 0000000000..2a54242a81
--- /dev/null
+++ b/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/editcontact/contract/EditContactUM.kt
@@ -0,0 +1,26 @@
+package com.tangem.features.addressbook.editcontact.contract
+
+import androidx.compose.runtime.Immutable
+import com.tangem.common.ui.account.AccountIconUM
+import com.tangem.core.ui.extensions.TextReference
+import com.tangem.domain.models.account.CryptoPortfolioIcon
+import kotlinx.collections.immutable.ImmutableList
+
+@Immutable
+internal data class EditContactUM(
+ val title: TextReference,
+ val name: String,
+ val namePlaceholder: TextReference,
+ val portfolioIcon: AccountIconUM.CryptoPortfolio,
+ val colors: Colors,
+ val onNameChange: (String) -> Unit,
+ val onCloseClick: () -> Unit,
+) {
+
+ @Immutable
+ data class Colors(
+ val selected: CryptoPortfolioIcon.Color,
+ val list: ImmutableList,
+ val onColorSelect: (CryptoPortfolioIcon.Color) -> Unit,
+ )
+}
\ No newline at end of file
diff --git a/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/editcontact/model/EditContactModel.kt b/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/editcontact/model/EditContactModel.kt
new file mode 100644
index 0000000000..1f1035618b
--- /dev/null
+++ b/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/editcontact/model/EditContactModel.kt
@@ -0,0 +1,68 @@
+package com.tangem.features.addressbook.editcontact.model
+
+import com.tangem.common.ui.account.AccountIconUM
+import com.tangem.core.decompose.di.ModelScoped
+import com.tangem.core.decompose.model.Model
+import com.tangem.core.decompose.model.ParamsContainer
+import com.tangem.core.ui.R
+import com.tangem.core.ui.extensions.resourceReference
+import com.tangem.domain.models.account.CryptoPortfolioIcon
+import com.tangem.features.addressbook.editcontact.EditContactComponent
+import com.tangem.features.addressbook.editcontact.contract.EditContactUM
+import com.tangem.utils.coroutines.CoroutineDispatcherProvider
+import kotlinx.collections.immutable.toImmutableList
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.update
+import javax.inject.Inject
+
+@ModelScoped
+internal class EditContactModel @Inject constructor(
+ paramsContainer: ParamsContainer,
+ override val dispatchers: CoroutineDispatcherProvider,
+) : Model() {
+
+ private val params: EditContactComponent.Params = paramsContainer.require()
+
+ val state: StateFlow
+ field = MutableStateFlow(getInitialState())
+
+ private fun onNameChange(name: String) {
+ state.update { it.copy(name = name) }
+ }
+
+ private fun onColorSelect(color: CryptoPortfolioIcon.Color) {
+ state.update { oldState ->
+ oldState.copy(
+ colors = oldState.colors.copy(selected = color),
+ portfolioIcon = oldState.portfolioIcon.copy(color = color),
+ )
+ }
+ }
+
+ private fun getInitialState(): EditContactUM {
+ val colors = CryptoPortfolioIcon.Color.entries.toImmutableList()
+ val selectedColor = colors.first()
+ val titleResId = if (params.contactId == null) {
+ R.string.address_book_new_contact
+ } else {
+ R.string.address_book_contact
+ }
+ return EditContactUM(
+ title = resourceReference(titleResId),
+ name = "",
+ namePlaceholder = resourceReference(R.string.address_book_new_contact),
+ portfolioIcon = AccountIconUM.CryptoPortfolio(
+ value = CryptoPortfolioIcon.Icon.Letter,
+ color = selectedColor,
+ ),
+ colors = EditContactUM.Colors(
+ selected = selectedColor,
+ list = colors,
+ onColorSelect = ::onColorSelect,
+ ),
+ onNameChange = ::onNameChange,
+ onCloseClick = params.onBackClick,
+ )
+ }
+}
\ No newline at end of file
diff --git a/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/editcontact/ui/EditContactContent.kt b/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/editcontact/ui/EditContactContent.kt
new file mode 100644
index 0000000000..2183d33c43
--- /dev/null
+++ b/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/editcontact/ui/EditContactContent.kt
@@ -0,0 +1,185 @@
+package com.tangem.features.addressbook.editcontact.ui
+
+import android.content.res.Configuration
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.*
+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.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.util.fastForEach
+import com.tangem.common.ui.account.AccountIcon
+import com.tangem.common.ui.account.AccountIconUM
+import com.tangem.common.ui.account.getUiColor
+import com.tangem.core.ui.R
+import com.tangem.core.ui.components.account.AccountIconSize
+import com.tangem.core.ui.components.fields.AutoSizeTextField
+import com.tangem.core.ui.ds.image.TangemIconUM
+import com.tangem.core.ui.ds.topbar.TangemTopBar
+import com.tangem.core.ui.ds2.button.TangemButton
+import com.tangem.core.ui.extensions.resolveReference
+import com.tangem.core.ui.extensions.stringReference
+import com.tangem.core.ui.extensions.stringResourceSafe
+import com.tangem.core.ui.res.TangemTheme
+import com.tangem.core.ui.res.TangemThemePreview
+import com.tangem.domain.models.account.CryptoPortfolioIcon
+import com.tangem.features.addressbook.editcontact.contract.EditContactUM
+import kotlinx.collections.immutable.toImmutableList
+
+@Composable
+internal fun EditContactContent(state: EditContactUM, modifier: Modifier = Modifier) {
+ Column(
+ modifier = modifier
+ .fillMaxSize()
+ .background(color = TangemTheme.colors3.bg.primary)
+ .systemBarsPadding(),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ TangemTopBar(
+ modifier = Modifier.statusBarsPadding(),
+ title = state.title,
+ endContent = {
+ TangemButton(
+ iconStart = TangemIconUM.Icon(iconRes = R.drawable.ic_close_24),
+ onClick = state.onCloseClick,
+ size = TangemButton.Size.X11,
+ variant = TangemButton.Variant.Material,
+ )
+ },
+ )
+
+ Column(
+ modifier = Modifier
+ .padding(horizontal = 16.dp)
+ .weight(1f),
+ verticalArrangement = Arrangement.spacedBy(12.dp),
+ ) {
+ ContactSummary(state = state)
+ ContactColor(colors = state.colors)
+ }
+ }
+}
+
+@Composable
+private fun ContactSummary(state: EditContactUM) {
+ val avatarName = state.name.ifBlank { state.namePlaceholder.resolveReference() }
+ Column(
+ modifier = Modifier
+ .clip(RoundedCornerShape(16.dp))
+ .fillMaxWidth()
+ .background(TangemTheme.colors3.bg.secondary),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Spacer(modifier = Modifier.height(24.dp))
+
+ AccountIcon(
+ name = stringReference(avatarName),
+ icon = state.portfolioIcon,
+ size = AccountIconSize.Large,
+ )
+ Spacer(modifier = Modifier.height(24.dp))
+
+ Text(
+ text = stringResourceSafe(R.string.address_book_contact_name),
+ style = TangemTheme.typography3.caption.medium,
+ color = TangemTheme.colors3.text.tertiary,
+ )
+ Spacer(modifier = Modifier.height(2.dp))
+
+ AutoSizeTextField(
+ value = state.name,
+ onValueChange = state.onNameChange,
+ centered = true,
+ singleLine = true,
+ placeholder = state.namePlaceholder,
+ textStyle = TangemTheme.typography3.heading.medium,
+ color = TangemTheme.colors3.text.primary,
+ placeholderColor = TangemTheme.colors3.text.tertiary,
+ )
+ Spacer(modifier = Modifier.height(20.dp))
+ }
+}
+
+@OptIn(ExperimentalLayoutApi::class)
+@Suppress("MagicNumber")
+@Composable
+private fun ContactColor(colors: EditContactUM.Colors) {
+ Box(
+ modifier = Modifier
+ .clip(RoundedCornerShape(16.dp))
+ .fillMaxWidth()
+ .background(TangemTheme.colors3.bg.secondary),
+ ) {
+ FlowRow(
+ maxItemsInEachRow = 6,
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 8.dp, vertical = 12.dp),
+ horizontalArrangement = Arrangement.spacedBy(4.dp, Alignment.CenterHorizontally),
+ ) {
+ colors.list.fastForEach { color ->
+ val isSelected = color == colors.selected
+ Box(
+ contentAlignment = Alignment.Center,
+ modifier = Modifier
+ .clip(CircleShape)
+ .clickable(onClick = { colors.onColorSelect(color) })
+ .size(48.dp),
+ ) {
+ if (isSelected) {
+ Box(
+ modifier = Modifier
+ .size(47.dp)
+ .border(2.dp, color.getUiColor(), shape = CircleShape),
+ )
+ Box(
+ modifier = Modifier
+ .size(36.dp)
+ .background(color = color.getUiColor(), shape = CircleShape),
+ )
+ } else {
+ Box(
+ modifier = Modifier
+ .size(40.dp)
+ .background(color = color.getUiColor(), shape = CircleShape),
+ )
+ }
+ }
+ }
+ }
+ }
+}
+
+@Preview(showBackground = true, widthDp = 360)
+@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
+@Composable
+private fun Preview_EditContactContent() {
+ val colors = CryptoPortfolioIcon.Color.entries.toImmutableList()
+ TangemThemePreview {
+ EditContactContent(
+ state = EditContactUM(
+ title = stringReference("New contact"),
+ name = "",
+ namePlaceholder = stringReference("New contact"),
+ portfolioIcon = AccountIconUM.CryptoPortfolio(
+ value = CryptoPortfolioIcon.Icon.Letter,
+ color = colors.first(),
+ ),
+ colors = EditContactUM.Colors(
+ selected = colors.first(),
+ list = colors,
+ onColorSelect = {},
+ ),
+ onNameChange = {},
+ onCloseClick = {},
+ ),
+ )
+ }
+}
\ No newline at end of file
diff --git a/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/list/DefaultAddressBookListComponent.kt b/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/list/DefaultAddressBookListComponent.kt
index 416edbd08c..63733b90f9 100644
--- a/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/list/DefaultAddressBookListComponent.kt
+++ b/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/list/DefaultAddressBookListComponent.kt
@@ -23,9 +23,9 @@ internal class DefaultAddressBookListComponent @AssistedInject constructor(
@Composable
override fun Content(modifier: Modifier) {
val state by model.state.collectAsStateWithLifecycle()
- when (state) {
- AddressBookListUM.Empty -> AddressBookEmptyScreen(
- onAddContactClick = params.onAddContactClick,
+ when (val addressBookListUM = state) {
+ is AddressBookListUM.Empty -> AddressBookEmptyScreen(
+ tangemButtonUM = addressBookListUM.tangemButtonUM,
onBackClick = router::pop,
modifier = modifier,
)
diff --git a/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/list/contract/AddressBookListUM.kt b/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/list/contract/AddressBookListUM.kt
index f5cf907646..4c0c74bab4 100644
--- a/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/list/contract/AddressBookListUM.kt
+++ b/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/list/contract/AddressBookListUM.kt
@@ -1,12 +1,15 @@
package com.tangem.features.addressbook.list.contract
import androidx.compose.runtime.Immutable
+import com.tangem.core.ui.ds.button.TangemButtonUM
import com.tangem.domain.addressbook.model.Contact
import kotlinx.collections.immutable.ImmutableList
@Immutable
internal sealed class AddressBookListUM {
- data object Empty : AddressBookListUM()
+ data class Empty(
+ val tangemButtonUM: TangemButtonUM,
+ ) : AddressBookListUM()
data class AddressList(val contacts: ImmutableList) : AddressBookListUM()
}
\ No newline at end of file
diff --git a/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/list/model/AddressBookListModel.kt b/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/list/model/AddressBookListModel.kt
index 224fa6edd0..27039d82aa 100644
--- a/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/list/model/AddressBookListModel.kt
+++ b/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/list/model/AddressBookListModel.kt
@@ -2,6 +2,16 @@ package com.tangem.features.addressbook.list.model
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
+import com.tangem.core.decompose.model.ParamsContainer
+import com.tangem.core.ui.R
+import com.tangem.core.ui.R.drawable.ic_plus_24
+import com.tangem.core.ui.ds.button.TangemButtonIconPosition
+import com.tangem.core.ui.ds.button.TangemButtonType
+import com.tangem.core.ui.ds.button.TangemButtonUM
+import com.tangem.core.ui.ds.image.TangemIconUM
+import com.tangem.core.ui.extensions.TextReference
+import com.tangem.core.ui.res.TangemTheme
+import com.tangem.features.addressbook.list.AddressBookListComponent
import com.tangem.features.addressbook.list.contract.AddressBookListUM
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.MutableStateFlow
@@ -10,10 +20,24 @@ import javax.inject.Inject
@ModelScoped
internal class AddressBookListModel @Inject constructor(
+ paramsContainer: ParamsContainer,
override val dispatchers: CoroutineDispatcherProvider,
) : Model() {
+ private val params = paramsContainer.require()
+
val state: StateFlow = MutableStateFlow(
- AddressBookListUM.Empty,
+ AddressBookListUM.Empty(
+ tangemButtonUM = TangemButtonUM(
+ text = TextReference.Res(R.string.address_book_new_contact),
+ tangemIconUM = TangemIconUM.Icon(
+ iconRes = ic_plus_24,
+ tintReference = { TangemTheme.colors3.text.inverse.primary },
+ ),
+ iconPosition = TangemButtonIconPosition.End,
+ type = TangemButtonType.Primary,
+ onClick = params.onAddContactClick,
+ ),
+ ),
)
}
\ No newline at end of file
diff --git a/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/list/ui/AddressBookEmptyScreen.kt b/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/list/ui/AddressBookEmptyScreen.kt
index 8c258eb5eb..b1045b5cc2 100644
--- a/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/list/ui/AddressBookEmptyScreen.kt
+++ b/features/address-book/impl/src/main/kotlin/com/tangem/features/addressbook/list/ui/AddressBookEmptyScreen.kt
@@ -14,17 +14,21 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.R
-import com.tangem.core.ui.components.PrimaryButtonIconEnd
+import com.tangem.core.ui.ds.button.PrimaryTangemButton
+import com.tangem.core.ui.ds.button.TangemButtonIconPosition
+import com.tangem.core.ui.ds.button.TangemButtonType
+import com.tangem.core.ui.ds.button.TangemButtonUM
import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.ds.topbar.TangemTopBar
import com.tangem.core.ui.ds2.button.TangemButton
+import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
@Composable
internal fun AddressBookEmptyScreen(
- onAddContactClick: () -> Unit,
+ tangemButtonUM: TangemButtonUM,
onBackClick: () -> Unit,
modifier: Modifier = Modifier,
) {
@@ -40,19 +44,17 @@ internal fun AddressBookEmptyScreen(
iconStart = TangemIconUM.Icon(iconRes = R.drawable.ic_back_24),
onClick = onBackClick,
size = TangemButton.Size.X11,
- variant = TangemButton.Variant.Secondary,
+ variant = TangemButton.Variant.Material,
)
},
)
NoContactInfo()
- PrimaryButtonIconEnd(
+ PrimaryTangemButton(
modifier = Modifier
.fillMaxWidth()
.navigationBarsPadding()
.padding(start = 16.dp, end = 16.dp, bottom = 12.dp),
- text = stringResourceSafe(R.string.address_book_add_contact),
- iconResId = R.drawable.ic_plus_24,
- onClick = onAddContactClick,
+ buttonUM = tangemButtonUM,
)
}
}
@@ -104,5 +106,14 @@ private fun ContactImage() {
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun Preview_AddressBookEmptyScreen() {
- AddressBookEmptyScreen(onAddContactClick = {}, onBackClick = {})
+ AddressBookEmptyScreen(
+ tangemButtonUM = TangemButtonUM(
+ text = TextReference.Res(R.string.address_book_new_contact),
+ tangemIconUM = TangemIconUM.Icon(iconRes = R.drawable.ic_plus_24),
+ iconPosition = TangemButtonIconPosition.End,
+ type = TangemButtonType.Secondary,
+ onClick = {},
+ ),
+ onBackClick = {},
+ )
}
\ No newline at end of file
diff --git a/features/address-book/impl/src/test/kotlin/com/tangem/features/addressbook/editcontact/model/EditContactModelTest.kt b/features/address-book/impl/src/test/kotlin/com/tangem/features/addressbook/editcontact/model/EditContactModelTest.kt
new file mode 100644
index 0000000000..d44b437435
--- /dev/null
+++ b/features/address-book/impl/src/test/kotlin/com/tangem/features/addressbook/editcontact/model/EditContactModelTest.kt
@@ -0,0 +1,113 @@
+package com.tangem.features.addressbook.editcontact.model
+
+import com.google.common.truth.Truth.assertThat
+import com.tangem.common.ui.account.AccountIconUM
+import com.tangem.core.decompose.model.MutableParamsContainer
+import com.tangem.core.decompose.model.ParamsContainer
+import com.tangem.core.ui.R
+import com.tangem.core.ui.extensions.resourceReference
+import com.tangem.domain.addressbook.model.ContactId
+import com.tangem.domain.models.account.CryptoPortfolioIcon
+import com.tangem.features.addressbook.editcontact.EditContactComponent
+import com.tangem.features.addressbook.editcontact.contract.EditContactUM
+import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
+import kotlinx.collections.immutable.toImmutableList
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.jupiter.api.Test
+
+@OptIn(ExperimentalCoroutinesApi::class)
+internal class EditContactModelTest {
+
+ @Test
+ fun `WHEN model created THEN initial state is correct`() = runTest {
+ val expectedColors = CryptoPortfolioIcon.Color.entries.toImmutableList()
+ val expectedSelectedColor = expectedColors.first()
+
+ val model = createModel(testScope = this)
+ val state = model.state.value
+
+ val expected = EditContactUM(
+ title = resourceReference(R.string.address_book_new_contact),
+ name = "",
+ namePlaceholder = resourceReference(R.string.address_book_new_contact),
+ portfolioIcon = AccountIconUM.CryptoPortfolio(
+ value = CryptoPortfolioIcon.Icon.Letter,
+ color = expectedSelectedColor,
+ ),
+ colors = EditContactUM.Colors(
+ selected = expectedSelectedColor,
+ list = expectedColors,
+ onColorSelect = state.colors.onColorSelect,
+ ),
+ onNameChange = state.onNameChange,
+ onCloseClick = state.onCloseClick,
+ )
+ assertThat(state).isEqualTo(expected)
+ }
+
+ @Test
+ fun `GIVEN existing contactId WHEN model created THEN title is contact`() = runTest {
+ // Arrange
+ val params = EditContactComponent.Params(
+ contactId = ContactId(value = "contact-id"),
+ onBackClick = {},
+ )
+
+ // Act
+ val model = createModel(testScope = this, params = params)
+ val state = model.state.value
+
+ // Assert
+ assertThat(state.title).isEqualTo(resourceReference(R.string.address_book_contact))
+ }
+
+ @Test
+ fun `GIVEN initial state WHEN onNameChange THEN name updated`() = runTest {
+ val model = createModel(testScope = this)
+ val newName = "Satoshi"
+
+ model.state.value.onNameChange(newName)
+
+ assertThat(model.state.value.name).isEqualTo(newName)
+ }
+
+ @Test
+ fun `GIVEN initial state WHEN onColorSelect THEN selected color and portfolio icon updated`() = runTest {
+ val model = createModel(testScope = this)
+ val newColor = CryptoPortfolioIcon.Color.entries.last()
+
+ model.state.value.colors.onColorSelect(newColor)
+
+ val state = model.state.value
+ assertThat(state.colors.selected).isEqualTo(newColor)
+ assertThat(state.portfolioIcon.color).isEqualTo(newColor)
+ }
+
+ private fun createModel(
+ testScope: TestScope,
+ params: EditContactComponent.Params = EditContactComponent.Params(
+ contactId = null,
+ onBackClick = {},
+ ),
+ paramsContainer: ParamsContainer = MutableParamsContainer(value = params),
+ ): EditContactModel {
+ return EditContactModel(
+ paramsContainer = paramsContainer,
+ dispatchers = testScope.createTestingCoroutineDispatcherProvider(),
+ )
+ }
+
+ private fun TestScope.createTestingCoroutineDispatcherProvider(): TestingCoroutineDispatcherProvider {
+ val testDispatcher = StandardTestDispatcher(testScheduler)
+ return TestingCoroutineDispatcherProvider(
+ main = testDispatcher,
+ mainImmediate = testDispatcher,
+ io = testDispatcher,
+ default = testDispatcher,
+ single = testDispatcher,
+ )
+ }
+}
\ No newline at end of file