diff --git a/features/onramp/impl/build.gradle.kts b/features/onramp/impl/build.gradle.kts index ea3b825ef0..2df9a81756 100644 --- a/features/onramp/impl/build.gradle.kts +++ b/features/onramp/impl/build.gradle.kts @@ -34,6 +34,7 @@ dependencies { implementation(projects.domain.appCurrency.models) implementation(projects.domain.balanceHiding) implementation(projects.domain.balanceHiding.models) + implementation(projects.domain.card) implementation(projects.domain.demo) implementation(projects.domain.legacy) implementation(projects.domain.models) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/DefaultOnrampAddToPortfolioComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/DefaultOnrampAddToPortfolioComponent.kt new file mode 100644 index 0000000000..aad77fc333 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/DefaultOnrampAddToPortfolioComponent.kt @@ -0,0 +1,53 @@ +package com.tangem.features.onramp.hottokens.portfolio + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent +import com.tangem.features.onramp.hottokens.portfolio.model.OnrampAddToPortfolioModel +import com.tangem.features.onramp.hottokens.portfolio.ui.OnrampAddToPortfolioBottomSheet +import com.tangem.features.onramp.hottokens.portfolio.ui.OnrampAddToPortfolioContent +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +internal class DefaultOnrampAddToPortfolioComponent @AssistedInject constructor( + @Assisted context: AppComponentContext, + @Assisted private val params: OnrampAddToPortfolioComponent.Params, +) : OnrampAddToPortfolioComponent, AppComponentContext by context { + + private val model: OnrampAddToPortfolioModel = getOrCreateModel(params) + + override fun dismiss() { + params.onDismiss() + } + + @Composable + override fun BottomSheet() { + val state by model.state.collectAsStateWithLifecycle() + val bottomSheetConfig = remember(key1 = this) { + TangemBottomSheetConfig( + isShown = true, + onDismissRequest = ::dismiss, + content = TangemBottomSheetConfigContent.Empty, + ) + } + + OnrampAddToPortfolioBottomSheet( + config = bottomSheetConfig, + content = { OnrampAddToPortfolioContent(state = state) }, + ) + } + + @AssistedFactory + interface Factory : OnrampAddToPortfolioComponent.Factory { + override fun create( + context: AppComponentContext, + params: OnrampAddToPortfolioComponent.Params, + ): DefaultOnrampAddToPortfolioComponent + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/OnrampAddToPortfolioComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/OnrampAddToPortfolioComponent.kt new file mode 100644 index 0000000000..268e140a96 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/OnrampAddToPortfolioComponent.kt @@ -0,0 +1,34 @@ +package com.tangem.features.onramp.hottokens.portfolio + +import com.tangem.core.decompose.factory.ComponentFactory +import com.tangem.core.ui.components.currency.icon.CurrencyIconState +import com.tangem.core.ui.decompose.ComposableBottomSheetComponent +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.wallets.models.UserWalletId + +/** + * Onramp bottom sheet component for adding token to portfolio + * +[REDACTED_AUTHOR] + */ +interface OnrampAddToPortfolioComponent : ComposableBottomSheetComponent { + + /** + * Params + * + * @property userWalletId user wallet id + * @property cryptoCurrency crypto currency + * @property currencyIconState currency icon state + * @property onSuccessAdding lambda be invoked when token will added + * @property onDismiss lambda be invoked when bottom sheet is dismissed + */ + data class Params( + val userWalletId: UserWalletId, + val cryptoCurrency: CryptoCurrency, + val currencyIconState: CurrencyIconState, + val onSuccessAdding: () -> Unit, + val onDismiss: () -> Unit, + ) + + interface Factory : ComponentFactory +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/di/OnrampAddToPortfolioComponentModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/di/OnrampAddToPortfolioComponentModule.kt new file mode 100644 index 0000000000..57d2660750 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/di/OnrampAddToPortfolioComponentModule.kt @@ -0,0 +1,20 @@ +package com.tangem.features.onramp.hottokens.portfolio.di + +import com.tangem.features.onramp.hottokens.portfolio.DefaultOnrampAddToPortfolioComponent +import com.tangem.features.onramp.hottokens.portfolio.OnrampAddToPortfolioComponent +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal interface OnrampAddToPortfolioComponentModule { + + @Binds + @Singleton + fun bindOnrampAddToPortfolioComponentFactory( + factory: DefaultOnrampAddToPortfolioComponent.Factory, + ): OnrampAddToPortfolioComponent.Factory +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/di/OnrampAddToPortfolioModelModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/di/OnrampAddToPortfolioModelModule.kt new file mode 100644 index 0000000000..086a66b0d0 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/di/OnrampAddToPortfolioModelModule.kt @@ -0,0 +1,20 @@ +package com.tangem.features.onramp.hottokens.portfolio.di + +import com.tangem.core.decompose.di.DecomposeComponent +import com.tangem.core.decompose.model.Model +import com.tangem.features.onramp.hottokens.portfolio.model.OnrampAddToPortfolioModel +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +@Module +@InstallIn(DecomposeComponent::class) +internal interface OnrampAddToPortfolioModelModule { + + @Binds + @IntoMap + @ClassKey(OnrampAddToPortfolioModel::class) + fun bindOnrampSelectCountryModel(model: OnrampAddToPortfolioModel): Model +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/entity/OnrampAddToPortfolioBSConfig.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/entity/OnrampAddToPortfolioBSConfig.kt new file mode 100644 index 0000000000..5136e3709e --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/entity/OnrampAddToPortfolioBSConfig.kt @@ -0,0 +1,13 @@ +package com.tangem.features.onramp.hottokens.portfolio.entity + +import com.tangem.core.ui.components.currency.icon.CurrencyIconState +import com.tangem.domain.tokens.model.CryptoCurrency + +internal sealed class OnrampAddToPortfolioBSConfig { + + data class AddToPortfolio( + val cryptoCurrency: CryptoCurrency, + val currencyIconState: CurrencyIconState, + val onSuccessAdding: () -> Unit, + ) : OnrampAddToPortfolioBSConfig() +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/entity/OnrampAddToPortfolioUM.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/entity/OnrampAddToPortfolioUM.kt new file mode 100644 index 0000000000..44a2c35c1e --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/entity/OnrampAddToPortfolioUM.kt @@ -0,0 +1,34 @@ +package com.tangem.features.onramp.hottokens.portfolio.entity + +import com.tangem.core.ui.components.currency.icon.CurrencyIconState +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.wrappedList +import com.tangem.features.onramp.impl.R + +/** + * Onramp add to portfolio UM + * + * @property currencyName currency name + * @property networkName network name + * @property currencyIconState currency icon state + * @property onAddClick lambda be invoked when add button is clicked + * +[REDACTED_AUTHOR] + */ +data class OnrampAddToPortfolioUM( + val currencyName: String, + val networkName: String, + val currencyIconState: CurrencyIconState, + val onAddClick: () -> Unit, +) { + + val bsTitle: TextReference = resourceReference(id = R.string.common_add_token) + + val subtitle: TextReference = resourceReference( + id = R.string.hot_crypto_token_network, + formatArgs = wrappedList(networkName), + ) + + val addButtonText: TextReference = resourceReference(R.string.common_add_to_portfolio) +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/model/OnrampAddToPortfolioModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/model/OnrampAddToPortfolioModel.kt new file mode 100644 index 0000000000..83470a0ffa --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/model/OnrampAddToPortfolioModel.kt @@ -0,0 +1,63 @@ +package com.tangem.features.onramp.hottokens.portfolio.model + +import arrow.core.getOrElse +import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.domain.card.DerivePublicKeysUseCase +import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase +import com.tangem.features.onramp.hottokens.portfolio.OnrampAddToPortfolioComponent +import com.tangem.features.onramp.hottokens.portfolio.entity.OnrampAddToPortfolioUM +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch +import timber.log.Timber +import javax.inject.Inject + +/** + * Model for adding token to portfolio + * + * @param paramsContainer params container + * @property dispatchers dispatchers + * @property derivePublicKeysUseCase use case for deriving public key + * @property addCryptoCurrenciesUseCase use case for adding crypto currency + * +[REDACTED_AUTHOR] + */ +@ComponentScoped +internal class OnrampAddToPortfolioModel @Inject constructor( + paramsContainer: ParamsContainer, + override val dispatchers: CoroutineDispatcherProvider, + private val derivePublicKeysUseCase: DerivePublicKeysUseCase, + private val addCryptoCurrenciesUseCase: AddCryptoCurrenciesUseCase, +) : Model() { + + val state: StateFlow get() = _state + private val _state = MutableStateFlow(value = getInitialState()) + + private val params: OnrampAddToPortfolioComponent.Params = paramsContainer.require() + + private fun getInitialState(): OnrampAddToPortfolioUM { + return OnrampAddToPortfolioUM( + currencyName = params.cryptoCurrency.name, + networkName = params.cryptoCurrency.network.name, + currencyIconState = params.currencyIconState, + onAddClick = ::onAddClick, + ) + } + + private fun onAddClick() { + modelScope.launch { + derivePublicKeysUseCase(params.userWalletId, listOfNotNull(params.cryptoCurrency)).getOrElse { + Timber.e("Failed to derive public keys: $it") + } + + addCryptoCurrenciesUseCase( + userWalletId = params.userWalletId, + currencies = listOfNotNull(params.cryptoCurrency), + ) + .onRight { params.onSuccessAdding() } + } + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/ui/OnrampAddToPortfolioBottomSheet.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/ui/OnrampAddToPortfolioBottomSheet.kt new file mode 100644 index 0000000000..253dd52eed --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/ui/OnrampAddToPortfolioBottomSheet.kt @@ -0,0 +1,17 @@ +package com.tangem.features.onramp.hottokens.portfolio.ui + +import androidx.compose.runtime.Composable +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent +import com.tangem.core.ui.res.TangemTheme + +@Composable +internal fun OnrampAddToPortfolioBottomSheet(config: TangemBottomSheetConfig, content: @Composable () -> Unit) { + TangemBottomSheet( + config = config, + addBottomInsets = true, + containerColor = TangemTheme.colors.background.tertiary, + content = { content() }, + ) +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/ui/OnrampAddToPortfolioContent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/ui/OnrampAddToPortfolioContent.kt new file mode 100644 index 0000000000..d57182b16b --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/ui/OnrampAddToPortfolioContent.kt @@ -0,0 +1,162 @@ +package com.tangem.features.onramp.hottokens.portfolio.ui + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +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.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.tangem.core.ui.components.SpacerH +import com.tangem.core.ui.components.SpacerH12 +import com.tangem.core.ui.components.SpacerH16 +import com.tangem.core.ui.components.SpacerH24 +import com.tangem.core.ui.components.appbar.TangemTopAppBar +import com.tangem.core.ui.components.appbar.TangemTopAppBarHeight +import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent +import com.tangem.core.ui.components.buttons.common.TangemButton +import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition +import com.tangem.core.ui.components.buttons.common.TangemButtonSize +import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults +import com.tangem.core.ui.components.currency.icon.CurrencyIcon +import com.tangem.core.ui.components.currency.icon.CurrencyIconState +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.res.TangemColorPalette +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.features.onramp.hottokens.portfolio.entity.OnrampAddToPortfolioUM +import com.tangem.features.onramp.impl.R + +/** + * Onramp add to portfolio content + * + * @param state state + */ +@Composable +fun OnrampAddToPortfolioContent(state: OnrampAddToPortfolioUM) { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + TopAppBar(text = state.bsTitle) + + SpacerH(height = 19.dp) + + CurrencyIcon(state = state.currencyIconState) + + SpacerH12() + + CurrencyName( + text = state.currencyName, + modifier = Modifier.padding(horizontal = 16.dp), + ) + + SpacerH(height = 6.dp) + + CurrencyNetworkName( + text = state.subtitle, + modifier = Modifier.padding(horizontal = 16.dp), + ) + + SpacerH24() + + AddToPortfolioButton( + text = state.addButtonText, + onClick = state.onAddClick, + modifier = Modifier.padding(horizontal = 16.dp), + ) + + SpacerH16() + } +} + +@Composable +private fun TopAppBar(text: TextReference) { + TangemTopAppBar( + title = text.resolveReference(), + endButton = TopAppBarButtonUM( + iconRes = R.drawable.ic_information_24, + onIconClicked = {}, + ), + iconTint = TangemTheme.colors.icon.informative, + titleAlignment = Alignment.CenterHorizontally, + height = TangemTopAppBarHeight.BOTTOM_SHEET, + ) +} + +@Composable +private fun CurrencyName(text: String, modifier: Modifier = Modifier) { + Text( + text = text, + modifier = modifier, + color = TangemTheme.colors.text.primary1, + maxLines = 1, + style = TangemTheme.typography.subtitle1, + ) +} + +@Composable +private fun CurrencyNetworkName(text: TextReference, modifier: Modifier = Modifier) { + Text( + text = text.resolveReference(), + modifier = modifier, + color = TangemTheme.colors.text.tertiary, + maxLines = 1, + style = TangemTheme.typography.body2, + ) +} + +@Composable +private fun AddToPortfolioButton(text: TextReference, onClick: () -> Unit, modifier: Modifier = Modifier) { + TangemButton( + text = text.resolveReference(), + icon = TangemButtonIconPosition.End(iconResId = R.drawable.ic_tangem_24), + onClick = onClick, + colors = TangemButtonsDefaults.primaryButtonColors, + showProgress = false, + enabled = true, + modifier = modifier.fillMaxWidth(), + size = TangemButtonSize.WideAction, + textStyle = TangemTheme.typography.subtitle1, + ) +} + +@Preview +@Composable +private fun PreviewOnrampAddToPortfolioContent() { + TangemThemePreview { + val bottomSheetConfig = remember { + TangemBottomSheetConfig( + isShown = true, + onDismissRequest = {}, + content = TangemBottomSheetConfigContent.Empty, + ) + } + OnrampAddToPortfolioBottomSheet( + config = bottomSheetConfig, + content = { + OnrampAddToPortfolioContent( + state = OnrampAddToPortfolioUM( + currencyName = "Tether", + networkName = "Ethereum", + currencyIconState = CurrencyIconState.TokenIcon( + url = null, + topBadgeIconResId = R.drawable.img_eth_22, + fallbackTint = TangemColorPalette.Black, + fallbackBackground = TangemColorPalette.Meadow, + isGrayscale = false, + showCustomBadge = false, + ), + onAddClick = {}, + ), + ) + }, + ) + } +} \ No newline at end of file