Updated on 2026-08-14
This commit is contained in:
commit
86d72b171f
657 changed files with 19875 additions and 4004 deletions
1
features/create-wallet-selection/api/.gitignore
vendored
Normal file
1
features/create-wallet-selection/api/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
build/
|
||||
22
features/create-wallet-selection/api/build.gradle.kts
Normal file
22
features/create-wallet-selection/api/build.gradle.kts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
plugins {
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.tangem.features.createwalletselection.api"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
/* Project - Domain */
|
||||
implementation(projects.domain.models)
|
||||
|
||||
/* Project - Core */
|
||||
implementation(projects.core.decompose)
|
||||
implementation(projects.core.ui)
|
||||
|
||||
/* Compose */
|
||||
implementation(deps.compose.runtime)
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.features.createwalletselection
|
||||
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
|
||||
interface CreateWalletSelectionComponent : ComposableContentComponent {
|
||||
interface Factory : ComponentFactory<Unit, CreateWalletSelectionComponent>
|
||||
}
|
||||
1
features/create-wallet-selection/impl/.gitignore
vendored
Normal file
1
features/create-wallet-selection/impl/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
build/
|
||||
70
features/create-wallet-selection/impl/build.gradle.kts
Normal file
70
features/create-wallet-selection/impl/build.gradle.kts
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
plugins {
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
alias(deps.plugins.kotlin.kapt)
|
||||
alias(deps.plugins.kotlin.serialization)
|
||||
alias(deps.plugins.hilt.android)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.tangem.features.createwalletselection.impl"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
/** Api */
|
||||
implementation(projects.features.createWalletSelection.api)
|
||||
|
||||
/** Hot Wallet Feature */
|
||||
implementation(projects.features.hotWallet.api)
|
||||
|
||||
/** Core modules */
|
||||
implementation(projects.core.configToggles)
|
||||
implementation(projects.core.analytics)
|
||||
implementation(projects.core.analytics.models)
|
||||
implementation(projects.core.utils)
|
||||
implementation(projects.core.ui)
|
||||
implementation(projects.core.res)
|
||||
implementation(projects.core.decompose)
|
||||
implementation(projects.core.navigation)
|
||||
implementation(projects.core.datasource)
|
||||
|
||||
/** Common */
|
||||
implementation(projects.common.ui)
|
||||
implementation(projects.common.routing)
|
||||
|
||||
/** Tangem libraries */
|
||||
implementation(projects.libs.tangemSdkApi)
|
||||
implementation(tangemDeps.card.core)
|
||||
implementation(tangemDeps.card.android) {
|
||||
exclude(module = "joda-time")
|
||||
}
|
||||
|
||||
/** AndroidX libraries */
|
||||
implementation(deps.androidx.core.ktx)
|
||||
implementation(deps.lifecycle.runtime.ktx)
|
||||
|
||||
/** Compose libraries */
|
||||
implementation(deps.compose.material)
|
||||
implementation(deps.compose.material3)
|
||||
implementation(deps.compose.animation)
|
||||
implementation(deps.compose.foundation)
|
||||
implementation(deps.compose.ui)
|
||||
implementation(deps.compose.ui.tooling)
|
||||
implementation(deps.compose.coil)
|
||||
implementation(deps.lottie.compose)
|
||||
implementation(deps.decompose.ext.compose)
|
||||
implementation(deps.androidx.activity.compose)
|
||||
implementation(deps.androidx.datastore)
|
||||
|
||||
/** Other libraries */
|
||||
implementation(deps.arrow.core)
|
||||
implementation(deps.kotlin.immutable.collections)
|
||||
implementation(deps.kotlin.serialization)
|
||||
implementation(deps.timber)
|
||||
implementation(deps.firebase.crashlytics)
|
||||
|
||||
/** DI */
|
||||
implementation(deps.hilt.android)
|
||||
kapt(deps.hilt.kapt)
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
package com.tangem.features.createwalletselection
|
||||
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.features.createwalletselection.entity.CreateWalletSelectionUM
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import javax.inject.Inject
|
||||
|
||||
@ModelScoped
|
||||
internal class CreateWalletSelectionModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val router: Router,
|
||||
) : Model() {
|
||||
|
||||
internal val uiState: StateFlow<CreateWalletSelectionUM>
|
||||
field = MutableStateFlow(
|
||||
CreateWalletSelectionUM(
|
||||
onBackClick = { router.pop() },
|
||||
onMobileWalletClick = ::onMobileWalletClick,
|
||||
onHardwareWalletClick = ::onHardwareWalletClick,
|
||||
onScanClick = ::onScanClick,
|
||||
),
|
||||
)
|
||||
|
||||
private fun onMobileWalletClick() {
|
||||
router.push(AppRoute.CreateMobileWallet)
|
||||
}
|
||||
|
||||
private fun onHardwareWalletClick() {
|
||||
// TODO open card order web page
|
||||
}
|
||||
|
||||
private fun onScanClick() {
|
||||
// TODO open card scanning
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package com.tangem.features.createwalletselection
|
||||
|
||||
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.createwalletselection.ui.CreateWalletSelectionContent
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
internal class DefaultCreateWalletSelectionComponent @AssistedInject constructor(
|
||||
@Assisted private val context: AppComponentContext,
|
||||
@Assisted private val params: Unit,
|
||||
) : CreateWalletSelectionComponent, AppComponentContext by context {
|
||||
|
||||
private val model: CreateWalletSelectionModel = getOrCreateModel(params)
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val state by model.uiState.collectAsStateWithLifecycle()
|
||||
CreateWalletSelectionContent(
|
||||
state = state,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : CreateWalletSelectionComponent.Factory {
|
||||
override fun create(context: AppComponentContext, params: Unit): DefaultCreateWalletSelectionComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package com.tangem.features.createwalletselection.di
|
||||
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.features.createwalletselection.CreateWalletSelectionComponent
|
||||
import com.tangem.features.createwalletselection.CreateWalletSelectionModel
|
||||
import com.tangem.features.createwalletselection.DefaultCreateWalletSelectionComponent
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import dagger.multibindings.ClassKey
|
||||
import dagger.multibindings.IntoMap
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object CreateWalletSelectionModule
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal interface CreateWalletSelectionModuleBinds {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindCreateWalletSelectionComponentFactory(
|
||||
impl: DefaultCreateWalletSelectionComponent.Factory,
|
||||
): CreateWalletSelectionComponent.Factory
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(CreateWalletSelectionModel::class)
|
||||
fun bindCreateWalletSelectionModel(model: CreateWalletSelectionModel): Model
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.features.createwalletselection.entity
|
||||
|
||||
internal data class CreateWalletSelectionUM(
|
||||
val isScanInProgress: Boolean = false,
|
||||
val hardwareWalletPrice: String = "$54.90",
|
||||
val onBackClick: () -> Unit,
|
||||
val onMobileWalletClick: () -> Unit,
|
||||
val onHardwareWalletClick: () -> Unit,
|
||||
val onScanClick: () -> Unit,
|
||||
)
|
||||
|
|
@ -0,0 +1,234 @@
|
|||
package com.tangem.features.createwalletselection.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.*
|
||||
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.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
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.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.createwalletselection.entity.CreateWalletSelectionUM
|
||||
import com.tangem.features.createwalletselection.impl.R
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
internal fun CreateWalletSelectionContent(state: CreateWalletSelectionUM, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.background(TangemTheme.colors.background.primary)
|
||||
.fillMaxSize()
|
||||
.systemBarsPadding(),
|
||||
) {
|
||||
TopAppBar(
|
||||
modifier = Modifier
|
||||
.statusBarsPadding(),
|
||||
colors = TopAppBarDefaults.topAppBarColors(
|
||||
containerColor = TangemTheme.colors.background.primary,
|
||||
),
|
||||
navigationIcon = {
|
||||
IconButton(onClick = state.onBackClick) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_back_24),
|
||||
tint = TangemTheme.colors.icon.primary1,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
},
|
||||
title = { },
|
||||
actions = {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.padding(16.dp),
|
||||
text = stringResourceSafe(R.string.wallet_create_nav_info_title),
|
||||
style = TangemTheme.typography.body1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
},
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(
|
||||
start = 16.dp,
|
||||
top = 24.dp,
|
||||
end = 16.dp,
|
||||
),
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
text = stringResourceSafe(R.string.wallet_create_title),
|
||||
style = TangemTheme.typography.h2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
WalletBlock(
|
||||
modifier = Modifier
|
||||
.padding(top = 24.dp),
|
||||
title = stringResourceSafe(R.string.wallet_create_mobile_title),
|
||||
description = stringResourceSafe(R.string.wallet_create_mobile_description),
|
||||
badge = {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.background(
|
||||
color = TangemTheme.colors.field.focused,
|
||||
shape = TangemTheme.shapes.roundedCorners8,
|
||||
)
|
||||
.padding(horizontal = 8.dp, vertical = 4.dp),
|
||||
) {
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.common_free),
|
||||
style = TangemTheme.typography.caption1,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
)
|
||||
}
|
||||
},
|
||||
onClick = state.onMobileWalletClick,
|
||||
)
|
||||
WalletBlock(
|
||||
title = stringResourceSafe(R.string.wallet_create_hardware_title),
|
||||
description = stringResourceSafe(R.string.wallet_create_hardware_description),
|
||||
badge = {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.background(
|
||||
color = TangemTheme.colors.text.accent.copy(alpha = 0.1f),
|
||||
shape = TangemTheme.shapes.roundedCorners8,
|
||||
)
|
||||
.padding(horizontal = 8.dp, vertical = 4.dp),
|
||||
) {
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.wallet_create_hardware_badge, state.hardwareWalletPrice),
|
||||
style = TangemTheme.typography.caption1,
|
||||
color = TangemTheme.colors.text.accent,
|
||||
)
|
||||
}
|
||||
},
|
||||
onClick = state.onHardwareWalletClick,
|
||||
)
|
||||
}
|
||||
AlreadyHaveTangemWalletBlock(
|
||||
onScanClick = state.onScanClick,
|
||||
isScanInProgress = state.isScanInProgress,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun WalletBlock(
|
||||
modifier: Modifier = Modifier,
|
||||
title: String,
|
||||
description: String,
|
||||
badge: @Composable () -> Unit,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 8.dp)
|
||||
.clip(TangemTheme.shapes.roundedCornersXMedium)
|
||||
.background(
|
||||
color = TangemTheme.colors.background.secondary,
|
||||
shape = TangemTheme.shapes.roundedCornersXMedium,
|
||||
)
|
||||
.clickable(onClick = onClick)
|
||||
.padding(16.dp),
|
||||
) {
|
||||
Row {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(end = 8.dp),
|
||||
text = title,
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
badge()
|
||||
}
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.padding(top = 4.dp),
|
||||
text = description,
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AlreadyHaveTangemWalletBlock(
|
||||
onScanClick: () -> Unit,
|
||||
isScanInProgress: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
.background(
|
||||
color = TangemTheme.colors.field.primary,
|
||||
shape = TangemTheme.shapes.roundedCornersXMedium,
|
||||
)
|
||||
.padding(
|
||||
horizontal = 20.dp,
|
||||
vertical = 16.dp,
|
||||
),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(end = 16.dp),
|
||||
text = stringResourceSafe(R.string.wallet_create_scan_question),
|
||||
style = TangemTheme.typography.button,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
TangemButton(
|
||||
modifier = Modifier
|
||||
.wrapContentWidth(),
|
||||
text = stringResourceSafe(R.string.wallet_create_scan_title),
|
||||
onClick = onScanClick,
|
||||
icon = TangemButtonIconPosition.End(iconResId = R.drawable.ic_tangem_24),
|
||||
size = TangemButtonSize.RoundedAction,
|
||||
showProgress = isScanInProgress,
|
||||
colors = TangemButtonsDefaults.secondaryButtonColors,
|
||||
textStyle = TangemTheme.typography.subtitle1,
|
||||
enabled = true,
|
||||
animateContentChange = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun PreviewCreateWalletContent() {
|
||||
TangemThemePreview {
|
||||
CreateWalletSelectionContent(
|
||||
state = CreateWalletSelectionUM(
|
||||
onBackClick = {},
|
||||
onMobileWalletClick = {},
|
||||
onHardwareWalletClick = {},
|
||||
onScanClick = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -33,8 +33,11 @@ internal class UserWalletListModel @Inject constructor(
|
|||
) : Model() {
|
||||
|
||||
private val isWalletSavingInProgress: MutableStateFlow<Boolean> = MutableStateFlow(value = false)
|
||||
private val userWalletsFetcher = userWalletsFetcherFactory
|
||||
.create(messageSender) { userWalletId -> router.push(AppRoute.WalletSettings(userWalletId)) }
|
||||
private val userWalletsFetcher = userWalletsFetcherFactory.create(
|
||||
messageSender = messageSender,
|
||||
onlyMultiCurrency = false,
|
||||
onWalletClick = { userWalletId -> router.push(AppRoute.WalletSettings(userWalletId)) },
|
||||
)
|
||||
|
||||
val state: MutableStateFlow<UserWalletListUM> = MutableStateFlow(
|
||||
value = UserWalletListUM(
|
||||
|
|
|
|||
|
|
@ -1,27 +0,0 @@
|
|||
plugins {
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.tangem.features.feeselector.api"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
/** Project - Core */
|
||||
implementation(projects.core.ui)
|
||||
implementation(projects.core.decompose)
|
||||
|
||||
/** Domain models */
|
||||
// api(projects.domain.models)
|
||||
implementation(projects.domain.appCurrency.models)
|
||||
// implementation(projects.domain.wallets.models)
|
||||
|
||||
/** Compose */
|
||||
implementation(deps.compose.runtime)
|
||||
implementation(deps.compose.foundation)
|
||||
|
||||
/** Other */
|
||||
implementation(deps.kotlin.immutable.collections)
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
package com.tangem.features.feeselector.api.component
|
||||
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
|
||||
interface FeeSelectorComponent : ComposableContentComponent, ComposableBottomSheetComponent {
|
||||
data class Params(val appCurrency: AppCurrency)
|
||||
|
||||
interface Factory : ComponentFactory<Params, FeeSelectorComponent>
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
package com.tangem.features.feeselector.impl.di
|
||||
|
||||
import com.tangem.features.feeselector.api.component.FeeSelectorComponent
|
||||
import com.tangem.features.feeselector.impl.component.DefaultFeeSelectorComponent
|
||||
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 FeeSelectorFeatureModule {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindComponentFactory(factory: DefaultFeeSelectorComponent.Factory): FeeSelectorComponent.Factory
|
||||
}
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
package com.tangem.features.feeselector.impl.entity
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.features.feeselector.api.entity.CustomFeeFieldUM
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import java.math.BigDecimal
|
||||
|
||||
@Immutable
|
||||
internal sealed class FeeSelectorUM {
|
||||
|
||||
data object Loading : FeeSelectorUM()
|
||||
|
||||
data class Content(
|
||||
val isDoneEnabled: Boolean,
|
||||
val feeItems: ImmutableList<FeeItem>,
|
||||
val selectedFeeItem: FeeItem,
|
||||
val isFeeApproximate: Boolean,
|
||||
val feeFiatRateDataHolder: FeeFiatRateDataHolder?,
|
||||
) : FeeSelectorUM()
|
||||
}
|
||||
|
||||
@Immutable
|
||||
data class FeeFiatRateDataHolder(
|
||||
val rate: BigDecimal,
|
||||
val appCurrency: AppCurrency,
|
||||
)
|
||||
|
||||
@Immutable
|
||||
internal sealed class FeeItem {
|
||||
abstract val onSelect: () -> Unit
|
||||
|
||||
data class Suggested(val title: TextReference, val amount: Amount, override val onSelect: () -> Unit) : FeeItem()
|
||||
data class Slow(val amount: Amount, override val onSelect: () -> Unit) : FeeItem()
|
||||
data class Market(val amount: Amount, override val onSelect: () -> Unit) : FeeItem()
|
||||
data class Fast(val amount: Amount, override val onSelect: () -> Unit) : FeeItem()
|
||||
data class Custom(val customValues: ImmutableList<CustomFeeFieldUM>, override val onSelect: () -> Unit) : FeeItem()
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
package com.tangem.features.feeselector.impl.model
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
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.decompose.navigation.Router
|
||||
import com.tangem.features.feeselector.api.component.FeeSelectorComponent
|
||||
import com.tangem.features.feeselector.impl.entity.FeeSelectorUM
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import javax.inject.Inject
|
||||
|
||||
@Stable
|
||||
@ModelScoped
|
||||
internal class FeeSelectorModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
private val router: Router,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
) : Model() {
|
||||
|
||||
@Suppress("UnusedPrivateMember")
|
||||
private val params = paramsContainer.require<FeeSelectorComponent.Params>()
|
||||
val uiState: StateFlow<FeeSelectorUM>
|
||||
field = MutableStateFlow<FeeSelectorUM>(FeeSelectorUM.Loading)
|
||||
|
||||
fun dismiss() {
|
||||
router.pop()
|
||||
}
|
||||
}
|
||||
22
features/hot-wallet/api/build.gradle.kts
Normal file
22
features/hot-wallet/api/build.gradle.kts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
plugins {
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.tangem.features.hotwallet.api"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
/* Project - Domain */
|
||||
implementation(projects.domain.models)
|
||||
|
||||
/* Project - Core */
|
||||
implementation(projects.core.decompose)
|
||||
implementation(projects.core.ui)
|
||||
|
||||
/* Compose */
|
||||
implementation(deps.compose.runtime)
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.features.hotwallet
|
||||
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
|
||||
interface AddExistingWalletComponent : ComposableContentComponent {
|
||||
interface Factory : ComponentFactory<Unit, AddExistingWalletComponent>
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.features.hotwallet
|
||||
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
|
||||
interface CreateMobileWalletComponent : ComposableContentComponent {
|
||||
interface Factory : ComponentFactory<Unit, CreateMobileWalletComponent>
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package com.tangem.features.hotwallet
|
||||
|
||||
interface HotWalletFeatureToggles {
|
||||
val isHotWalletEnabled: Boolean
|
||||
}
|
||||
67
features/hot-wallet/impl/build.gradle.kts
Normal file
67
features/hot-wallet/impl/build.gradle.kts
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
plugins {
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
alias(deps.plugins.kotlin.kapt)
|
||||
alias(deps.plugins.kotlin.serialization)
|
||||
alias(deps.plugins.hilt.android)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.tangem.features.hotwallet.impl"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
/** Api */
|
||||
implementation(projects.features.hotWallet.api)
|
||||
|
||||
/** Core modules */
|
||||
implementation(projects.core.configToggles)
|
||||
implementation(projects.core.analytics)
|
||||
implementation(projects.core.analytics.models)
|
||||
implementation(projects.core.utils)
|
||||
implementation(projects.core.ui)
|
||||
implementation(projects.core.res)
|
||||
implementation(projects.core.decompose)
|
||||
implementation(projects.core.navigation)
|
||||
implementation(projects.core.datasource)
|
||||
|
||||
/** Common */
|
||||
implementation(projects.common.ui)
|
||||
implementation(projects.common.routing)
|
||||
|
||||
/** Tangem libraries */
|
||||
implementation(projects.libs.tangemSdkApi)
|
||||
implementation(tangemDeps.card.core)
|
||||
implementation(tangemDeps.card.android) {
|
||||
exclude(module = "joda-time")
|
||||
}
|
||||
|
||||
/** AndroidX libraries */
|
||||
implementation(deps.androidx.core.ktx)
|
||||
implementation(deps.lifecycle.runtime.ktx)
|
||||
|
||||
/** Compose libraries */
|
||||
implementation(deps.compose.material) // to use buttons and text field in MultiWalletSeedPhraseImport.kt
|
||||
implementation(deps.compose.material3)
|
||||
implementation(deps.compose.animation)
|
||||
implementation(deps.compose.foundation)
|
||||
implementation(deps.compose.ui)
|
||||
implementation(deps.compose.ui.tooling)
|
||||
implementation(deps.compose.coil)
|
||||
implementation(deps.lottie.compose)
|
||||
implementation(deps.decompose.ext.compose)
|
||||
implementation(deps.androidx.activity.compose)
|
||||
implementation(deps.androidx.datastore)
|
||||
|
||||
/** Other libraries */
|
||||
implementation(deps.arrow.core)
|
||||
implementation(deps.kotlin.immutable.collections)
|
||||
implementation(deps.kotlin.serialization)
|
||||
implementation(deps.timber)
|
||||
implementation(deps.firebase.crashlytics)
|
||||
|
||||
/** DI */
|
||||
implementation(deps.hilt.android)
|
||||
kapt(deps.hilt.kapt)
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.features.hotwallet
|
||||
|
||||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
|
||||
internal class DefaultHotWalletFeatureToggles(
|
||||
private val featureTogglesManager: FeatureTogglesManager,
|
||||
) : HotWalletFeatureToggles {
|
||||
override val isHotWalletEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(name = "HOT_WALLET_ENABLED")
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
package com.tangem.features.hotwallet.addexistingwallet.im.port
|
||||
|
||||
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.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.features.hotwallet.addexistingwallet.im.port.ui.AddExistingWalletImportContent
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
internal class AddExistingWalletImportComponent @AssistedInject constructor(
|
||||
@Assisted private val context: AppComponentContext,
|
||||
@Assisted private val params: Params,
|
||||
) : ComposableContentComponent, AppComponentContext by context {
|
||||
private val model: AddExistingWalletImportModel = getOrCreateModel(params)
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val state by model.uiState.collectAsStateWithLifecycle()
|
||||
AddExistingWalletImportContent(
|
||||
state = state,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
interface ModelCallbacks {
|
||||
fun onBackClick()
|
||||
}
|
||||
|
||||
data class Params(
|
||||
val callbacks: ModelCallbacks,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.tangem.features.hotwallet.addexistingwallet.im.port
|
||||
|
||||
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.hotwallet.addexistingwallet.im.port.entity.AddExistingWalletImportUM
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import javax.inject.Inject
|
||||
|
||||
@ModelScoped
|
||||
internal class AddExistingWalletImportModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
) : Model() {
|
||||
|
||||
private val params: AddExistingWalletImportComponent.Params = paramsContainer.require()
|
||||
|
||||
internal val uiState: StateFlow<AddExistingWalletImportUM>
|
||||
field = MutableStateFlow(
|
||||
AddExistingWalletImportUM(
|
||||
onBackClick = params.callbacks::onBackClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package com.tangem.features.hotwallet.addexistingwallet.im.port.entity
|
||||
|
||||
internal data class AddExistingWalletImportUM(
|
||||
val onBackClick: () -> Unit,
|
||||
)
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package com.tangem.features.hotwallet.addexistingwallet.im.port.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.components.appbar.TangemTopAppBar
|
||||
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.hotwallet.addexistingwallet.im.port.entity.AddExistingWalletImportUM
|
||||
import com.tangem.core.ui.R
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
internal fun AddExistingWalletImportContent(state: AddExistingWalletImportUM, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.background(TangemTheme.colors.background.primary)
|
||||
.fillMaxSize()
|
||||
.systemBarsPadding(),
|
||||
) {
|
||||
TangemTopAppBar(
|
||||
modifier = Modifier
|
||||
.statusBarsPadding(),
|
||||
startButton = TopAppBarButtonUM.Back(state.onBackClick),
|
||||
title = stringResourceSafe(R.string.wallet_import_title),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun PreviewAddExistingWalletImportContent() {
|
||||
TangemThemePreview {
|
||||
AddExistingWalletImportContent(
|
||||
state = AddExistingWalletImportUM(
|
||||
onBackClick = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package com.tangem.features.hotwallet.addexistingwallet.root
|
||||
|
||||
import com.arkivanov.decompose.router.stack.StackNavigation
|
||||
import com.arkivanov.decompose.router.stack.pop
|
||||
import com.arkivanov.decompose.router.stack.push
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.features.hotwallet.addexistingwallet.start.AddExistingWalletStartComponent
|
||||
import com.tangem.features.hotwallet.addexistingwallet.im.port.AddExistingWalletImportComponent
|
||||
import com.tangem.features.hotwallet.addexistingwallet.root.routing.AddExistingWalletRoute
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import javax.inject.Inject
|
||||
|
||||
@ModelScoped
|
||||
internal class AddExistingWalletModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val router: Router,
|
||||
) : Model() {
|
||||
|
||||
val addExistingWalletStartModelCallbacks = AddExistingWalletStartModelCallbacks()
|
||||
val addExistingWalletImportModelCallbacks = AddExistingWalletImportModelCallbacks()
|
||||
|
||||
val stackNavigation = StackNavigation<AddExistingWalletRoute>()
|
||||
|
||||
inner class AddExistingWalletStartModelCallbacks : AddExistingWalletStartComponent.ModelCallbacks {
|
||||
override fun onBackClick() {
|
||||
router.pop()
|
||||
}
|
||||
|
||||
override fun onImportPhraseClick() {
|
||||
stackNavigation.push(AddExistingWalletRoute.Import)
|
||||
}
|
||||
}
|
||||
|
||||
inner class AddExistingWalletImportModelCallbacks : AddExistingWalletImportComponent.ModelCallbacks {
|
||||
override fun onBackClick() {
|
||||
stackNavigation.pop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
package com.tangem.features.hotwallet.addexistingwallet.root
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.arkivanov.decompose.extensions.compose.subscribeAsState
|
||||
import com.arkivanov.decompose.router.stack.childStack
|
||||
import com.arkivanov.decompose.router.stack.pop
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.context.childByContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.features.hotwallet.AddExistingWalletComponent
|
||||
import com.tangem.features.hotwallet.addexistingwallet.root.routing.AddExistingWalletChildFactory
|
||||
import com.tangem.features.hotwallet.addexistingwallet.root.routing.AddExistingWalletRoute
|
||||
import com.tangem.features.hotwallet.addexistingwallet.root.ui.AddExistingWalletContent
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
internal class DefaultAddExistingWalletComponent @AssistedInject constructor(
|
||||
@Assisted appComponentContext: AppComponentContext,
|
||||
@Assisted private val params: Unit,
|
||||
addExistingWalletChildFactory: AddExistingWalletChildFactory,
|
||||
) : AddExistingWalletComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
private val model: AddExistingWalletModel = getOrCreateModel(params)
|
||||
|
||||
private val startRoute = AddExistingWalletRoute.Start
|
||||
|
||||
private val innerStack = childStack(
|
||||
key = "addExistingWalletInnerStack",
|
||||
source = model.stackNavigation,
|
||||
serializer = null,
|
||||
initialConfiguration = startRoute,
|
||||
handleBackButton = true,
|
||||
childFactory = { configuration, factoryContext ->
|
||||
addExistingWalletChildFactory.createChild(
|
||||
route = configuration,
|
||||
childContext = childByContext(factoryContext),
|
||||
model = model,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val stackState by innerStack.subscribeAsState()
|
||||
|
||||
BackHandler(onBack = ::onChildBack)
|
||||
AddExistingWalletContent(
|
||||
stackState = stackState,
|
||||
)
|
||||
}
|
||||
|
||||
private fun onChildBack() {
|
||||
val isEmptyStack = innerStack.value.backStack.isEmpty()
|
||||
|
||||
if (isEmptyStack) {
|
||||
router.pop()
|
||||
} else {
|
||||
model.stackNavigation.pop()
|
||||
}
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : AddExistingWalletComponent.Factory {
|
||||
override fun create(context: AppComponentContext, params: Unit): DefaultAddExistingWalletComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
package com.tangem.features.hotwallet.addexistingwallet.root.di
|
||||
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.features.hotwallet.AddExistingWalletComponent
|
||||
import com.tangem.features.hotwallet.addexistingwallet.root.AddExistingWalletModel
|
||||
import com.tangem.features.hotwallet.addexistingwallet.root.DefaultAddExistingWalletComponent
|
||||
import com.tangem.features.hotwallet.addexistingwallet.start.AddExistingWalletStartModel
|
||||
import com.tangem.features.hotwallet.addexistingwallet.im.port.AddExistingWalletImportModel
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import dagger.multibindings.ClassKey
|
||||
import dagger.multibindings.IntoMap
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object AddExistingWalletModule
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal interface AddExistingWalletModuleBinds {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindAddExistingWalletComponentFactory(
|
||||
impl: DefaultAddExistingWalletComponent.Factory,
|
||||
): AddExistingWalletComponent.Factory
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(AddExistingWalletModel::class)
|
||||
fun bindAddExistingWalletModel(model: AddExistingWalletModel): Model
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(AddExistingWalletStartModel::class)
|
||||
fun bindAddExistingWalletStartModel(model: AddExistingWalletStartModel): Model
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(AddExistingWalletImportModel::class)
|
||||
fun bindAddExistingWalletImportModel(model: AddExistingWalletImportModel): Model
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package com.tangem.features.hotwallet.addexistingwallet.root.entity
|
||||
|
||||
internal data class AddExistingWalletUM(
|
||||
val onBackClick: () -> Unit,
|
||||
)
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package com.tangem.features.hotwallet.addexistingwallet.root.routing
|
||||
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.features.hotwallet.addexistingwallet.root.AddExistingWalletModel
|
||||
import com.tangem.features.hotwallet.addexistingwallet.start.AddExistingWalletStartComponent
|
||||
import com.tangem.features.hotwallet.addexistingwallet.im.port.AddExistingWalletImportComponent
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class AddExistingWalletChildFactory @Inject constructor() {
|
||||
|
||||
fun createChild(
|
||||
route: AddExistingWalletRoute,
|
||||
childContext: AppComponentContext,
|
||||
model: AddExistingWalletModel,
|
||||
): ComposableContentComponent {
|
||||
return when (route) {
|
||||
is AddExistingWalletRoute.Start -> AddExistingWalletStartComponent(
|
||||
context = childContext,
|
||||
params = AddExistingWalletStartComponent.Params(
|
||||
callbacks = model.addExistingWalletStartModelCallbacks,
|
||||
),
|
||||
)
|
||||
is AddExistingWalletRoute.Import -> AddExistingWalletImportComponent(
|
||||
context = childContext,
|
||||
params = AddExistingWalletImportComponent.Params(
|
||||
callbacks = model.addExistingWalletImportModelCallbacks,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.features.hotwallet.addexistingwallet.root.routing
|
||||
|
||||
import com.tangem.core.decompose.navigation.Route
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
internal sealed class AddExistingWalletRoute : Route {
|
||||
|
||||
@Serializable
|
||||
object Start : AddExistingWalletRoute()
|
||||
|
||||
@Serializable
|
||||
object Import : AddExistingWalletRoute()
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package com.tangem.features.hotwallet.addexistingwallet.root.ui
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.systemBarsPadding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.arkivanov.decompose.extensions.compose.stack.Children
|
||||
import com.arkivanov.decompose.extensions.compose.stack.animation.slide
|
||||
import com.arkivanov.decompose.extensions.compose.stack.animation.stackAnimation
|
||||
import com.arkivanov.decompose.router.stack.ChildStack
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.hotwallet.addexistingwallet.root.routing.AddExistingWalletRoute
|
||||
|
||||
@Composable
|
||||
internal fun AddExistingWalletContent(stackState: ChildStack<AddExistingWalletRoute, ComposableContentComponent>) {
|
||||
Children(
|
||||
stack = stackState,
|
||||
animation = stackAnimation(slide()),
|
||||
modifier = Modifier
|
||||
.background(color = TangemTheme.colors.background.primary)
|
||||
.fillMaxSize()
|
||||
.imePadding()
|
||||
.systemBarsPadding(),
|
||||
) {
|
||||
it.instance.Content(Modifier.fillMaxSize())
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package com.tangem.features.hotwallet.addexistingwallet.start
|
||||
|
||||
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.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.features.hotwallet.addexistingwallet.start.ui.AddExistingWalletStartContent
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
internal class AddExistingWalletStartComponent @AssistedInject constructor(
|
||||
@Assisted private val context: AppComponentContext,
|
||||
@Assisted private val params: Params,
|
||||
) : ComposableContentComponent, AppComponentContext by context {
|
||||
private val model: AddExistingWalletStartModel = getOrCreateModel(params)
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val state by model.uiState.collectAsStateWithLifecycle()
|
||||
AddExistingWalletStartContent(
|
||||
state = state,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
interface ModelCallbacks {
|
||||
fun onBackClick()
|
||||
fun onImportPhraseClick()
|
||||
}
|
||||
|
||||
data class Params(
|
||||
val callbacks: ModelCallbacks,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package com.tangem.features.hotwallet.addexistingwallet.start
|
||||
|
||||
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.hotwallet.addexistingwallet.start.entity.AddExistingWalletStartUM
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import javax.inject.Inject
|
||||
|
||||
@ModelScoped
|
||||
internal class AddExistingWalletStartModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
) : Model() {
|
||||
|
||||
private val params: AddExistingWalletStartComponent.Params = paramsContainer.require()
|
||||
|
||||
internal val uiState: StateFlow<AddExistingWalletStartUM>
|
||||
field = MutableStateFlow(
|
||||
AddExistingWalletStartUM(
|
||||
onBackClick = params.callbacks::onBackClick,
|
||||
onImportPhraseClick = params.callbacks::onImportPhraseClick,
|
||||
onScanCardClick = { /* [REDACTED_TODO_COMMENT] */ },
|
||||
onBuyCardClick = { /* [REDACTED_TODO_COMMENT] */ },
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.features.hotwallet.addexistingwallet.start.entity
|
||||
|
||||
internal data class AddExistingWalletStartUM(
|
||||
val onBackClick: () -> Unit,
|
||||
val onImportPhraseClick: () -> Unit,
|
||||
val onScanCardClick: () -> Unit,
|
||||
val onBuyCardClick: () -> Unit,
|
||||
)
|
||||
|
|
@ -0,0 +1,219 @@
|
|||
package com.tangem.features.hotwallet.addexistingwallet.start.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.*
|
||||
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.res.painterResource
|
||||
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.components.PrimaryButton
|
||||
import com.tangem.core.ui.components.appbar.TangemTopAppBar
|
||||
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonSize
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults
|
||||
import com.tangem.core.ui.extensions.clickableSingle
|
||||
import com.tangem.core.ui.extensions.conditional
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.hotwallet.addexistingwallet.start.entity.AddExistingWalletStartUM
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
internal fun AddExistingWalletStartContent(state: AddExistingWalletStartUM, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.background(TangemTheme.colors.background.primary)
|
||||
.fillMaxSize()
|
||||
.systemBarsPadding(),
|
||||
) {
|
||||
TangemTopAppBar(
|
||||
modifier = Modifier
|
||||
.statusBarsPadding(),
|
||||
startButton = TopAppBarButtonUM.Back(state.onBackClick),
|
||||
title = "Add existing wallet",
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(
|
||||
start = 16.dp,
|
||||
top = 24.dp,
|
||||
end = 16.dp,
|
||||
),
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
text = stringResourceSafe(R.string.wallet_import_seed_navtitle),
|
||||
style = TangemTheme.typography.h2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
OptionBlock(
|
||||
modifier = Modifier
|
||||
.padding(top = 24.dp),
|
||||
title = stringResourceSafe(R.string.wallet_import_seed_title),
|
||||
description = stringResourceSafe(R.string.wallet_import_seed_description),
|
||||
badge = null,
|
||||
onClick = state.onImportPhraseClick,
|
||||
enabled = true,
|
||||
)
|
||||
OptionBlock(
|
||||
title = stringResourceSafe(R.string.wallet_import_scan_title),
|
||||
description = stringResourceSafe(R.string.wallet_import_scan_description),
|
||||
badge = {
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.padding(top = 2.dp)
|
||||
.size(20.dp),
|
||||
painter = painterResource(R.drawable.ic_tangem_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.secondary,
|
||||
)
|
||||
},
|
||||
onClick = state.onScanCardClick,
|
||||
enabled = true,
|
||||
)
|
||||
OptionBlock(
|
||||
title = stringResourceSafe(R.string.wallet_import_google_drive_title),
|
||||
description = stringResourceSafe(R.string.wallet_import_google_drive_description),
|
||||
badge = {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 4.dp)
|
||||
.background(
|
||||
color = TangemTheme.colors.field.focused,
|
||||
shape = TangemTheme.shapes.roundedCorners8,
|
||||
)
|
||||
.padding(horizontal = 8.dp, vertical = 4.dp),
|
||||
) {
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.common_coming_soon),
|
||||
style = TangemTheme.typography.caption1,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
},
|
||||
onClick = null,
|
||||
enabled = false,
|
||||
)
|
||||
}
|
||||
BuyTangemWalletBlock(
|
||||
onScanClick = state.onBuyCardClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun OptionBlock(
|
||||
title: String,
|
||||
description: String,
|
||||
badge: (@Composable () -> Unit)?,
|
||||
onClick: (() -> Unit)?,
|
||||
enabled: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 8.dp)
|
||||
.clip(TangemTheme.shapes.roundedCornersXMedium)
|
||||
.background(
|
||||
color = TangemTheme.colors.background.secondary,
|
||||
shape = TangemTheme.shapes.roundedCornersXMedium,
|
||||
)
|
||||
.conditional(onClick != null) {
|
||||
onClick?.let { clickableSingle(onClick = it) } ?: Modifier
|
||||
}
|
||||
.padding(16.dp),
|
||||
) {
|
||||
Row {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.weight(1f, fill = false)
|
||||
.padding(end = 4.dp),
|
||||
text = title,
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
color = if (enabled) {
|
||||
TangemTheme.colors.text.primary1
|
||||
} else {
|
||||
TangemTheme.colors.text.secondary
|
||||
},
|
||||
)
|
||||
badge?.invoke()
|
||||
}
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.padding(top = 4.dp),
|
||||
text = description,
|
||||
style = TangemTheme.typography.body2,
|
||||
color = if (enabled) {
|
||||
TangemTheme.colors.text.tertiary
|
||||
} else {
|
||||
TangemTheme.colors.text.disabled
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BuyTangemWalletBlock(onScanClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
.background(
|
||||
color = TangemTheme.colors.field.primary,
|
||||
shape = TangemTheme.shapes.roundedCornersXMedium,
|
||||
)
|
||||
.padding(
|
||||
horizontal = 20.dp,
|
||||
vertical = 16.dp,
|
||||
),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(end = 16.dp),
|
||||
text = stringResourceSafe(R.string.wallet_import_buy_question),
|
||||
style = TangemTheme.typography.button,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
PrimaryButton(
|
||||
modifier = Modifier
|
||||
.wrapContentWidth(),
|
||||
text = stringResourceSafe(R.string.wallet_import_buy_title),
|
||||
onClick = onScanClick,
|
||||
size = TangemButtonSize.RoundedAction,
|
||||
colors = TangemButtonsDefaults.secondaryButtonColors,
|
||||
enabled = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun PreviewCreateWalletContent() {
|
||||
TangemThemePreview {
|
||||
AddExistingWalletStartContent(
|
||||
state = AddExistingWalletStartUM(
|
||||
onBackClick = {},
|
||||
onImportPhraseClick = {},
|
||||
onScanCardClick = {},
|
||||
onBuyCardClick = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package com.tangem.features.hotwallet.createmobilewallet
|
||||
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.features.hotwallet.createmobilewallet.entity.CreateMobileWalletUM
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import javax.inject.Inject
|
||||
|
||||
@ModelScoped
|
||||
internal class CreateMobileWalletModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val router: Router,
|
||||
) : Model() {
|
||||
|
||||
internal val uiState: StateFlow<CreateMobileWalletUM>
|
||||
field = MutableStateFlow(
|
||||
CreateMobileWalletUM(
|
||||
onBackClick = { router.pop() },
|
||||
onCreateClick = ::onCreateClick,
|
||||
),
|
||||
)
|
||||
|
||||
private fun onCreateClick() {
|
||||
// TODO create a wallet
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package com.tangem.features.hotwallet.createmobilewallet
|
||||
|
||||
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.hotwallet.CreateMobileWalletComponent
|
||||
import com.tangem.features.hotwallet.createmobilewallet.ui.CreateMobileWalletContent
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
internal class DefaultCreateMobileWalletComponent @AssistedInject constructor(
|
||||
@Assisted private val context: AppComponentContext,
|
||||
@Assisted private val params: Unit,
|
||||
) : CreateMobileWalletComponent, AppComponentContext by context {
|
||||
private val model: CreateMobileWalletModel = getOrCreateModel(params)
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val state by model.uiState.collectAsStateWithLifecycle()
|
||||
CreateMobileWalletContent(
|
||||
state = state,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : CreateMobileWalletComponent.Factory {
|
||||
override fun create(context: AppComponentContext, params: Unit): DefaultCreateMobileWalletComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package com.tangem.features.hotwallet.createmobilewallet.di
|
||||
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.features.hotwallet.CreateMobileWalletComponent
|
||||
import com.tangem.features.hotwallet.createmobilewallet.CreateMobileWalletModel
|
||||
import com.tangem.features.hotwallet.createmobilewallet.DefaultCreateMobileWalletComponent
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import dagger.multibindings.ClassKey
|
||||
import dagger.multibindings.IntoMap
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object CreateMobileWalletModule
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal interface CreateMobileWalletModuleBinds {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindCreateMobileWalletComponentFactory(
|
||||
impl: DefaultCreateMobileWalletComponent.Factory,
|
||||
): CreateMobileWalletComponent.Factory
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(CreateMobileWalletModel::class)
|
||||
fun bindCreateMobileWalletModel(model: CreateMobileWalletModel): Model
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package com.tangem.features.hotwallet.createmobilewallet.entity
|
||||
|
||||
internal data class CreateMobileWalletUM(
|
||||
val onBackClick: () -> Unit,
|
||||
val onCreateClick: () -> Unit,
|
||||
)
|
||||
|
|
@ -0,0 +1,140 @@
|
|||
package com.tangem.features.hotwallet.createmobilewallet.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
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.PrimaryButton
|
||||
import com.tangem.core.ui.components.appbar.TangemTopAppBar
|
||||
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.hotwallet.createmobilewallet.entity.CreateMobileWalletUM
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
internal fun CreateMobileWalletContent(state: CreateMobileWalletUM, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.background(TangemTheme.colors.background.primary)
|
||||
.fillMaxSize()
|
||||
.systemBarsPadding(),
|
||||
) {
|
||||
TangemTopAppBar(
|
||||
modifier = Modifier
|
||||
.statusBarsPadding(),
|
||||
startButton = TopAppBarButtonUM.Back(state.onBackClick),
|
||||
title = TextReference.EMPTY,
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(
|
||||
start = 16.dp,
|
||||
top = 24.dp,
|
||||
end = 16.dp,
|
||||
),
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
painter = painterResource(R.drawable.ic_create_mobile_wallet_56),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
start = 16.dp,
|
||||
top = 20.dp,
|
||||
end = 16.dp,
|
||||
),
|
||||
text = stringResourceSafe(R.string.hw_create_title),
|
||||
style = TangemTheme.typography.h2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
FeatureBlock(
|
||||
modifier = Modifier
|
||||
.padding(top = 32.dp),
|
||||
title = stringResourceSafe(R.string.hw_create_keys_title),
|
||||
description = stringResourceSafe(R.string.hw_create_keys_description),
|
||||
iconRes = R.drawable.ic_lock_24,
|
||||
)
|
||||
FeatureBlock(
|
||||
modifier = Modifier
|
||||
.padding(top = 24.dp),
|
||||
title = stringResourceSafe(R.string.hw_create_seed_title),
|
||||
description = stringResourceSafe(R.string.hw_create_seed_description),
|
||||
iconRes = R.drawable.ic_settings_24,
|
||||
)
|
||||
}
|
||||
PrimaryButton(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
text = "Create",
|
||||
showProgress = false,
|
||||
enabled = true,
|
||||
onClick = state.onCreateClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun FeatureBlock(title: String, description: String, iconRes: Int, modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 12.dp),
|
||||
painter = painterResource(iconRes),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.primary1,
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 8.dp),
|
||||
) {
|
||||
Text(
|
||||
text = title,
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.padding(top = 4.dp),
|
||||
text = description,
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun PreviewCreateWalletContent() {
|
||||
TangemThemePreview {
|
||||
CreateMobileWalletContent(
|
||||
state = CreateMobileWalletUM(
|
||||
onBackClick = {},
|
||||
onCreateClick = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package com.tangem.features.hotwallet.di
|
||||
|
||||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
import com.tangem.features.hotwallet.DefaultHotWalletFeatureToggles
|
||||
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object HotWalletFeatureModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideFeatureToggles(featureTogglesManager: FeatureTogglesManager): HotWalletFeatureToggles {
|
||||
return DefaultHotWalletFeatureToggles(featureTogglesManager)
|
||||
}
|
||||
}
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal interface HotWalletFeatureModuleBinds
|
||||
1
features/kyc/api/.gitignore
vendored
Normal file
1
features/kyc/api/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
22
features/kyc/api/build.gradle.kts
Normal file
22
features/kyc/api/build.gradle.kts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
plugins {
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.tangem.features.kyc.api"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
/* Project - Domain */
|
||||
implementation(projects.domain.models)
|
||||
|
||||
/* Project - Core */
|
||||
implementation(projects.core.decompose)
|
||||
implementation(projects.core.ui)
|
||||
|
||||
/* Compose */
|
||||
implementation(deps.compose.runtime)
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.features.kyc
|
||||
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
|
||||
interface KycComponent {
|
||||
|
||||
fun launch()
|
||||
|
||||
interface Factory {
|
||||
fun create(appComponentContext: AppComponentContext): KycComponent
|
||||
}
|
||||
}
|
||||
1
features/kyc/impl/.gitignore
vendored
Normal file
1
features/kyc/impl/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
60
features/kyc/impl/build.gradle.kts
Normal file
60
features/kyc/impl/build.gradle.kts
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
plugins {
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
alias(deps.plugins.kotlin.kapt)
|
||||
alias(deps.plugins.kotlin.serialization)
|
||||
alias(deps.plugins.hilt.android)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.tangem.features.kyc.impl"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
/** Api */
|
||||
implementation(projects.features.kyc.api)
|
||||
|
||||
/** Domain */
|
||||
implementation(projects.domain.visa)
|
||||
implementation(projects.domain.wallets.models)
|
||||
|
||||
/** Core modules */
|
||||
implementation(projects.core.configToggles)
|
||||
implementation(projects.core.analytics)
|
||||
implementation(projects.core.analytics.models)
|
||||
implementation(projects.core.utils)
|
||||
implementation(projects.core.ui)
|
||||
implementation(projects.core.res)
|
||||
implementation(projects.core.decompose)
|
||||
implementation(projects.core.navigation)
|
||||
implementation(projects.core.datasource)
|
||||
implementation(projects.core.error)
|
||||
|
||||
/** Common */
|
||||
implementation(projects.common.ui)
|
||||
implementation(projects.common.routing)
|
||||
|
||||
/** Compose libraries */
|
||||
implementation(deps.compose.material3)
|
||||
implementation(deps.compose.animation)
|
||||
implementation(deps.compose.foundation)
|
||||
implementation(deps.compose.ui)
|
||||
implementation(deps.compose.ui.tooling)
|
||||
implementation(deps.compose.coil)
|
||||
implementation(deps.lottie.compose)
|
||||
implementation(deps.decompose.ext.compose)
|
||||
implementation(deps.androidx.activity.compose)
|
||||
|
||||
/** Other libraries */
|
||||
implementation(deps.kotlin.immutable.collections)
|
||||
implementation(deps.kotlin.serialization)
|
||||
implementation(deps.timber)
|
||||
implementation(deps.firebase.crashlytics)
|
||||
implementation(deps.sumsub.sdk)
|
||||
implementation(deps.arrow.core)
|
||||
|
||||
/** DI */
|
||||
implementation(deps.hilt.android)
|
||||
kapt(deps.hilt.kapt)
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
package com.tangem.features.kyc
|
||||
|
||||
import com.sumsub.sns.core.SNSMobileSDK
|
||||
import com.sumsub.sns.core.data.listener.SNSCompleteHandler
|
||||
import com.sumsub.sns.core.data.listener.TokenExpirationHandler
|
||||
import com.sumsub.sns.core.data.model.SNSCompletionResult
|
||||
import com.sumsub.sns.core.data.model.SNSSDKState
|
||||
import com.sumsub.sns.core.theme.SNSTheme
|
||||
import com.sumsub.sns.core.theme.SNSThemeMetric
|
||||
import com.sumsub.sns.core.theme.colors
|
||||
import com.sumsub.sns.core.theme.metrics
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.domain.pay.repository.KycRepository
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.util.Locale
|
||||
|
||||
class DefaultKycComponent @AssistedInject constructor(
|
||||
@Assisted appComponentContext: AppComponentContext,
|
||||
private val kycRepositoryFactory: KycRepository.Factory,
|
||||
) : KycComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
private val kycRepository = kycRepositoryFactory.create(UserWalletId("0FFFFF"))
|
||||
|
||||
override fun launch() {
|
||||
componentScope.launch {
|
||||
// val startInfo = kycRepository.getKycStartInfo().getOrNull() ?: return@launch
|
||||
|
||||
val tokenExpirationHandler = object : TokenExpirationHandler {
|
||||
override fun onTokenExpired(): String? {
|
||||
val newToken = runBlocking { kycRepository.getKycStartInfo().getOrNull()?.token }
|
||||
return newToken
|
||||
}
|
||||
}
|
||||
|
||||
val snsSdk = SNSMobileSDK.Builder(activity)
|
||||
.withAccessToken(
|
||||
"TODO",
|
||||
onTokenExpiration = tokenExpirationHandler,
|
||||
)
|
||||
.withTheme(
|
||||
SNSTheme {
|
||||
colors {
|
||||
}
|
||||
metrics {
|
||||
this.screenHeaderAlignment = SNSThemeMetric.TextAlignment.CENTER
|
||||
}
|
||||
},
|
||||
)
|
||||
.withLocale(Locale("en"))
|
||||
.withCompleteHandler(
|
||||
object : SNSCompleteHandler {
|
||||
override fun onComplete(result: SNSCompletionResult, state: SNSSDKState) {
|
||||
}
|
||||
},
|
||||
)
|
||||
.build()
|
||||
|
||||
snsSdk.launch()
|
||||
}
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : KycComponent.Factory {
|
||||
override fun create(appComponentContext: AppComponentContext): DefaultKycComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.features.kyc.di
|
||||
|
||||
import com.tangem.features.kyc.DefaultKycComponent
|
||||
import com.tangem.features.kyc.KycComponent
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal interface FeatureModule {
|
||||
|
||||
@Binds
|
||||
fun bindComponentFactory(impl: DefaultKycComponent.Factory): KycComponent.Factory
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@ android {
|
|||
dependencies {
|
||||
/* Project - API */
|
||||
implementation(projects.features.manageTokens.api)
|
||||
implementation(projects.features.swapV2.api)
|
||||
|
||||
/* Project - Core */
|
||||
implementation(projects.core.decompose)
|
||||
|
|
|
|||
|
|
@ -7,20 +7,27 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|||
import com.arkivanov.decompose.ComponentContext
|
||||
import com.arkivanov.decompose.extensions.compose.subscribeAsState
|
||||
import com.arkivanov.decompose.router.slot.childSlot
|
||||
import com.arkivanov.decompose.router.slot.dismiss
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.context.childByContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
|
||||
import com.tangem.features.managetokens.choosetoken.entity.ChooseManageTokensBottomSheetConfig
|
||||
import com.tangem.features.managetokens.choosetoken.model.ChooseManagedTokensModel
|
||||
import com.tangem.features.managetokens.choosetoken.ui.ChooseManagedTokenContent
|
||||
import com.tangem.features.managetokens.component.ChooseManagedTokensComponent
|
||||
import com.tangem.features.swap.v2.api.choosetoken.SwapChooseTokenNetworkComponent
|
||||
import com.tangem.features.swap.v2.api.choosetoken.SwapChooseTokenNetworkTrigger
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
internal class DefaultChooseManagedTokensComponent @AssistedInject constructor(
|
||||
@Assisted context: AppComponentContext,
|
||||
@Assisted private val params: ChooseManagedTokensComponent.Params,
|
||||
private val swapChooseTokenNetworkFactory: SwapChooseTokenNetworkComponent.Factory,
|
||||
private val swapChooseTokenNetworkTrigger: SwapChooseTokenNetworkTrigger,
|
||||
) : ChooseManagedTokensComponent, AppComponentContext by context {
|
||||
|
||||
private val model: ChooseManagedTokensModel = getOrCreateModel(params)
|
||||
|
|
@ -48,18 +55,21 @@ internal class DefaultChooseManagedTokensComponent @AssistedInject constructor(
|
|||
config: ChooseManageTokensBottomSheetConfig,
|
||||
componentContext: ComponentContext,
|
||||
): ComposableBottomSheetComponent = when (config) {
|
||||
else -> getStubComponent()
|
||||
}
|
||||
|
||||
private fun getStubComponent() = StubComponent()
|
||||
|
||||
class StubComponent : ComposableBottomSheetComponent {
|
||||
override fun dismiss() {}
|
||||
|
||||
@Composable
|
||||
override fun BottomSheet() {
|
||||
/* no-op */
|
||||
}
|
||||
is ChooseManageTokensBottomSheetConfig.SwapTokensBottomSheetConfig -> swapChooseTokenNetworkFactory.create(
|
||||
context = childByContext(componentContext),
|
||||
params = SwapChooseTokenNetworkComponent.Params(
|
||||
userWalletId = config.userWalletId,
|
||||
initialCurrency = config.initialCurrency,
|
||||
token = config.token,
|
||||
onDismiss = model.bottomSheetNavigation::dismiss,
|
||||
onResult = {
|
||||
componentScope.launch {
|
||||
swapChooseTokenNetworkTrigger.trigger(it)
|
||||
}
|
||||
router.pop()
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import com.tangem.pagination.BatchFetchResult
|
|||
import com.tangem.pagination.PaginationStatus
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -142,7 +143,11 @@ internal class ChooseManagedTokensModel @Inject constructor(
|
|||
private fun updateItems(items: ImmutableList<CurrencyItemUM>) {
|
||||
uiState.update { state ->
|
||||
state.copy(
|
||||
readContent = state.readContent.copy(items = items),
|
||||
readContent = state.readContent.copy(
|
||||
items = items.filterNot {
|
||||
it.id.value == params.initialCurrency.id.rawCurrencyId?.value
|
||||
}.toPersistentList(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package com.tangem.features.managetokens.utils
|
|||
import arrow.core.getOrElse
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.domain.managetokens.CheckIsCurrencyNotAddedUseCase
|
||||
import com.tangem.domain.managetokens.CreateCurrencyUseCase
|
||||
import com.tangem.domain.managetokens.CreateCryptoCurrencyUseCase
|
||||
import com.tangem.domain.managetokens.FindTokenUseCase
|
||||
import com.tangem.domain.managetokens.ValidateTokenFormUseCase
|
||||
import com.tangem.domain.managetokens.model.AddCustomTokenForm
|
||||
|
|
@ -23,7 +23,7 @@ import javax.inject.Inject
|
|||
@ModelScoped
|
||||
internal class CustomCurrencyValidator @Inject constructor(
|
||||
private val validateTokenFormUseCase: ValidateTokenFormUseCase,
|
||||
private val createCustomCurrencyUseCase: CreateCurrencyUseCase,
|
||||
private val createCryptoCurrencyUseCase: CreateCryptoCurrencyUseCase,
|
||||
private val findTokenUseCase: FindTokenUseCase,
|
||||
private val checkIsCurrencyNotAddedUseCase: CheckIsCurrencyNotAddedUseCase,
|
||||
) {
|
||||
|
|
@ -164,7 +164,7 @@ internal class CustomCurrencyValidator @Inject constructor(
|
|||
derivationPath: Network.DerivationPath,
|
||||
validatedForm: AddCustomTokenForm.Validated.All?,
|
||||
) {
|
||||
val currency = createCustomCurrencyUseCase(
|
||||
val currency = createCryptoCurrencyUseCase(
|
||||
userWalletId = userWalletId,
|
||||
networkId = networkId,
|
||||
derivationPath = derivationPath,
|
||||
|
|
|
|||
|
|
@ -9,8 +9,9 @@ import com.tangem.core.ui.format.bigdecimal.fiat
|
|||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.format.bigdecimal.percent
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.quote.QuoteStatus
|
||||
import com.tangem.domain.models.quote.fold
|
||||
import com.tangem.domain.onramp.model.HotCryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Quote
|
||||
import com.tangem.utils.converter.Converter
|
||||
import java.math.BigDecimal
|
||||
|
||||
|
|
@ -30,7 +31,7 @@ internal class HotTokenItemStateConverter(
|
|||
id = value.cryptoCurrency.id.value,
|
||||
iconState = CryptoCurrencyToIconStateConverter().convert(value.cryptoCurrency),
|
||||
titleState = TokenItemState.TitleState.Content(text = stringReference(value.cryptoCurrency.name)),
|
||||
subtitleState = value.quote.getCryptoPriceState(appCurrency),
|
||||
subtitleState = value.quoteStatus.getCryptoPriceState(appCurrency),
|
||||
fiatAmountState = null,
|
||||
subtitle2State = null,
|
||||
onItemClick = onItemClick.let { onItemClick -> { onItemClick(it, value) } },
|
||||
|
|
@ -38,17 +39,17 @@ internal class HotTokenItemStateConverter(
|
|||
)
|
||||
}
|
||||
|
||||
private fun Quote.getCryptoPriceState(appCurrency: AppCurrency): TokenItemState.SubtitleState {
|
||||
return when (this) {
|
||||
is Quote.Empty -> TokenItemState.SubtitleState.Unknown
|
||||
is Quote.Value -> {
|
||||
private fun QuoteStatus.getCryptoPriceState(appCurrency: AppCurrency): TokenItemState.SubtitleState {
|
||||
return fold(
|
||||
onData = {
|
||||
TokenItemState.SubtitleState.CryptoPriceContent(
|
||||
price = fiatRate.getFormattedCryptoPrice(appCurrency),
|
||||
priceChangePercent = priceChange.format { percent() },
|
||||
type = priceChange.getPriceChangeType(),
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
onEmpty = { TokenItemState.SubtitleState.Unknown },
|
||||
)
|
||||
}
|
||||
|
||||
private fun BigDecimal.getFormattedCryptoPrice(appCurrency: AppCurrency): String {
|
||||
|
|
|
|||
|
|
@ -51,6 +51,11 @@ internal fun OnrampAmountContent(state: OnrampAmountBlockUM, modifier: Modifier
|
|||
private fun OnrampAmountField(amountField: AmountFieldModel) {
|
||||
val decimalFormat = rememberDecimalFormat()
|
||||
val requester = remember { FocusRequester() }
|
||||
val symbolColor = if (amountField.fiatValue.isBlank()) {
|
||||
TangemTheme.colors.text.disabled
|
||||
} else {
|
||||
TangemTheme.colors.text.primary1
|
||||
}
|
||||
AmountTextField(
|
||||
value = amountField.fiatValue,
|
||||
decimals = amountField.fiatAmount.decimals,
|
||||
|
|
@ -59,6 +64,7 @@ private fun OnrampAmountField(amountField: AmountFieldModel) {
|
|||
symbol = amountField.fiatAmount.currencySymbol,
|
||||
currencyCode = amountField.fiatAmount.currencySymbol,
|
||||
decimalFormat = decimalFormat,
|
||||
symbolColor = symbolColor,
|
||||
),
|
||||
onValueChange = amountField.onValueChange,
|
||||
keyboardOptions = amountField.keyboardOptions,
|
||||
|
|
|
|||
|
|
@ -14,11 +14,11 @@ import androidx.compose.ui.draw.clip
|
|||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.selectedBorder
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.onramp.impl.R
|
||||
import com.tangem.features.onramp.paymentmethod.entity.PaymentMethodUM
|
||||
import com.tangem.features.onramp.paymentmethod.entity.PaymentMethodsBottomSheetConfig
|
||||
import com.tangem.features.onramp.utils.selectedBorder
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@Composable
|
||||
|
|
@ -59,27 +59,16 @@ private fun SelectPaymentMethodBottomSheetContent(
|
|||
val isSelected = remember(item, selectedMethodId) {
|
||||
item.id == selectedMethodId
|
||||
}
|
||||
val itemModifier = if (isSelected) {
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.selectedBorder()
|
||||
.clickable(onClick = item.onSelect)
|
||||
.padding(
|
||||
start = TangemTheme.dimens.spacing12,
|
||||
top = TangemTheme.dimens.spacing10,
|
||||
bottom = TangemTheme.dimens.spacing10,
|
||||
)
|
||||
} else {
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(TangemTheme.dimens.radius16))
|
||||
.clickable(onClick = item.onSelect)
|
||||
.padding(
|
||||
start = TangemTheme.dimens.spacing12,
|
||||
top = TangemTheme.dimens.spacing10,
|
||||
bottom = TangemTheme.dimens.spacing10,
|
||||
)
|
||||
}
|
||||
val itemModifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.selectedBorder(isSelected = isSelected)
|
||||
.clip(RoundedCornerShape(TangemTheme.dimens.radius16))
|
||||
.clickable(onClick = item.onSelect)
|
||||
.padding(
|
||||
start = TangemTheme.dimens.spacing12,
|
||||
top = TangemTheme.dimens.spacing10,
|
||||
bottom = TangemTheme.dimens.spacing10,
|
||||
)
|
||||
PaymentMethodItem(
|
||||
modifier = itemModifier,
|
||||
paymentMethod = item,
|
||||
|
|
|
|||
|
|
@ -26,13 +26,10 @@ import androidx.compose.ui.util.fastForEach
|
|||
import coil.compose.SubcomposeAsyncImage
|
||||
import coil.request.ImageRequest
|
||||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
|
||||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.domain.onramp.model.OnrampPaymentMethod
|
||||
|
|
@ -41,7 +38,6 @@ import com.tangem.features.onramp.paymentmethod.ui.PaymentMethodIcon
|
|||
import com.tangem.features.onramp.providers.entity.ProviderListItemUM
|
||||
import com.tangem.features.onramp.providers.entity.SelectPaymentAndProviderUM
|
||||
import com.tangem.features.onramp.providers.model.previewData.SelectProviderPreviewData
|
||||
import com.tangem.features.onramp.utils.selectedBorder
|
||||
|
||||
@Composable
|
||||
internal fun SelectProviderBottomSheet(config: TangemBottomSheetConfig, content: @Composable () -> Unit) {
|
||||
|
|
@ -151,13 +147,7 @@ private fun ProviderItem(state: ProviderListItemUM, modifier: Modifier = Modifie
|
|||
private fun AvailableProviderItem(state: ProviderListItemUM.Available.Content, modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.then(
|
||||
if (state.isSelected) {
|
||||
Modifier.selectedBorder()
|
||||
} else {
|
||||
Modifier.clip(RoundedCornerShape(16.dp))
|
||||
},
|
||||
)
|
||||
.selectedBorder(isSelected = state.isSelected)
|
||||
.clickable(onClick = state.onClick)
|
||||
.padding(all = TangemTheme.dimens.spacing12),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
|
|
@ -227,13 +217,7 @@ private fun UnavailableProviderItem(
|
|||
) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.then(
|
||||
if (isSelected) {
|
||||
Modifier.selectedBorder()
|
||||
} else {
|
||||
Modifier.clip(RoundedCornerShape(16.dp))
|
||||
},
|
||||
)
|
||||
.selectedBorder(isSelected = isSelected)
|
||||
.padding(all = TangemTheme.dimens.spacing12),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
|
||||
|
|
|
|||
|
|
@ -1,24 +0,0 @@
|
|||
package com.tangem.features.onramp.utils
|
||||
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
@Composable
|
||||
internal fun Modifier.selectedBorder() = border(
|
||||
width = 2.5.dp,
|
||||
color = TangemTheme.colors.text.accent.copy(alpha = 0.1f),
|
||||
shape = RoundedCornerShape(18.dp),
|
||||
)
|
||||
.padding(2.5.dp)
|
||||
.border(
|
||||
width = 1.dp,
|
||||
color = TangemTheme.colors.text.accent,
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
)
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
|
|
@ -13,12 +13,25 @@ dependencies {
|
|||
implementation(projects.core.decompose)
|
||||
implementation(projects.core.ui)
|
||||
|
||||
/** Common */
|
||||
implementation(projects.common.ui)
|
||||
|
||||
/** Domain models */
|
||||
api(projects.domain.models)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.appCurrency.models)
|
||||
implementation(projects.domain.nft.models)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.transaction.models)
|
||||
implementation(projects.domain.wallets.models)
|
||||
|
||||
/* Compose */
|
||||
/** Compose */
|
||||
implementation(deps.compose.runtime)
|
||||
implementation(deps.compose.foundation)
|
||||
|
||||
/** Tangem */
|
||||
implementation(tangemDeps.blockchain)
|
||||
|
||||
/** Other */
|
||||
implementation(deps.arrow.core)
|
||||
implementation(deps.kotlin.immutable.collections)
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.features.send.v2.api
|
||||
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
|
||||
import com.tangem.features.send.v2.api.params.FeeSelectorParams
|
||||
|
||||
interface FeeSelectorBlockComponent : ComposableContentComponent {
|
||||
|
||||
fun updateState(feeSelectorUM: FeeSelectorUM)
|
||||
|
||||
interface Factory : ComponentFactory<FeeSelectorParams.FeeSelectorBlockParams, FeeSelectorBlockComponent>
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.features.send.v2.api
|
||||
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
|
||||
import com.tangem.features.send.v2.api.params.FeeSelectorParams
|
||||
|
||||
interface FeeSelectorComponent : ComposableBottomSheetComponent {
|
||||
interface Factory : ComponentFactory<FeeSelectorParams.FeeSelectorDetailsParams, FeeSelectorComponent>
|
||||
}
|
||||
|
|
@ -1,3 +1,6 @@
|
|||
package com.tangem.features.send.v2.api
|
||||
|
||||
interface SendFeatureToggles
|
||||
interface SendFeatureToggles {
|
||||
|
||||
val isSendRedesignEnabled: Boolean
|
||||
}
|
||||
|
|
@ -1,41 +1,28 @@
|
|||
package com.tangem.features.send.v2.subcomponents.notifications
|
||||
package com.tangem.features.send.v2.api
|
||||
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.transaction.error.GetFeeError
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.features.send.v2.subcomponents.notifications
|
||||
import com.tangem.features.send.v2.subcomponents.notifications.model.NotificationData
|
||||
import com.tangem.features.send.v2.subcomponents.notifications.model.NotificationsModel
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class NotificationsComponent(
|
||||
appComponentContext: AppComponentContext,
|
||||
params: Params,
|
||||
) : AppComponentContext by appComponentContext {
|
||||
interface SendNotificationsComponent {
|
||||
|
||||
private val model: NotificationsModel = getOrCreateModel(params)
|
||||
|
||||
val state: StateFlow<ImmutableList<NotificationUM>> = model.uiState
|
||||
val state: StateFlow<ImmutableList<NotificationUM>>
|
||||
|
||||
fun LazyListScope.content(
|
||||
state: ImmutableList<NotificationUM>,
|
||||
modifier: Modifier = Modifier,
|
||||
hasPaddingAbove: Boolean = false,
|
||||
isClickDisabled: Boolean = false,
|
||||
) {
|
||||
notifications(
|
||||
notifications = state,
|
||||
modifier = modifier,
|
||||
hasPaddingAbove = hasPaddingAbove,
|
||||
isClickDisabled = isClickDisabled,
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
data class Params(
|
||||
val analyticsCategoryName: String,
|
||||
|
|
@ -44,5 +31,17 @@ internal class NotificationsComponent(
|
|||
val feeCryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
val appCurrency: AppCurrency,
|
||||
val notificationData: NotificationData,
|
||||
)
|
||||
) {
|
||||
data class NotificationData(
|
||||
val destinationAddress: String,
|
||||
val memo: String?,
|
||||
val amountValue: BigDecimal,
|
||||
val reduceAmountBy: BigDecimal,
|
||||
val isIgnoreReduce: Boolean,
|
||||
val fee: Fee?,
|
||||
val feeError: GetFeeError?,
|
||||
)
|
||||
}
|
||||
|
||||
interface Factory : ComponentFactory<Params, SendNotificationsComponent>
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package com.tangem.features.send.v2.api.callbacks
|
||||
|
||||
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
|
||||
|
||||
interface FeeSelectorModelCallback {
|
||||
fun onFeeResult(feeSelectorUM: FeeSelectorUM)
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.features.feeselector.api.entity
|
||||
package com.tangem.features.send.v2.api.entity
|
||||
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
package com.tangem.features.send.v2.api.entity
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.transaction.error.GetFeeError
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import java.math.BigDecimal
|
||||
import java.math.BigInteger
|
||||
|
||||
@Immutable
|
||||
sealed class FeeSelectorUM {
|
||||
|
||||
data object Loading : FeeSelectorUM()
|
||||
|
||||
data class Error(val error: GetFeeError) : FeeSelectorUM()
|
||||
|
||||
data class Content(
|
||||
val feeItems: ImmutableList<FeeItem>,
|
||||
val selectedFeeItem: FeeItem,
|
||||
val isFeeApproximate: Boolean,
|
||||
val feeFiatRateUM: FeeFiatRateUM?,
|
||||
val displayNonceInput: Boolean,
|
||||
val nonce: BigInteger?,
|
||||
val onNonceChange: (String) -> Unit,
|
||||
) : FeeSelectorUM()
|
||||
}
|
||||
|
||||
@Immutable
|
||||
data class FeeFiatRateUM(
|
||||
val rate: BigDecimal,
|
||||
val appCurrency: AppCurrency,
|
||||
)
|
||||
|
||||
@Immutable
|
||||
sealed class FeeItem {
|
||||
abstract val fee: Fee
|
||||
|
||||
fun isSame(other: FeeItem): Boolean {
|
||||
return this::class == other::class
|
||||
}
|
||||
|
||||
data class Suggested(val title: TextReference, override val fee: Fee) : FeeItem()
|
||||
data class Slow(override val fee: Fee) : FeeItem()
|
||||
data class Market(override val fee: Fee) : FeeItem()
|
||||
data class Fast(override val fee: Fee) : FeeItem()
|
||||
data class Custom(override val fee: Fee, val customValues: ImmutableList<CustomFeeFieldUM>) : FeeItem()
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package com.tangem.features.send.v2.api.params
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.transaction.error.GetFeeError
|
||||
import com.tangem.features.send.v2.api.callbacks.FeeSelectorModelCallback
|
||||
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
|
||||
|
||||
sealed class FeeSelectorParams {
|
||||
abstract val state: FeeSelectorUM
|
||||
abstract val onLoadFee: suspend () -> Either<GetFeeError, TransactionFee>
|
||||
abstract val cryptoCurrencyStatus: CryptoCurrencyStatus
|
||||
abstract val callback: FeeSelectorModelCallback
|
||||
abstract val suggestedFeeState: SuggestedFeeState
|
||||
|
||||
data class FeeSelectorBlockParams(
|
||||
override val state: FeeSelectorUM,
|
||||
override val onLoadFee: suspend () -> Either<GetFeeError, TransactionFee>,
|
||||
override val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
override val callback: FeeSelectorModelCallback,
|
||||
override val suggestedFeeState: SuggestedFeeState,
|
||||
) : FeeSelectorParams()
|
||||
|
||||
data class FeeSelectorDetailsParams(
|
||||
override val state: FeeSelectorUM,
|
||||
override val onLoadFee: suspend () -> Either<GetFeeError, TransactionFee>,
|
||||
override val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
override val callback: FeeSelectorModelCallback,
|
||||
override val suggestedFeeState: SuggestedFeeState,
|
||||
) : FeeSelectorParams()
|
||||
|
||||
sealed class SuggestedFeeState {
|
||||
data object None : SuggestedFeeState()
|
||||
data class Suggestion(val title: TextReference, val fee: Fee) : SuggestedFeeState()
|
||||
}
|
||||
}
|
||||
|
|
@ -16,7 +16,6 @@ dependencies {
|
|||
implementation(projects.features.sendV2.api)
|
||||
implementation(projects.features.txhistory.api)
|
||||
implementation(projects.features.nft.api)
|
||||
implementation(projects.features.feeSelector.api)
|
||||
|
||||
/** Libs */
|
||||
implementation(projects.libs.crypto)
|
||||
|
|
|
|||
|
|
@ -4,5 +4,8 @@ import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
|||
import com.tangem.features.send.v2.api.SendFeatureToggles
|
||||
|
||||
internal class DefaultSendFeatureToggles(
|
||||
@Suppress("UnusedPrivateMember") private val featureToggles: FeatureTogglesManager,
|
||||
) : SendFeatureToggles
|
||||
private val featureToggles: FeatureTogglesManager,
|
||||
) : SendFeatureToggles {
|
||||
override val isSendRedesignEnabled: Boolean
|
||||
get() = featureToggles.isFeatureEnabled("SEND_REDESIGN_ENABLED")
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.features.send.v2.common.analytics
|
||||
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.BLOCKCHAIN
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.SOURCE
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.TOKEN_PARAM
|
||||
|
||||
|
|
@ -95,6 +96,20 @@ internal sealed class CommonSendAnalyticEvents(
|
|||
params = mapOf(SOURCE to source.name),
|
||||
)
|
||||
|
||||
/** Fee screen is closed with non empty nonce */
|
||||
data class NonceInserted(
|
||||
val categoryName: String,
|
||||
val token: String,
|
||||
val blockchain: String,
|
||||
) : CommonSendAnalyticEvents(
|
||||
category = categoryName,
|
||||
event = "Nonce Inserted",
|
||||
params = mapOf(
|
||||
TOKEN_PARAM to token,
|
||||
BLOCKCHAIN to blockchain,
|
||||
),
|
||||
)
|
||||
|
||||
companion object {
|
||||
const val SEND_CATEGORY = "Token / Send"
|
||||
const val NFT_SEND_CATEGORY = "NFT"
|
||||
|
|
|
|||
|
|
@ -5,8 +5,10 @@ import com.tangem.features.send.v2.DefaultSendFeatureToggles
|
|||
import com.tangem.features.send.v2.api.NFTSendComponent
|
||||
import com.tangem.features.send.v2.api.SendComponent
|
||||
import com.tangem.features.send.v2.api.SendFeatureToggles
|
||||
import com.tangem.features.send.v2.api.SendNotificationsComponent
|
||||
import com.tangem.features.send.v2.send.DefaultSendComponent
|
||||
import com.tangem.features.send.v2.sendnft.DefaultNFTSendComponent
|
||||
import com.tangem.features.send.v2.subcomponents.notifications.DefaultSendNotificationsComponent
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -35,4 +37,10 @@ internal interface SendFeatureModuleBinds {
|
|||
@Binds
|
||||
@Singleton
|
||||
fun provideNFTSendComponentFactory(impl: DefaultNFTSendComponent.Factory): NFTSendComponent.Factory
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun provideNotificationComponentFactory(
|
||||
impl: DefaultSendNotificationsComponent.Factory,
|
||||
): SendNotificationsComponent.Factory
|
||||
}
|
||||
|
|
@ -0,0 +1,195 @@
|
|||
package com.tangem.features.send.v2.feeselector
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Devices
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.common.ui.amountScreen.utils.getFiatString
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.ui.components.SpacerWMax
|
||||
import com.tangem.core.ui.components.TextShimmer
|
||||
import com.tangem.core.ui.components.atoms.text.EllipsisText
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.format.bigdecimal.BigDecimalFormatConstants.EMPTY_BALANCE_SIGN
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.core.ui.format.bigdecimal.fee
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.features.send.v2.api.FeeSelectorBlockComponent
|
||||
import com.tangem.features.send.v2.api.entity.FeeFiatRateUM
|
||||
import com.tangem.features.send.v2.api.entity.FeeItem
|
||||
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
|
||||
import com.tangem.features.send.v2.api.params.FeeSelectorParams
|
||||
import com.tangem.features.send.v2.feeselector.model.FeeSelectorModel
|
||||
import com.tangem.features.send.v2.impl.R
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class DefaultFeeSelectorBlockComponent @AssistedInject constructor(
|
||||
@Assisted appComponentContext: AppComponentContext,
|
||||
@Assisted params: FeeSelectorParams.FeeSelectorBlockParams,
|
||||
) : FeeSelectorBlockComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
private val model: FeeSelectorModel = getOrCreateModel(params = params)
|
||||
|
||||
init {
|
||||
model.uiState
|
||||
.onEach(params.callback::onFeeResult)
|
||||
.launchIn(componentScope)
|
||||
}
|
||||
|
||||
override fun updateState(feeSelectorUM: FeeSelectorUM) {
|
||||
model.updateState(feeSelectorUM)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val state by model.uiState.collectAsStateWithLifecycle()
|
||||
FeeSelectorBlockContent(modifier = modifier, state = state)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : FeeSelectorBlockComponent.Factory {
|
||||
override fun create(
|
||||
context: AppComponentContext,
|
||||
params: FeeSelectorParams.FeeSelectorBlockParams,
|
||||
): DefaultFeeSelectorBlockComponent
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun FeeSelectorBlockContent(state: FeeSelectorUM, modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.background(TangemTheme.colors.background.action)
|
||||
.padding(12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(24.dp),
|
||||
painter = painterResource(R.drawable.ic_fee_new_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.accent,
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.padding(start = TangemTheme.dimens.spacing4),
|
||||
text = stringResourceSafe(R.string.common_network_fee_title),
|
||||
style = TangemTheme.typography.body1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.padding(start = TangemTheme.dimens.spacing6)
|
||||
.size(TangemTheme.dimens.size16),
|
||||
painter = painterResource(id = R.drawable.ic_token_info_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
)
|
||||
SpacerWMax()
|
||||
when (state) {
|
||||
is FeeSelectorUM.Content -> FeeContent(state)
|
||||
is FeeSelectorUM.Loading -> FeeLoading()
|
||||
is FeeSelectorUM.Error -> FeeError()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RowScope.FeeError() {
|
||||
Text(
|
||||
text = EMPTY_BALANCE_SIGN,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
style = TangemTheme.typography.body2,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RowScope.FeeLoading() {
|
||||
TextShimmer(
|
||||
radius = TangemTheme.dimens.radius3,
|
||||
style = TangemTheme.typography.body1,
|
||||
modifier = Modifier.width(width = TangemTheme.dimens.size90),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RowScope.FeeContent(state: FeeSelectorUM.Content) {
|
||||
val fiatRate = state.feeFiatRateUM
|
||||
EllipsisText(
|
||||
text = if (fiatRate != null) {
|
||||
getFiatString(
|
||||
value = state.selectedFeeItem.fee.amount.value,
|
||||
rate = fiatRate.rate,
|
||||
appCurrency = fiatRate.appCurrency,
|
||||
approximate = state.isFeeApproximate,
|
||||
)
|
||||
} else {
|
||||
state.selectedFeeItem.fee.amount.value.format {
|
||||
crypto(
|
||||
symbol = state.selectedFeeItem.fee.amount.currencySymbol,
|
||||
decimals = state.selectedFeeItem.fee.amount.decimals,
|
||||
).fee(canBeLower = state.isFeeApproximate)
|
||||
}
|
||||
},
|
||||
style = TangemTheme.typography.body1,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
textAlign = TextAlign.End,
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(start = TangemTheme.dimens.spacing4),
|
||||
)
|
||||
Icon(
|
||||
modifier = Modifier.size(width = 18.dp, height = 24.dp),
|
||||
painter = painterResource(id = R.drawable.ic_select_18_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, device = Devices.PIXEL_7_PRO)
|
||||
@Preview(showBackground = true, device = Devices.PIXEL_7_PRO, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun FeeSelectorBlockContent_Preview() {
|
||||
TangemThemePreview {
|
||||
val feeItem = FeeItem.Market(
|
||||
Fee.Common(amount = Amount(value = BigDecimal("0.0002876"), blockchain = Blockchain.Ethereum)),
|
||||
)
|
||||
FeeSelectorBlockContent(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
state = FeeSelectorUM.Content(
|
||||
feeItems = persistentListOf(feeItem),
|
||||
selectedFeeItem = feeItem,
|
||||
isFeeApproximate = false,
|
||||
feeFiatRateUM = FeeFiatRateUM(
|
||||
rate = BigDecimal("2500"),
|
||||
appCurrency = AppCurrency.Default,
|
||||
),
|
||||
displayNonceInput = false,
|
||||
nonce = null,
|
||||
onNonceChange = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +1,21 @@
|
|||
package com.tangem.features.feeselector.impl.component
|
||||
package com.tangem.features.send.v2.feeselector
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.features.feeselector.api.component.FeeSelectorComponent
|
||||
import com.tangem.features.feeselector.impl.model.FeeSelectorModel
|
||||
import com.tangem.features.feeselector.impl.ui.FeeSelectorModalBottomSheet
|
||||
import com.tangem.features.send.v2.api.FeeSelectorComponent
|
||||
import com.tangem.features.send.v2.api.params.FeeSelectorParams
|
||||
import com.tangem.features.send.v2.feeselector.model.FeeSelectorModel
|
||||
import com.tangem.features.send.v2.feeselector.ui.FeeSelectorModalBottomSheet
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
internal class DefaultFeeSelectorComponent @AssistedInject constructor(
|
||||
@Assisted appComponentContext: AppComponentContext,
|
||||
@Assisted private val params: FeeSelectorComponent.Params,
|
||||
@Assisted private val params: FeeSelectorParams.FeeSelectorDetailsParams,
|
||||
) : FeeSelectorComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
private val model: FeeSelectorModel = getOrCreateModel(params = params)
|
||||
|
|
@ -25,21 +26,15 @@ internal class DefaultFeeSelectorComponent @AssistedInject constructor(
|
|||
|
||||
@Composable
|
||||
override fun BottomSheet() {
|
||||
FeeSelectorModalBottomSheet(onDismiss = ::dismiss, state = TODO())
|
||||
}
|
||||
|
||||
// Temporary workaround, to use test this component
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
BackHandler(onBack = router::pop)
|
||||
FeeSelectorModalBottomSheet(onDismiss = ::dismiss, state = TODO())
|
||||
val state by model.uiState.collectAsStateWithLifecycle()
|
||||
FeeSelectorModalBottomSheet(onDismiss = ::dismiss, state = state, feeSelectorIntents = model)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : FeeSelectorComponent.Factory {
|
||||
override fun create(
|
||||
context: AppComponentContext,
|
||||
params: FeeSelectorComponent.Params,
|
||||
params: FeeSelectorParams.FeeSelectorDetailsParams,
|
||||
): DefaultFeeSelectorComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.tangem.features.send.v2.feeselector.di
|
||||
|
||||
import com.tangem.features.send.v2.api.FeeSelectorBlockComponent
|
||||
import com.tangem.features.send.v2.api.FeeSelectorComponent
|
||||
import com.tangem.features.send.v2.feeselector.DefaultFeeSelectorBlockComponent
|
||||
import com.tangem.features.send.v2.feeselector.DefaultFeeSelectorComponent
|
||||
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 FeeSelectorFeatureModule {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindComponentFactory(factory: DefaultFeeSelectorComponent.Factory): FeeSelectorComponent.Factory
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindBlockComponentFactory(factory: DefaultFeeSelectorBlockComponent.Factory): FeeSelectorBlockComponent.Factory
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
package com.tangem.features.feeselector.impl.di
|
||||
package com.tangem.features.send.v2.feeselector.di
|
||||
|
||||
import com.tangem.core.decompose.di.ModelComponent
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.features.feeselector.impl.model.FeeSelectorModel
|
||||
import com.tangem.features.send.v2.feeselector.model.FeeSelectorModel
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.features.send.v2.feeselector.model
|
||||
|
||||
import com.tangem.features.send.v2.api.entity.FeeItem
|
||||
|
||||
internal interface FeeSelectorIntents {
|
||||
fun onFeeItemSelected(feeItem: FeeItem)
|
||||
fun onCustomFeeValueChange(index: Int, value: String)
|
||||
fun onCustomFeeNextClick()
|
||||
fun onDoneClick()
|
||||
}
|
||||
|
||||
internal class StubFeeSelectorIntents : FeeSelectorIntents {
|
||||
override fun onFeeItemSelected(feeItem: FeeItem) {}
|
||||
override fun onCustomFeeValueChange(index: Int, value: String) {}
|
||||
override fun onCustomFeeNextClick() {}
|
||||
override fun onDoneClick() {}
|
||||
}
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
package com.tangem.features.send.v2.feeselector.model
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
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.decompose.navigation.Router
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.transaction.usecase.IsFeeApproximateUseCase
|
||||
import com.tangem.features.send.v2.api.entity.FeeItem
|
||||
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
|
||||
import com.tangem.features.send.v2.api.params.FeeSelectorParams
|
||||
import com.tangem.features.send.v2.feeselector.model.transformers.FeeItemSelectedTransformer
|
||||
import com.tangem.features.send.v2.feeselector.model.transformers.FeeSelectorErrorTransformer
|
||||
import com.tangem.features.send.v2.feeselector.model.transformers.FeeSelectorLoadedTransformer
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.transformer.update
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@Stable
|
||||
@ModelScoped
|
||||
internal class FeeSelectorModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
private val isFeeApproximateUseCase: IsFeeApproximateUseCase,
|
||||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val router: Router,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
) : Model(), FeeSelectorIntents {
|
||||
|
||||
private val params = paramsContainer.require<FeeSelectorParams>()
|
||||
private var appCurrency: AppCurrency = AppCurrency.Default
|
||||
|
||||
val uiState: StateFlow<FeeSelectorUM>
|
||||
field = MutableStateFlow<FeeSelectorUM>(params.state)
|
||||
|
||||
init {
|
||||
initAppCurrency()
|
||||
loadFee()
|
||||
}
|
||||
|
||||
fun updateState(feeSelectorUM: FeeSelectorUM) {
|
||||
uiState.value = feeSelectorUM
|
||||
}
|
||||
|
||||
fun dismiss() {
|
||||
router.pop()
|
||||
}
|
||||
|
||||
private fun initAppCurrency() {
|
||||
modelScope.launch {
|
||||
appCurrency = getSelectedAppCurrencyUseCase.invokeSync().getOrElse { AppCurrency.Default }
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadFee() {
|
||||
modelScope.launch {
|
||||
params.onLoadFee()
|
||||
.fold(
|
||||
ifLeft = { error -> uiState.update(FeeSelectorErrorTransformer(error)) },
|
||||
ifRight = { fee ->
|
||||
uiState.update(
|
||||
FeeSelectorLoadedTransformer(
|
||||
cryptoCurrencyStatus = params.cryptoCurrencyStatus,
|
||||
appCurrency = appCurrency,
|
||||
fees = fee,
|
||||
suggestedFeeState = params.suggestedFeeState,
|
||||
isFeeApproximate = isFeeApproximate(fee.normal.amount.type),
|
||||
feeSelectorIntents = this@FeeSelectorModel,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isFeeApproximate(amountType: AmountType): Boolean {
|
||||
val networkId = params.cryptoCurrencyStatus.currency.network.id
|
||||
return isFeeApproximateUseCase(networkId = networkId, amountType = amountType)
|
||||
}
|
||||
|
||||
override fun onFeeItemSelected(feeItem: FeeItem) {
|
||||
uiState.update(FeeItemSelectedTransformer(feeItem))
|
||||
}
|
||||
|
||||
override fun onCustomFeeValueChange(index: Int, value: String) {
|
||||
// TODO: [REDACTED_JIRA]
|
||||
}
|
||||
|
||||
override fun onCustomFeeNextClick() {
|
||||
// TODO: [REDACTED_JIRA]
|
||||
}
|
||||
|
||||
override fun onDoneClick() {
|
||||
params.callback.onFeeResult(uiState.value)
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
package com.tangem.features.send.v2.feeselector.model.transformers
|
||||
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.features.send.v2.api.params.FeeSelectorParams
|
||||
import com.tangem.features.send.v2.api.entity.FeeItem
|
||||
import com.tangem.features.send.v2.feeselector.model.FeeSelectorIntents
|
||||
import com.tangem.utils.converter.Converter
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
internal class FeeItemConverter(
|
||||
private val suggestedFeeState: FeeSelectorParams.SuggestedFeeState,
|
||||
private val normalFee: Fee,
|
||||
private val feeSelectorIntents: FeeSelectorIntents,
|
||||
private val appCurrency: AppCurrency,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
) : Converter<TransactionFee, ImmutableList<FeeItem>> {
|
||||
|
||||
private val customFeeFieldConverter = FeeSelectorCustomFieldConverter(
|
||||
feeSelectorIntents = feeSelectorIntents,
|
||||
appCurrency = appCurrency,
|
||||
feeCryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
normalFee = normalFee,
|
||||
)
|
||||
|
||||
override fun convert(value: TransactionFee): ImmutableList<FeeItem> {
|
||||
val fees = mutableListOf<FeeItem>()
|
||||
|
||||
when (suggestedFeeState) {
|
||||
FeeSelectorParams.SuggestedFeeState.None -> Unit
|
||||
is FeeSelectorParams.SuggestedFeeState.Suggestion -> fees.add(
|
||||
FeeItem.Suggested(
|
||||
title = suggestedFeeState.title,
|
||||
fee = suggestedFeeState.fee,
|
||||
),
|
||||
)
|
||||
}
|
||||
when (value) {
|
||||
is TransactionFee.Choosable -> {
|
||||
fees.add(FeeItem.Slow(fee = value.minimum))
|
||||
fees.add(FeeItem.Market(fee = value.normal))
|
||||
fees.add(FeeItem.Fast(fee = value.priority))
|
||||
}
|
||||
is TransactionFee.Single -> {
|
||||
fees.add(FeeItem.Market(fee = value.normal))
|
||||
}
|
||||
}
|
||||
val customFeeFields = customFeeFieldConverter.convert(normalFee)
|
||||
if (customFeeFields.isNotEmpty()) {
|
||||
fees.add(
|
||||
FeeItem.Custom(
|
||||
fee = customFeeFieldConverter.convertBack(customFeeFields),
|
||||
customValues = customFeeFields,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
return fees.toImmutableList()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.features.send.v2.feeselector.model.transformers
|
||||
|
||||
import com.tangem.features.send.v2.api.entity.FeeItem
|
||||
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
|
||||
internal class FeeItemSelectedTransformer(private val selectedFeeItem: FeeItem) : Transformer<FeeSelectorUM> {
|
||||
override fun transform(prevState: FeeSelectorUM): FeeSelectorUM {
|
||||
prevState as? FeeSelectorUM.Content ?: return prevState
|
||||
|
||||
return prevState.copy(selectedFeeItem = selectedFeeItem)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
package com.tangem.features.send.v2.feeselector.model.transformers
|
||||
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.core.ui.utils.parseToBigDecimal
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.features.send.v2.subcomponents.fee.model.converters.custom.bitcoin.BitcoinCustomFeeConverter
|
||||
import com.tangem.features.send.v2.subcomponents.fee.model.converters.custom.ethereum.EthereumCustomFeeConverter
|
||||
import com.tangem.features.send.v2.subcomponents.fee.model.converters.custom.kaspa.KaspaCustomFeeConverter
|
||||
import com.tangem.features.send.v2.api.entity.CustomFeeFieldUM
|
||||
import com.tangem.features.send.v2.feeselector.model.FeeSelectorIntents
|
||||
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeSelectorUM
|
||||
import com.tangem.utils.converter.TwoWayConverter
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
internal class FeeSelectorCustomFieldConverter(
|
||||
private val feeSelectorIntents: FeeSelectorIntents,
|
||||
private val appCurrency: AppCurrency,
|
||||
private val feeCryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
private val normalFee: Fee,
|
||||
) : TwoWayConverter<Fee, ImmutableList<CustomFeeFieldUM>> {
|
||||
|
||||
private val ethereumCustomFeeConverter by lazy(LazyThreadSafetyMode.NONE) {
|
||||
EthereumCustomFeeConverter(
|
||||
onCustomFeeValueChange = feeSelectorIntents::onCustomFeeValueChange,
|
||||
onNextClick = feeSelectorIntents::onCustomFeeNextClick,
|
||||
appCurrency = appCurrency,
|
||||
feeCryptoCurrencyStatus = feeCryptoCurrencyStatus,
|
||||
)
|
||||
}
|
||||
|
||||
private val bitcoinCustomFeeConverter by lazy(LazyThreadSafetyMode.NONE) {
|
||||
BitcoinCustomFeeConverter(
|
||||
onCustomFeeValueChange = feeSelectorIntents::onCustomFeeValueChange,
|
||||
onNextClick = feeSelectorIntents::onCustomFeeNextClick,
|
||||
appCurrency = appCurrency,
|
||||
feeCryptoCurrencyStatus = feeCryptoCurrencyStatus,
|
||||
)
|
||||
}
|
||||
|
||||
private val kaspaCustomFeeConverter by lazy(LazyThreadSafetyMode.NONE) {
|
||||
KaspaCustomFeeConverter(
|
||||
onCustomFeeValueChange = feeSelectorIntents::onCustomFeeValueChange,
|
||||
appCurrency = appCurrency,
|
||||
feeCryptoCurrencyStatus = feeCryptoCurrencyStatus,
|
||||
)
|
||||
}
|
||||
|
||||
override fun convert(value: Fee): ImmutableList<CustomFeeFieldUM> {
|
||||
return when (value) {
|
||||
is Fee.Ethereum -> ethereumCustomFeeConverter.convert(value)
|
||||
is Fee.Bitcoin -> bitcoinCustomFeeConverter.convert(value)
|
||||
is Fee.Kaspa -> kaspaCustomFeeConverter.convert(value)
|
||||
else -> persistentListOf()
|
||||
}
|
||||
}
|
||||
|
||||
override fun convertBack(value: ImmutableList<CustomFeeFieldUM>): Fee {
|
||||
return if (value.isEmpty()) {
|
||||
normalFee
|
||||
} else {
|
||||
when (normalFee) {
|
||||
is Fee.Ethereum -> ethereumCustomFeeConverter.convertBack(normalFee = normalFee, value = value)
|
||||
is Fee.Bitcoin -> bitcoinCustomFeeConverter.convertBack(normalFee = normalFee, value = value)
|
||||
is Fee.Kaspa -> kaspaCustomFeeConverter.convertBack(normalFee = normalFee, value = value)
|
||||
else -> {
|
||||
val customFee = value.firstOrNull()
|
||||
Fee.Common(
|
||||
normalFee.amount.copy(
|
||||
value = customFee?.value?.parseToBigDecimal(customFee.decimals),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onValueChange(feeSelectorState: FeeSelectorUM.Content, index: Int, value: String) =
|
||||
when (val fee = feeSelectorState.fees.normal) {
|
||||
is Fee.Ethereum -> ethereumCustomFeeConverter.onValueChange(
|
||||
feeValue = fee,
|
||||
customValues = feeSelectorState.customValues,
|
||||
index = index,
|
||||
value = value,
|
||||
)
|
||||
is Fee.Bitcoin -> bitcoinCustomFeeConverter.onValueChange(
|
||||
customValues = feeSelectorState.customValues,
|
||||
index = index,
|
||||
value = value,
|
||||
txSize = fee.txSize,
|
||||
)
|
||||
is Fee.Kaspa -> kaspaCustomFeeConverter.onValueChange(
|
||||
customValues = feeSelectorState.customValues,
|
||||
index = index,
|
||||
value = value,
|
||||
)
|
||||
else -> feeSelectorState.customValues
|
||||
}
|
||||
|
||||
fun tryAutoFixValue(feeSelectorState: FeeSelectorUM.Content) = when (feeSelectorState.fees) {
|
||||
is TransactionFee.Choosable -> feeSelectorState.fees.minimum
|
||||
is TransactionFee.Single -> feeSelectorState.fees.normal
|
||||
}.let {
|
||||
when (it) {
|
||||
is Fee.Kaspa -> kaspaCustomFeeConverter.tryAutoFixValue(
|
||||
minimumFee = it,
|
||||
customValues = feeSelectorState.customValues,
|
||||
)
|
||||
else -> feeSelectorState.customValues
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.features.send.v2.feeselector.model.transformers
|
||||
|
||||
import com.tangem.domain.transaction.error.GetFeeError
|
||||
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
|
||||
internal class FeeSelectorErrorTransformer(private val error: GetFeeError) : Transformer<FeeSelectorUM> {
|
||||
|
||||
override fun transform(prevState: FeeSelectorUM): FeeSelectorUM {
|
||||
return FeeSelectorUM.Error(error = error)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
package com.tangem.features.send.v2.feeselector.model.transformers
|
||||
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.features.send.v2.api.entity.FeeFiatRateUM
|
||||
import com.tangem.features.send.v2.api.entity.FeeItem
|
||||
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
|
||||
import com.tangem.features.send.v2.api.params.FeeSelectorParams
|
||||
import com.tangem.features.send.v2.feeselector.model.FeeSelectorIntents
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class FeeSelectorLoadedTransformer(
|
||||
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
private val appCurrency: AppCurrency,
|
||||
private val fees: TransactionFee,
|
||||
private val suggestedFeeState: FeeSelectorParams.SuggestedFeeState,
|
||||
private val isFeeApproximate: Boolean,
|
||||
private val feeSelectorIntents: FeeSelectorIntents,
|
||||
) : Transformer<FeeSelectorUM> {
|
||||
|
||||
private val feeItemsConverter = FeeItemConverter(
|
||||
suggestedFeeState = suggestedFeeState,
|
||||
normalFee = fees.normal,
|
||||
feeSelectorIntents = feeSelectorIntents,
|
||||
appCurrency = appCurrency,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
)
|
||||
|
||||
override fun transform(prevState: FeeSelectorUM): FeeSelectorUM {
|
||||
val feeItems: ImmutableList<FeeItem> = feeItemsConverter.convert(fees)
|
||||
|
||||
val selectedFee = when (prevState) {
|
||||
is FeeSelectorUM.Content -> feeItems.first { it.isSame(prevState.selectedFeeItem) }
|
||||
is FeeSelectorUM.Error,
|
||||
FeeSelectorUM.Loading,
|
||||
-> feeItems.find { it is FeeItem.Suggested } ?: feeItems.first { it is FeeItem.Market }
|
||||
}
|
||||
|
||||
return FeeSelectorUM.Content(
|
||||
feeItems = feeItems,
|
||||
selectedFeeItem = selectedFee,
|
||||
isFeeApproximate = isFeeApproximate,
|
||||
feeFiatRateUM = cryptoCurrencyStatus.value.fiatRate?.let { rate ->
|
||||
FeeFiatRateUM(
|
||||
rate = rate,
|
||||
appCurrency = appCurrency,
|
||||
)
|
||||
},
|
||||
displayNonceInput = false,
|
||||
nonce = null,
|
||||
onNonceChange = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.features.feeselector.impl.ui
|
||||
package com.tangem.features.send.v2.feeselector.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.annotation.DrawableRes
|
||||
|
|
@ -15,12 +15,12 @@ import androidx.compose.material3.Icon
|
|||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Devices
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
|
|
@ -30,6 +30,7 @@ import androidx.compose.ui.unit.dp
|
|||
import androidx.compose.ui.util.fastForEachIndexed
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.common.ui.amountScreen.utils.getFiatReference
|
||||
import com.tangem.core.ui.components.PrimaryButton
|
||||
import com.tangem.core.ui.components.atoms.text.EllipsisText
|
||||
|
|
@ -46,18 +47,27 @@ import com.tangem.core.ui.format.bigdecimal.format
|
|||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.features.feeselector.api.entity.CustomFeeFieldUM
|
||||
import com.tangem.features.feeselector.impl.R
|
||||
import com.tangem.features.feeselector.impl.entity.FeeFiatRateDataHolder
|
||||
import com.tangem.features.feeselector.impl.entity.FeeItem
|
||||
import com.tangem.features.feeselector.impl.entity.FeeSelectorUM
|
||||
import com.tangem.features.send.v2.api.entity.CustomFeeFieldUM
|
||||
import com.tangem.features.send.v2.api.entity.FeeFiatRateUM
|
||||
import com.tangem.features.send.v2.api.entity.FeeItem
|
||||
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
|
||||
import com.tangem.features.send.v2.feeselector.model.FeeSelectorIntents
|
||||
import com.tangem.features.send.v2.feeselector.model.StubFeeSelectorIntents
|
||||
import com.tangem.features.send.v2.impl.R
|
||||
import com.tangem.utils.StringsSigns
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import java.math.BigDecimal
|
||||
import java.math.BigInteger
|
||||
|
||||
@Composable
|
||||
internal fun FeeSelectorModalBottomSheet(state: FeeSelectorUM.Content, onDismiss: () -> Unit) {
|
||||
internal fun FeeSelectorModalBottomSheet(
|
||||
state: FeeSelectorUM,
|
||||
feeSelectorIntents: FeeSelectorIntents,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
if (state !is FeeSelectorUM.Content) return
|
||||
|
||||
TangemModalBottomSheetWithFooter<TangemBottomSheetConfigContent.Empty>(
|
||||
config = TangemBottomSheetConfig(
|
||||
isShown = true,
|
||||
|
|
@ -73,7 +83,11 @@ internal fun FeeSelectorModalBottomSheet(state: FeeSelectorUM.Content, onDismiss
|
|||
)
|
||||
},
|
||||
content = {
|
||||
FeeSelectorItems(state = state, modifier = Modifier.padding(vertical = 4.dp, horizontal = 16.dp))
|
||||
FeeSelectorItems(
|
||||
state = state,
|
||||
feeSelectorIntents = feeSelectorIntents,
|
||||
modifier = Modifier.padding(vertical = 4.dp, horizontal = 16.dp),
|
||||
)
|
||||
},
|
||||
footer = {
|
||||
PrimaryButton(
|
||||
|
|
@ -81,8 +95,7 @@ internal fun FeeSelectorModalBottomSheet(state: FeeSelectorUM.Content, onDismiss
|
|||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
text = stringResourceSafe(R.string.common_done),
|
||||
onClick = onDismiss,
|
||||
enabled = state.isDoneEnabled,
|
||||
onClick = feeSelectorIntents::onDoneClick,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
|
@ -90,10 +103,15 @@ internal fun FeeSelectorModalBottomSheet(state: FeeSelectorUM.Content, onDismiss
|
|||
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
@Composable
|
||||
private fun FeeSelectorItems(state: FeeSelectorUM.Content, modifier: Modifier = Modifier) {
|
||||
private fun FeeSelectorItems(
|
||||
state: FeeSelectorUM.Content,
|
||||
feeSelectorIntents: FeeSelectorIntents,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
val feeFiatRateUM = state.feeFiatRateUM
|
||||
state.feeItems.fastForEachIndexed { index, item ->
|
||||
val isSelected = item == state.selectedFeeItem
|
||||
val isSelected = item.isSame(state.selectedFeeItem)
|
||||
val lastItem = index == state.feeItems.size - 1
|
||||
val iconTint by animateColorAsState(
|
||||
targetValue = if (isSelected) TangemTheme.colors.icon.accent else TangemTheme.colors.text.tertiary,
|
||||
|
|
@ -107,7 +125,6 @@ private fun FeeSelectorItems(state: FeeSelectorUM.Content, modifier: Modifier =
|
|||
},
|
||||
label = "Fee selector icon background change",
|
||||
)
|
||||
val onSelect by rememberUpdatedState(item.onSelect)
|
||||
val itemModifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(TangemTheme.colors.background.primary)
|
||||
|
|
@ -126,7 +143,7 @@ private fun FeeSelectorItems(state: FeeSelectorUM.Content, modifier: Modifier =
|
|||
Modifier
|
||||
},
|
||||
)
|
||||
.clickableSingle(onClick = onSelect)
|
||||
.clickableSingle(onClick = { feeSelectorIntents.onFeeItemSelected(item) })
|
||||
when (item) {
|
||||
is FeeItem.Suggested -> RegularFeeItemContent(
|
||||
modifier = itemModifier,
|
||||
|
|
@ -135,23 +152,23 @@ private fun FeeSelectorItems(state: FeeSelectorUM.Content, modifier: Modifier =
|
|||
iconBackgroundColor = iconBackgroundColor,
|
||||
iconTint = iconTint,
|
||||
preDot = stringReference(
|
||||
item.amount.value.format {
|
||||
item.fee.amount.value.format {
|
||||
crypto(
|
||||
symbol = item.amount.currencySymbol,
|
||||
decimals = item.amount.decimals,
|
||||
symbol = item.fee.amount.currencySymbol,
|
||||
decimals = item.fee.amount.decimals,
|
||||
).fee(canBeLower = state.isFeeApproximate)
|
||||
},
|
||||
),
|
||||
postDot = if (state.feeFiatRateDataHolder != null) {
|
||||
postDot = if (feeFiatRateUM != null) {
|
||||
getFiatReference(
|
||||
value = item.amount.value,
|
||||
rate = state.feeFiatRateDataHolder.rate,
|
||||
appCurrency = state.feeFiatRateDataHolder.appCurrency,
|
||||
value = item.fee.amount.value,
|
||||
rate = feeFiatRateUM.rate,
|
||||
appCurrency = feeFiatRateUM.appCurrency,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
},
|
||||
ellipsizeOffset = item.amount.currencySymbol.length,
|
||||
ellipsizeOffset = item.fee.amount.currencySymbol.length,
|
||||
showDivider = !isSelected && !lastItem,
|
||||
)
|
||||
is FeeItem.Slow -> RegularFeeItemContent(
|
||||
|
|
@ -161,23 +178,23 @@ private fun FeeSelectorItems(state: FeeSelectorUM.Content, modifier: Modifier =
|
|||
iconBackgroundColor = iconBackgroundColor,
|
||||
iconTint = iconTint,
|
||||
preDot = stringReference(
|
||||
item.amount.value.format {
|
||||
item.fee.amount.value.format {
|
||||
crypto(
|
||||
symbol = item.amount.currencySymbol,
|
||||
decimals = item.amount.decimals,
|
||||
symbol = item.fee.amount.currencySymbol,
|
||||
decimals = item.fee.amount.decimals,
|
||||
).fee(canBeLower = state.isFeeApproximate)
|
||||
},
|
||||
),
|
||||
postDot = if (state.feeFiatRateDataHolder != null) {
|
||||
postDot = if (feeFiatRateUM != null) {
|
||||
getFiatReference(
|
||||
value = item.amount.value,
|
||||
rate = state.feeFiatRateDataHolder.rate,
|
||||
appCurrency = state.feeFiatRateDataHolder.appCurrency,
|
||||
value = item.fee.amount.value,
|
||||
rate = feeFiatRateUM.rate,
|
||||
appCurrency = feeFiatRateUM.appCurrency,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
},
|
||||
ellipsizeOffset = item.amount.currencySymbol.length,
|
||||
ellipsizeOffset = item.fee.amount.currencySymbol.length,
|
||||
showDivider = !isSelected && !lastItem,
|
||||
)
|
||||
is FeeItem.Market -> RegularFeeItemContent(
|
||||
|
|
@ -187,23 +204,23 @@ private fun FeeSelectorItems(state: FeeSelectorUM.Content, modifier: Modifier =
|
|||
iconBackgroundColor = iconBackgroundColor,
|
||||
iconTint = iconTint,
|
||||
preDot = stringReference(
|
||||
item.amount.value.format {
|
||||
item.fee.amount.value.format {
|
||||
crypto(
|
||||
symbol = item.amount.currencySymbol,
|
||||
decimals = item.amount.decimals,
|
||||
symbol = item.fee.amount.currencySymbol,
|
||||
decimals = item.fee.amount.decimals,
|
||||
).fee(canBeLower = state.isFeeApproximate)
|
||||
},
|
||||
),
|
||||
postDot = if (state.feeFiatRateDataHolder != null) {
|
||||
postDot = if (feeFiatRateUM != null) {
|
||||
getFiatReference(
|
||||
value = item.amount.value,
|
||||
rate = state.feeFiatRateDataHolder.rate,
|
||||
appCurrency = state.feeFiatRateDataHolder.appCurrency,
|
||||
value = item.fee.amount.value,
|
||||
rate = feeFiatRateUM.rate,
|
||||
appCurrency = feeFiatRateUM.appCurrency,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
},
|
||||
ellipsizeOffset = item.amount.currencySymbol.length,
|
||||
ellipsizeOffset = item.fee.amount.currencySymbol.length,
|
||||
showDivider = !isSelected && !lastItem,
|
||||
)
|
||||
is FeeItem.Fast -> RegularFeeItemContent(
|
||||
|
|
@ -213,23 +230,23 @@ private fun FeeSelectorItems(state: FeeSelectorUM.Content, modifier: Modifier =
|
|||
iconBackgroundColor = iconBackgroundColor,
|
||||
iconTint = iconTint,
|
||||
preDot = stringReference(
|
||||
item.amount.value.format {
|
||||
item.fee.amount.value.format {
|
||||
crypto(
|
||||
symbol = item.amount.currencySymbol,
|
||||
decimals = item.amount.decimals,
|
||||
symbol = item.fee.amount.currencySymbol,
|
||||
decimals = item.fee.amount.decimals,
|
||||
).fee(canBeLower = state.isFeeApproximate)
|
||||
},
|
||||
),
|
||||
postDot = if (state.feeFiatRateDataHolder != null) {
|
||||
postDot = if (feeFiatRateUM != null) {
|
||||
getFiatReference(
|
||||
value = item.amount.value,
|
||||
rate = state.feeFiatRateDataHolder.rate,
|
||||
appCurrency = state.feeFiatRateDataHolder.appCurrency,
|
||||
value = item.fee.amount.value,
|
||||
rate = feeFiatRateUM.rate,
|
||||
appCurrency = feeFiatRateUM.appCurrency,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
},
|
||||
ellipsizeOffset = item.amount.currencySymbol.length,
|
||||
ellipsizeOffset = item.fee.amount.currencySymbol.length,
|
||||
showDivider = !isSelected && !lastItem,
|
||||
)
|
||||
is FeeItem.Custom -> CustomFeeBlock(
|
||||
|
|
@ -238,18 +255,25 @@ private fun FeeSelectorItems(state: FeeSelectorUM.Content, modifier: Modifier =
|
|||
isSelected = isSelected,
|
||||
iconBackgroundColor = iconBackgroundColor,
|
||||
iconTint = iconTint,
|
||||
displayNonceInput = state.displayNonceInput,
|
||||
nonce = state.nonce,
|
||||
onNonceChange = state.onNonceChange,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
private fun CustomFeeBlock(
|
||||
customFee: FeeItem.Custom,
|
||||
isSelected: Boolean,
|
||||
iconBackgroundColor: Color,
|
||||
iconTint: Color,
|
||||
displayNonceInput: Boolean,
|
||||
nonce: BigInteger?,
|
||||
onNonceChange: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
|
|
@ -279,7 +303,13 @@ private fun CustomFeeBlock(
|
|||
enter = expandVertically().plus(fadeIn()),
|
||||
exit = shrinkVertically().plus(fadeOut()),
|
||||
) {
|
||||
ExpandedCustomFeeItems(customFeeFields = customFee.customValues, onValueChange = { _, _ -> })
|
||||
ExpandedCustomFeeItems(
|
||||
customFeeFields = customFee.customValues,
|
||||
onValueChange = { _, _ -> },
|
||||
displayNonceInput = displayNonceInput,
|
||||
nonce = nonce,
|
||||
onNonceChange = onNonceChange,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -288,11 +318,14 @@ private fun CustomFeeBlock(
|
|||
private fun ExpandedCustomFeeItems(
|
||||
customFeeFields: ImmutableList<CustomFeeFieldUM>,
|
||||
onValueChange: (Int, String) -> Unit,
|
||||
displayNonceInput: Boolean,
|
||||
nonce: BigInteger?,
|
||||
onNonceChange: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
customFeeFields.fastForEachIndexed { index, field ->
|
||||
val showDivider = index != customFeeFields.size - 1
|
||||
val showDivider = index != customFeeFields.size - 1 || displayNonceInput
|
||||
if (field.label != null) {
|
||||
InputRowEnterInfoAmountV2(
|
||||
text = field.value,
|
||||
|
|
@ -321,6 +354,21 @@ private fun ExpandedCustomFeeItems(
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (displayNonceInput) {
|
||||
// TODO implement v2 input without binding to amount
|
||||
InputRowEnterInfoAmountV2(
|
||||
text = nonce?.toString() ?: "",
|
||||
decimals = 0,
|
||||
title = resourceReference(R.string.send_nonce),
|
||||
titleColor = TangemTheme.colors.text.tertiary,
|
||||
symbol = null,
|
||||
onValueChange = onNonceChange,
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||
keyboardActions = KeyboardActions(),
|
||||
showDivider = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -426,85 +474,77 @@ private fun FeeSelectorBS_Preview(
|
|||
state: FeeSelectorUM.Content,
|
||||
) {
|
||||
TangemThemePreview {
|
||||
FeeSelectorModalBottomSheet(onDismiss = {}, state = state)
|
||||
FeeSelectorModalBottomSheet(onDismiss = {}, state = state, feeSelectorIntents = StubFeeSelectorIntents())
|
||||
}
|
||||
}
|
||||
|
||||
private class FeeSelectorUMContentProvider : CollectionPreviewParameterProvider<FeeSelectorUM.Content>(
|
||||
collection = listOf(
|
||||
FeeSelectorUM.Content(
|
||||
isDoneEnabled = false,
|
||||
feeItems = persistentListOf(
|
||||
FeeItem.Suggested(
|
||||
title = stringReference("Suggested by Tangem"),
|
||||
amount = Amount(value = BigDecimal("0.1"), blockchain = Blockchain.Ethereum),
|
||||
onSelect = onSelectStub,
|
||||
fee = Fee.Common(Amount(value = BigDecimal("0.1"), blockchain = Blockchain.Ethereum)),
|
||||
),
|
||||
FeeItem.Slow(
|
||||
amount = Amount(value = BigDecimal("0.01"), blockchain = Blockchain.Ethereum),
|
||||
onSelect = onSelectStub,
|
||||
),
|
||||
FeeItem.Market(
|
||||
amount = Amount(value = BigDecimal("0.02"), blockchain = Blockchain.Ethereum),
|
||||
onSelect = onSelectStub,
|
||||
),
|
||||
FeeItem.Fast(
|
||||
amount = Amount(value = BigDecimal("0.3"), blockchain = Blockchain.Ethereum),
|
||||
onSelect = onSelectStub,
|
||||
),
|
||||
FeeItem.Custom(customValues = customFeeFields, onSelect = onSelectStub),
|
||||
FeeItem.Slow(fee = Fee.Common(Amount(value = BigDecimal("0.01"), blockchain = Blockchain.Ethereum))),
|
||||
FeeItem.Market(fee = Fee.Common(Amount(value = BigDecimal("0.02"), blockchain = Blockchain.Ethereum))),
|
||||
FeeItem.Fast(fee = Fee.Common(Amount(value = BigDecimal("0.03"), blockchain = Blockchain.Ethereum))),
|
||||
customFeeItem,
|
||||
),
|
||||
// selectedFeeItem = FeeItem.Market(
|
||||
// amount = Amount(value = BigDecimal("0.02"), blockchain = Blockchain.Ethereum),
|
||||
// onSelect = onSelectStub
|
||||
// ),
|
||||
selectedFeeItem = FeeItem.Custom(customValues = customFeeFields, onSelect = onSelectStub),
|
||||
selectedFeeItem = customFeeItem,
|
||||
isFeeApproximate = true,
|
||||
feeFiatRateDataHolder = FeeFiatRateDataHolder(
|
||||
feeFiatRateUM = FeeFiatRateUM(
|
||||
rate = BigDecimal.TEN,
|
||||
appCurrency = AppCurrency.Default,
|
||||
),
|
||||
displayNonceInput = true,
|
||||
onNonceChange = {},
|
||||
nonce = null,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
private val onSelectStub: () -> Unit = {}
|
||||
|
||||
private val customFeeFields = persistentListOf(
|
||||
CustomFeeFieldUM(
|
||||
value = "0.119806",
|
||||
onValueChange = {},
|
||||
keyboardOptions = KeyboardOptions(),
|
||||
keyboardActions = KeyboardActions(),
|
||||
symbol = "ETH",
|
||||
decimals = 8,
|
||||
title = resourceReference(R.string.send_max_fee),
|
||||
footer = resourceReference(R.string.send_custom_amount_fee_footer),
|
||||
label = stringReference("~ 0,03 \$"),
|
||||
isReadonly = false,
|
||||
),
|
||||
CustomFeeFieldUM(
|
||||
value = "40000",
|
||||
onValueChange = {},
|
||||
keyboardOptions = KeyboardOptions(),
|
||||
keyboardActions = KeyboardActions(),
|
||||
symbol = "GWEI",
|
||||
decimals = 8,
|
||||
title = resourceReference(R.string.send_gas_price),
|
||||
footer = resourceReference(R.string.send_gas_price_footer),
|
||||
label = null,
|
||||
isReadonly = false,
|
||||
),
|
||||
CustomFeeFieldUM(
|
||||
value = "31400",
|
||||
onValueChange = {},
|
||||
keyboardOptions = KeyboardOptions(),
|
||||
keyboardActions = KeyboardActions(),
|
||||
symbol = null,
|
||||
decimals = 8,
|
||||
title = resourceReference(R.string.send_gas_limit),
|
||||
footer = resourceReference(R.string.send_gas_limit_footer),
|
||||
label = null,
|
||||
isReadonly = false,
|
||||
private val customFeeItem = FeeItem.Custom(
|
||||
fee = Fee.Common(Amount(value = BigDecimal("0.05"), blockchain = Blockchain.Ethereum)),
|
||||
customValues = persistentListOf(
|
||||
CustomFeeFieldUM(
|
||||
value = "0.119806",
|
||||
onValueChange = {},
|
||||
keyboardOptions = KeyboardOptions(),
|
||||
keyboardActions = KeyboardActions(),
|
||||
symbol = "ETH",
|
||||
decimals = 8,
|
||||
title = resourceReference(R.string.send_max_fee),
|
||||
footer = resourceReference(R.string.send_custom_amount_fee_footer),
|
||||
label = stringReference("~ 0,03 \$"),
|
||||
isReadonly = false,
|
||||
),
|
||||
CustomFeeFieldUM(
|
||||
value = "40000",
|
||||
onValueChange = {},
|
||||
keyboardOptions = KeyboardOptions(),
|
||||
keyboardActions = KeyboardActions(),
|
||||
symbol = "GWEI",
|
||||
decimals = 8,
|
||||
title = resourceReference(R.string.send_gas_price),
|
||||
footer = resourceReference(R.string.send_gas_price_footer),
|
||||
label = null,
|
||||
isReadonly = false,
|
||||
),
|
||||
CustomFeeFieldUM(
|
||||
value = "31400",
|
||||
onValueChange = {},
|
||||
keyboardOptions = KeyboardOptions(),
|
||||
keyboardActions = KeyboardActions(),
|
||||
symbol = null,
|
||||
decimals = 8,
|
||||
title = resourceReference(R.string.send_gas_limit),
|
||||
footer = resourceReference(R.string.send_gas_limit_footer),
|
||||
label = null,
|
||||
isReadonly = false,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
@ -19,6 +19,7 @@ import com.tangem.core.decompose.model.getOrCreateModel
|
|||
import com.tangem.core.decompose.navigation.inner.InnerRouter
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.features.send.v2.api.SendComponent
|
||||
import com.tangem.features.send.v2.common.CommonSendRoute
|
||||
import com.tangem.features.send.v2.common.analytics.CommonSendAnalyticEvents
|
||||
|
|
@ -57,14 +58,19 @@ internal class DefaultSendComponent @AssistedInject constructor(
|
|||
popCallback = { onChildBack() },
|
||||
)
|
||||
|
||||
private val model: SendModel = getOrCreateModel(params = params, router = innerRouter)
|
||||
|
||||
private val initialRoute = if (params.amount == null) {
|
||||
CommonSendRoute.Destination(isEditMode = false)
|
||||
if (model.uiState.value.isRedesignEnabled) {
|
||||
CommonSendRoute.Amount(isEditMode = false)
|
||||
} else {
|
||||
CommonSendRoute.Destination(isEditMode = false)
|
||||
}
|
||||
} else {
|
||||
CommonSendRoute.Empty
|
||||
}
|
||||
private val currentRoute = MutableStateFlow(initialRoute)
|
||||
|
||||
private val model: SendModel = getOrCreateModel(params = params, router = innerRouter)
|
||||
private val currentRoute = MutableStateFlow(initialRoute)
|
||||
|
||||
private val childStack = childStack(
|
||||
key = "sendInnerStack",
|
||||
|
|
@ -155,9 +161,14 @@ internal class DefaultSendComponent @AssistedInject constructor(
|
|||
callback = model,
|
||||
onBackClick = ::onChildBack,
|
||||
onNextClick = {
|
||||
val nextRoute = if (model.uiState.value.isRedesignEnabled) {
|
||||
CommonSendRoute.Confirm
|
||||
} else {
|
||||
CommonSendRoute.Amount(isEditMode = false)
|
||||
}
|
||||
innerRouter.safeNextClick(
|
||||
currentRoute = route,
|
||||
nextRoute = CommonSendRoute.Amount(isEditMode = false),
|
||||
nextRoute = nextRoute,
|
||||
popBack = ::onChildBack,
|
||||
childStack = childStack,
|
||||
)
|
||||
|
|
@ -169,62 +180,68 @@ internal class DefaultSendComponent @AssistedInject constructor(
|
|||
factoryContext: AppComponentContext,
|
||||
route: CommonSendRoute,
|
||||
): ComposableContentComponent {
|
||||
val cryptoCurrencyStatus = model.cryptoCurrencyStatus
|
||||
return if (cryptoCurrencyStatus != null) {
|
||||
SendAmountComponent(
|
||||
appComponentContext = factoryContext,
|
||||
params = SendAmountComponentParams.AmountParams(
|
||||
state = model.uiState.value.amountUM,
|
||||
currentRoute = currentRoute.filterIsInstance<CommonSendRoute.Amount>(),
|
||||
isBalanceHidingFlow = model.isBalanceHiddenFlow,
|
||||
analyticsCategoryName = analyticCategoryName,
|
||||
userWallet = model.userWallet,
|
||||
appCurrency = model.appCurrency,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
callback = model,
|
||||
predefinedValues = model.predefinedValues,
|
||||
onBackClick = {
|
||||
if (route.isEditMode) {
|
||||
return SendAmountComponent(
|
||||
appComponentContext = factoryContext,
|
||||
params = SendAmountComponentParams.AmountParams(
|
||||
state = model.uiState.value.amountUM,
|
||||
currentRoute = currentRoute.filterIsInstance<CommonSendRoute.Amount>(),
|
||||
isBalanceHidingFlow = model.isBalanceHiddenFlow,
|
||||
analyticsCategoryName = analyticCategoryName,
|
||||
appCurrency = model.appCurrency,
|
||||
userWalletId = params.userWalletId,
|
||||
cryptoCurrency = params.currency,
|
||||
cryptoCurrencyStatusFlow = model.cryptoCurrencyStatusFlow,
|
||||
callback = model,
|
||||
predefinedValues = model.predefinedValues,
|
||||
onBackClick = {
|
||||
if (route.isEditMode) {
|
||||
onChildBack()
|
||||
} else {
|
||||
analyticsEventHandler.send(
|
||||
CommonSendAnalyticEvents.CloseButtonClicked(
|
||||
categoryName = analyticCategoryName,
|
||||
source = SendScreenSource.Amount,
|
||||
isFromSummary = false,
|
||||
isValid = model.uiState.value.amountUM.isPrimaryButtonEnabled,
|
||||
),
|
||||
)
|
||||
if (model.uiState.value.isRedesignEnabled) {
|
||||
onChildBack()
|
||||
} else {
|
||||
analyticsEventHandler.send(
|
||||
CommonSendAnalyticEvents.CloseButtonClicked(
|
||||
categoryName = analyticCategoryName,
|
||||
source = SendScreenSource.Amount,
|
||||
isFromSummary = false,
|
||||
isValid = model.uiState.value.amountUM.isPrimaryButtonEnabled,
|
||||
),
|
||||
)
|
||||
router.pop()
|
||||
}
|
||||
},
|
||||
onNextClick = {
|
||||
innerRouter.safeNextClick(
|
||||
currentRoute = route,
|
||||
nextRoute = CommonSendRoute.Confirm,
|
||||
popBack = ::onChildBack,
|
||||
childStack = childStack,
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
} else {
|
||||
model.showAlertError()
|
||||
getStubComponent()
|
||||
}
|
||||
}
|
||||
},
|
||||
onNextClick = {
|
||||
val nextRoute = if (model.uiState.value.isRedesignEnabled) {
|
||||
CommonSendRoute.Destination(isEditMode = false)
|
||||
} else {
|
||||
CommonSendRoute.Confirm
|
||||
}
|
||||
innerRouter.safeNextClick(
|
||||
currentRoute = route,
|
||||
nextRoute = nextRoute,
|
||||
popBack = ::onChildBack,
|
||||
childStack = childStack,
|
||||
)
|
||||
},
|
||||
isRedesignEnabled = model.uiState.value.isRedesignEnabled,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("ComplexCondition")
|
||||
private fun getFeeComponent(factoryContext: AppComponentContext): ComposableContentComponent {
|
||||
val state = model.uiState.value
|
||||
val sendAmount = (state.amountUM as? AmountState.Data)?.amountTextField?.cryptoAmount?.value
|
||||
val destinationAddress = (state.destinationUM as? DestinationUM.Content)?.addressTextField?.value
|
||||
val cryptoCurrencyStatus = model.cryptoCurrencyStatus
|
||||
val feeCryptoCurrencyStatus = model.feeCryptoCurrencyStatus
|
||||
val destinationAddress = (state.destinationUM as? DestinationUM.Content)?.addressTextField?.actualAddress
|
||||
val cryptoCurrencyStatus = model.cryptoCurrencyStatusFlow.value
|
||||
val feeCryptoCurrencyStatus = model.feeCryptoCurrencyStatusFlow.value
|
||||
|
||||
return if (sendAmount != null && destinationAddress != null &&
|
||||
feeCryptoCurrencyStatus != null && cryptoCurrencyStatus != null
|
||||
) {
|
||||
// TODO Apply new component [REDACTED_TASK_KEY]
|
||||
SendFeeComponent(
|
||||
appComponentContext = factoryContext,
|
||||
params = SendFeeComponentParams.FeeParams(
|
||||
|
|
@ -249,10 +266,12 @@ internal class DefaultSendComponent @AssistedInject constructor(
|
|||
}
|
||||
|
||||
private fun getConfirmComponent(factoryContext: AppComponentContext): ComposableContentComponent {
|
||||
val cryptoCurrencyStatus = model.cryptoCurrencyStatus
|
||||
val feeCryptoCurrencyStatus = model.feeCryptoCurrencyStatus
|
||||
val cryptoCurrencyStatus = model.cryptoCurrencyStatusFlow.value
|
||||
val feeCryptoCurrencyStatus = model.feeCryptoCurrencyStatusFlow.value
|
||||
|
||||
return if (cryptoCurrencyStatus != null && feeCryptoCurrencyStatus != null) {
|
||||
return if (cryptoCurrencyStatus.value != CryptoCurrencyStatus.Loading &&
|
||||
feeCryptoCurrencyStatus.value != CryptoCurrencyStatus.Loading
|
||||
) {
|
||||
SendConfirmComponent(
|
||||
appComponentContext = factoryContext,
|
||||
params = SendConfirmComponent.Params(
|
||||
|
|
@ -263,6 +282,8 @@ internal class DefaultSendComponent @AssistedInject constructor(
|
|||
analyticsCategoryName = analyticCategoryName,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
feeCryptoCurrencyStatus = feeCryptoCurrencyStatus,
|
||||
cryptoCurrencyStatusFlow = model.cryptoCurrencyStatusFlow,
|
||||
feeCryptoCurrencyStatusFlow = model.feeCryptoCurrencyStatusFlow,
|
||||
appCurrency = model.appCurrency,
|
||||
callback = model,
|
||||
predefinedValues = model.predefinedValues,
|
||||
|
|
|
|||
|
|
@ -2,8 +2,11 @@ package com.tangem.features.send.v2.send.analytics
|
|||
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.BLOCKCHAIN
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.FEE_TYPE
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.NONCE
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.TOKEN_PARAM
|
||||
import com.tangem.core.ui.extensions.capitalize
|
||||
import com.tangem.features.send.v2.common.analytics.CommonSendAnalyticEvents
|
||||
|
||||
/**
|
||||
|
|
@ -18,11 +21,15 @@ internal sealed class SendAnalyticEvents(
|
|||
data class TransactionScreenOpened(
|
||||
val token: String,
|
||||
val feeType: AnalyticsParam.FeeType,
|
||||
val blockchain: String,
|
||||
val nonceNotEmpty: Boolean,
|
||||
) : SendAnalyticEvents(
|
||||
event = "Transaction Sent Screen Opened",
|
||||
params = mapOf(
|
||||
TOKEN_PARAM to token,
|
||||
FEE_TYPE to feeType.value,
|
||||
BLOCKCHAIN to blockchain,
|
||||
NONCE to nonceNotEmpty.toString().capitalize(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -26,6 +26,8 @@ internal class SendAnalyticHelper @Inject constructor(
|
|||
SendAnalyticEvents.TransactionScreenOpened(
|
||||
token = cryptoCurrency.symbol,
|
||||
feeType = feeType,
|
||||
blockchain = cryptoCurrency.network.name,
|
||||
nonceNotEmpty = feeSelectorUM.nonce != null,
|
||||
),
|
||||
)
|
||||
analyticsEventHandler.send(
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ import com.tangem.domain.appcurrency.model.AppCurrency
|
|||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.transaction.error.GetFeeError
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.features.send.v2.api.SendNotificationsComponent
|
||||
import com.tangem.features.send.v2.api.SendNotificationsComponent.Params.NotificationData
|
||||
import com.tangem.features.send.v2.common.CommonSendRoute
|
||||
import com.tangem.features.send.v2.common.PredefinedValues
|
||||
import com.tangem.features.send.v2.common.ui.state.ConfirmUM
|
||||
|
|
@ -26,10 +28,10 @@ import com.tangem.features.send.v2.subcomponents.destination.SendDestinationBloc
|
|||
import com.tangem.features.send.v2.subcomponents.destination.SendDestinationComponentParams.DestinationBlockParams
|
||||
import com.tangem.features.send.v2.subcomponents.fee.SendFeeBlockComponent
|
||||
import com.tangem.features.send.v2.subcomponents.fee.SendFeeComponentParams
|
||||
import com.tangem.features.send.v2.subcomponents.notifications.NotificationsComponent
|
||||
import com.tangem.features.send.v2.subcomponents.notifications.model.NotificationData
|
||||
import com.tangem.features.send.v2.subcomponents.notifications.DefaultSendNotificationsComponent
|
||||
import com.tangem.utils.extensions.orZero
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
internal class SendConfirmComponent(
|
||||
appComponentContext: AppComponentContext,
|
||||
|
|
@ -61,10 +63,13 @@ internal class SendConfirmComponent(
|
|||
state = model.uiState.value.amountUM,
|
||||
analyticsCategoryName = params.analyticsCategoryName,
|
||||
userWallet = params.userWallet,
|
||||
cryptoCurrencyStatus = params.cryptoCurrencyStatus,
|
||||
appCurrency = params.appCurrency,
|
||||
blockClickEnableFlow = blockClickEnableFlow.asStateFlow(),
|
||||
predefinedValues = params.predefinedValues,
|
||||
isRedesignEnabled = model.uiState.value.isRedesignEnabled,
|
||||
userWalletId = params.userWallet.walletId,
|
||||
cryptoCurrency = params.cryptoCurrencyStatus.currency,
|
||||
cryptoCurrencyStatusFlow = params.cryptoCurrencyStatusFlow,
|
||||
),
|
||||
onResult = model::onAmountResult,
|
||||
onClick = model::showEditAmount,
|
||||
|
|
@ -88,9 +93,9 @@ internal class SendConfirmComponent(
|
|||
onClick = model::showEditFee,
|
||||
)
|
||||
|
||||
private val notificationsComponent = NotificationsComponent(
|
||||
private val notificationsComponent = DefaultSendNotificationsComponent(
|
||||
appComponentContext = child("sendConfirmNotifications"),
|
||||
params = NotificationsComponent.Params(
|
||||
params = SendNotificationsComponent.Params(
|
||||
analyticsCategoryName = params.analyticsCategoryName,
|
||||
userWalletId = params.userWallet.walletId,
|
||||
cryptoCurrencyStatus = params.cryptoCurrencyStatus,
|
||||
|
|
@ -143,6 +148,8 @@ internal class SendConfirmComponent(
|
|||
val userWallet: UserWallet,
|
||||
val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
val feeCryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
val cryptoCurrencyStatusFlow: StateFlow<CryptoCurrencyStatus>,
|
||||
val feeCryptoCurrencyStatusFlow: StateFlow<CryptoCurrencyStatus>,
|
||||
val appCurrency: AppCurrency,
|
||||
val callback: ModelCallback,
|
||||
val currentRoute: Flow<CommonSendRoute>,
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import com.tangem.domain.transaction.usecase.SendTransactionUseCase
|
|||
import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
|
||||
import com.tangem.domain.utils.convertToSdkAmount
|
||||
import com.tangem.domain.wallets.models.requireColdWallet
|
||||
import com.tangem.features.send.v2.api.SendNotificationsComponent.Params.NotificationData
|
||||
import com.tangem.features.send.v2.common.CommonSendRoute
|
||||
import com.tangem.features.send.v2.common.SendBalanceUpdater
|
||||
import com.tangem.features.send.v2.common.SendConfirmAlertFactory
|
||||
|
|
@ -55,7 +56,6 @@ import com.tangem.features.send.v2.subcomponents.fee.model.checkAndCalculateSubt
|
|||
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeSelectorUM
|
||||
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeUM
|
||||
import com.tangem.features.send.v2.subcomponents.notifications.NotificationsUpdateTrigger
|
||||
import com.tangem.features.send.v2.subcomponents.notifications.model.NotificationData
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.extensions.orZero
|
||||
import com.tangem.utils.extensions.stripZeroPlainString
|
||||
|
|
@ -122,7 +122,7 @@ internal class SendConfirmModel @Inject constructor(
|
|||
enteredMemo = destinationUM?.memoTextField?.value,
|
||||
reduceAmountBy = amountState?.reduceAmountBy.orZero(),
|
||||
isIgnoreReduce = amountState?.isIgnoreReduce == true,
|
||||
enteredDestination = destinationUM?.addressTextField?.value,
|
||||
enteredDestination = destinationUM?.addressTextField?.actualAddress,
|
||||
fee = feeSelectorUM?.selectedFee,
|
||||
feeError = (feeUM?.feeSelectorUM as? FeeSelectorUM.Error)?.error,
|
||||
)
|
||||
|
|
@ -316,10 +316,11 @@ internal class SendConfirmModel @Inject constructor(
|
|||
|
||||
private fun verifyAndSendTransaction() {
|
||||
val amountValue = amountState?.amountTextField?.cryptoAmount?.value ?: return
|
||||
val destination = destinationUM?.addressTextField?.value ?: return
|
||||
val destination = destinationUM?.addressTextField?.actualAddress ?: return
|
||||
val memo = destinationUM?.memoTextField?.value
|
||||
val fee = feeSelectorUM?.selectedFee
|
||||
val feeValue = fee?.amount?.value ?: return
|
||||
val nonce = feeSelectorUM?.nonce
|
||||
|
||||
val receivingAmount = checkAndCalculateSubtractedAmount(
|
||||
isAmountSubtractAvailable = isAmountSubtractAvailable,
|
||||
|
|
@ -334,6 +335,7 @@ internal class SendConfirmModel @Inject constructor(
|
|||
amount = receivingAmount.convertToSdkAmount(cryptoCurrency),
|
||||
fee = fee,
|
||||
memo = memo,
|
||||
nonce = nonce,
|
||||
destination = destination,
|
||||
userWalletId = userWallet.walletId,
|
||||
network = cryptoCurrency.network,
|
||||
|
|
@ -454,6 +456,7 @@ internal class SendConfirmModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
private fun configConfirmNavigation() {
|
||||
combine(
|
||||
flow = uiState,
|
||||
|
|
@ -470,7 +473,11 @@ internal class SendConfirmModel @Inject constructor(
|
|||
id = R.string.send_summary_title,
|
||||
formatArgs = wrappedList(params.cryptoCurrencyStatus.currency.name),
|
||||
),
|
||||
subtitle = amountUM?.title,
|
||||
subtitle = if (uiState.value.isRedesignEnabled) {
|
||||
null
|
||||
} else {
|
||||
amountUM?.title
|
||||
},
|
||||
backIconRes = R.drawable.ic_close_24,
|
||||
backIconClick = {
|
||||
analyticsEventHandler.send(
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ import androidx.compose.foundation.layout.Column
|
|||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
|
|
@ -27,7 +28,7 @@ import com.tangem.features.send.v2.subcomponents.amount.SendAmountBlockComponent
|
|||
import com.tangem.features.send.v2.subcomponents.destination.SendDestinationBlockComponent
|
||||
import com.tangem.features.send.v2.subcomponents.fee.SendFeeBlockComponent
|
||||
import com.tangem.features.send.v2.subcomponents.notifications
|
||||
import com.tangem.features.send.v2.subcomponents.notifications.NotificationsComponent
|
||||
import com.tangem.features.send.v2.subcomponents.notifications.DefaultSendNotificationsComponent
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
private const val BLOCKS_KEY = "BLOCKS_KEY"
|
||||
|
|
@ -39,7 +40,7 @@ internal fun SendConfirmContent(
|
|||
destinationBlockComponent: SendDestinationBlockComponent,
|
||||
amountBlockComponent: SendAmountBlockComponent,
|
||||
feeBlockComponent: SendFeeBlockComponent,
|
||||
notificationsComponent: NotificationsComponent,
|
||||
notificationsComponent: DefaultSendNotificationsComponent,
|
||||
notificationsUM: ImmutableList<NotificationUM>,
|
||||
) {
|
||||
val confirmUM = sendUM.confirmUM as? ConfirmUM.Content
|
||||
|
|
@ -98,8 +99,13 @@ private fun LazyListScope.blocks(
|
|||
modifier = Modifier.padding(vertical = 12.dp),
|
||||
)
|
||||
}
|
||||
destinationBlockComponent.Content(modifier = Modifier)
|
||||
amountBlockComponent.Content(modifier = Modifier)
|
||||
if (uiState.isRedesignEnabled) {
|
||||
amountBlockComponent.Content(modifier = Modifier)
|
||||
destinationBlockComponent.Content(modifier = Modifier)
|
||||
} else {
|
||||
destinationBlockComponent.Content(modifier = Modifier)
|
||||
amountBlockComponent.Content(modifier = Modifier)
|
||||
}
|
||||
feeBlockComponent.Content(modifier = Modifier)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ import com.tangem.domain.wallets.models.isMultiCurrency
|
|||
import com.tangem.domain.wallets.models.requireColdWallet
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.features.send.v2.api.SendComponent
|
||||
import com.tangem.features.send.v2.api.SendFeatureToggles
|
||||
import com.tangem.features.send.v2.common.CommonSendRoute
|
||||
import com.tangem.features.send.v2.common.PredefinedValues
|
||||
import com.tangem.features.send.v2.common.SendConfirmAlertFactory
|
||||
|
|
@ -87,10 +88,10 @@ internal class SendModel @Inject constructor(
|
|||
private val createTransferTransactionUseCase: CreateTransferTransactionUseCase,
|
||||
private val getFeeUseCase: GetFeeUseCase,
|
||||
private val sendAmountUpdateQRTrigger: SendAmountUpdateQRTrigger,
|
||||
private val sendFeatureToggles: SendFeatureToggles,
|
||||
) : Model(), SendComponentCallback {
|
||||
|
||||
private val params: SendComponent.Params = paramsContainer.require()
|
||||
private val userWalletId = params.userWalletId
|
||||
private val cryptoCurrency = params.currency
|
||||
|
||||
private val _uiState = MutableStateFlow(initialState())
|
||||
|
|
@ -99,9 +100,23 @@ internal class SendModel @Inject constructor(
|
|||
private val _isBalanceHiddenFlow = MutableStateFlow(false)
|
||||
val isBalanceHiddenFlow = _isBalanceHiddenFlow.asStateFlow()
|
||||
|
||||
private val _cryptoCurrencyStatusFlow = MutableStateFlow(
|
||||
CryptoCurrencyStatus(
|
||||
params.currency,
|
||||
value = CryptoCurrencyStatus.Loading,
|
||||
),
|
||||
)
|
||||
val cryptoCurrencyStatusFlow = _cryptoCurrencyStatusFlow.asStateFlow()
|
||||
|
||||
private val _feeCryptoCurrencyStatusFlow = MutableStateFlow(
|
||||
CryptoCurrencyStatus(
|
||||
params.currency,
|
||||
value = CryptoCurrencyStatus.Loading,
|
||||
),
|
||||
)
|
||||
val feeCryptoCurrencyStatusFlow = _feeCryptoCurrencyStatusFlow.asStateFlow()
|
||||
|
||||
var userWallet: UserWallet by Delegates.notNull()
|
||||
var cryptoCurrencyStatus: CryptoCurrencyStatus? = null
|
||||
var feeCryptoCurrencyStatus: CryptoCurrencyStatus? = null
|
||||
var appCurrency: AppCurrency = AppCurrency.Default
|
||||
var predefinedValues: PredefinedValues = PredefinedValues.Empty
|
||||
|
||||
|
|
@ -150,7 +165,7 @@ internal class SendModel @Inject constructor(
|
|||
} else {
|
||||
val destinationUM = uiState.value.destinationUM as? DestinationUM.Content ?: error("Invalid destination")
|
||||
val amountUM = uiState.value.amountUM as? AmountState.Data ?: error("Invalid amount")
|
||||
val enteredDestinationAddress = destinationUM.addressTextField.value
|
||||
val enteredDestinationAddress = destinationUM.addressTextField.actualAddress
|
||||
val enteredMemo = destinationUM.memoTextField?.value
|
||||
val enteredAmount = amountUM.amountTextField.cryptoAmount.value ?: error("Invalid amount")
|
||||
|
||||
|
|
@ -277,16 +292,16 @@ internal class SendModel @Inject constructor(
|
|||
): Flow<Either<CurrencyStatusError, CryptoCurrencyStatus>> {
|
||||
return when {
|
||||
isSingleWalletWithToken -> getSingleCryptoCurrencyStatusUseCase.invokeMultiWallet(
|
||||
userWalletId = userWalletId,
|
||||
userWalletId = params.userWalletId,
|
||||
currencyId = cryptoCurrency.id,
|
||||
isSingleWalletWithTokens = true,
|
||||
)
|
||||
isMultiCurrency -> getSingleCryptoCurrencyStatusUseCase.invokeMultiWallet(
|
||||
userWalletId = userWalletId,
|
||||
userWalletId = params.userWalletId,
|
||||
currencyId = cryptoCurrency.id,
|
||||
isSingleWalletWithTokens = false,
|
||||
)
|
||||
else -> getSingleCryptoCurrencyStatusUseCase.invokeSingleWallet(userWalletId = userWalletId)
|
||||
else -> getSingleCryptoCurrencyStatusUseCase.invokeSingleWallet(userWalletId = params.userWalletId)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -296,7 +311,7 @@ internal class SendModel @Inject constructor(
|
|||
): CryptoCurrencyStatus {
|
||||
return if (isMultiCurrency) {
|
||||
getFeePaidCryptoCurrencyStatusSyncUseCase(
|
||||
userWalletId = userWalletId,
|
||||
userWalletId = params.userWalletId,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
).getOrNull() ?: cryptoCurrencyStatus
|
||||
} else {
|
||||
|
|
@ -305,8 +320,8 @@ internal class SendModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun onDataLoaded(currencyStatus: CryptoCurrencyStatus, feeCurrencyStatus: CryptoCurrencyStatus) {
|
||||
cryptoCurrencyStatus = currencyStatus
|
||||
feeCryptoCurrencyStatus = feeCurrencyStatus
|
||||
_cryptoCurrencyStatusFlow.value = currencyStatus
|
||||
_feeCryptoCurrencyStatusFlow.value = feeCurrencyStatus
|
||||
|
||||
if (params.amount != null) {
|
||||
router.replaceAll(CommonSendRoute.Confirm)
|
||||
|
|
@ -358,12 +373,14 @@ internal class SendModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun initialState(): SendUM = SendUM(
|
||||
amountUM = AmountState.Empty(),
|
||||
amountUM = AmountState.Empty(isRedesignEnabled = sendFeatureToggles.isSendRedesignEnabled),
|
||||
destinationUM = SendDestinationInitialStateTransformer(
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
isRedesignEnabled = sendFeatureToggles.isSendRedesignEnabled,
|
||||
).transform(DestinationUM.Empty()),
|
||||
feeUM = FeeUM.Empty(),
|
||||
confirmUM = ConfirmUM.Empty,
|
||||
navigationUM = NavigationUM.Empty,
|
||||
isRedesignEnabled = sendFeatureToggles.isSendRedesignEnabled,
|
||||
)
|
||||
}
|
||||
|
|
@ -12,4 +12,5 @@ internal data class SendUM(
|
|||
val feeUM: FeeUM,
|
||||
val confirmUM: ConfirmUM,
|
||||
val navigationUM: NavigationUM,
|
||||
val isRedesignEnabled: Boolean,
|
||||
)
|
||||
|
|
@ -157,7 +157,7 @@ internal class DefaultNFTSendComponent @AssistedInject constructor(
|
|||
|
||||
private fun getFeeComponent(factoryContext: AppComponentContext): ComposableContentComponent {
|
||||
val state = model.uiState.value
|
||||
val destinationAddress = (state.destinationUM as? DestinationUM.Content)?.addressTextField?.value
|
||||
val destinationAddress = (state.destinationUM as? DestinationUM.Content)?.addressTextField?.actualAddress
|
||||
return if (destinationAddress != null) {
|
||||
SendFeeComponent(
|
||||
appComponentContext = factoryContext,
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
|||
import com.tangem.domain.transaction.error.GetFeeError
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.features.nft.component.NFTDetailsBlockComponent
|
||||
import com.tangem.features.send.v2.api.SendNotificationsComponent
|
||||
import com.tangem.features.send.v2.common.CommonSendRoute
|
||||
import com.tangem.features.send.v2.common.PredefinedValues
|
||||
import com.tangem.features.send.v2.common.ui.state.ConfirmUM
|
||||
|
|
@ -26,8 +27,7 @@ import com.tangem.features.send.v2.subcomponents.destination.SendDestinationBloc
|
|||
import com.tangem.features.send.v2.subcomponents.destination.SendDestinationComponentParams.DestinationBlockParams
|
||||
import com.tangem.features.send.v2.subcomponents.fee.SendFeeBlockComponent
|
||||
import com.tangem.features.send.v2.subcomponents.fee.SendFeeComponentParams
|
||||
import com.tangem.features.send.v2.subcomponents.notifications.NotificationsComponent
|
||||
import com.tangem.features.send.v2.subcomponents.notifications.model.NotificationData
|
||||
import com.tangem.features.send.v2.subcomponents.notifications.DefaultSendNotificationsComponent
|
||||
import kotlinx.coroutines.flow.*
|
||||
import java.math.BigDecimal
|
||||
|
||||
|
|
@ -83,15 +83,15 @@ internal class NFTSendConfirmComponent(
|
|||
),
|
||||
)
|
||||
|
||||
private val notificationsComponent = NotificationsComponent(
|
||||
private val notificationsComponent = DefaultSendNotificationsComponent(
|
||||
appComponentContext = child("NFTSendConfirmNotifications"),
|
||||
params = NotificationsComponent.Params(
|
||||
params = SendNotificationsComponent.Params(
|
||||
analyticsCategoryName = params.analyticsCategoryName,
|
||||
userWalletId = params.userWallet.walletId,
|
||||
cryptoCurrencyStatus = params.cryptoCurrencyStatus,
|
||||
feeCryptoCurrencyStatus = params.feeCryptoCurrencyStatus,
|
||||
appCurrency = params.appCurrency,
|
||||
notificationData = NotificationData(
|
||||
notificationData = SendNotificationsComponent.Params.NotificationData(
|
||||
destinationAddress = model.confirmData.enteredDestination.orEmpty(),
|
||||
memo = model.confirmData.enteredMemo,
|
||||
amountValue = BigDecimal.ZERO,
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import com.tangem.domain.transaction.usecase.SendTransactionUseCase
|
|||
import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
|
||||
import com.tangem.domain.wallets.models.requireColdWallet
|
||||
import com.tangem.features.nft.entity.NFTSendSuccessTrigger
|
||||
import com.tangem.features.send.v2.api.SendNotificationsComponent.Params.NotificationData
|
||||
import com.tangem.features.send.v2.common.CommonSendRoute
|
||||
import com.tangem.features.send.v2.common.SendBalanceUpdater
|
||||
import com.tangem.features.send.v2.common.SendConfirmAlertFactory
|
||||
|
|
@ -47,7 +48,6 @@ import com.tangem.features.send.v2.subcomponents.fee.SendFeeCheckReloadTrigger
|
|||
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeSelectorUM
|
||||
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeUM
|
||||
import com.tangem.features.send.v2.subcomponents.notifications.NotificationsUpdateTrigger
|
||||
import com.tangem.features.send.v2.subcomponents.notifications.model.NotificationData
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.extensions.stripZeroPlainString
|
||||
import com.tangem.utils.transformer.update
|
||||
|
|
@ -108,7 +108,7 @@ internal class NFTSendConfirmModel @Inject constructor(
|
|||
|
||||
val confirmData: ConfirmData
|
||||
get() = ConfirmData(
|
||||
enteredDestination = destinationUM?.addressTextField?.value,
|
||||
enteredDestination = destinationUM?.addressTextField?.actualAddress,
|
||||
enteredMemo = destinationUM?.memoTextField?.value,
|
||||
fee = feeSelectorUM?.selectedFee,
|
||||
feeError = (feeUM?.feeSelectorUM as? FeeSelectorUM.Error)?.error,
|
||||
|
|
@ -258,7 +258,7 @@ internal class NFTSendConfirmModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun verifyAndSendTransaction() {
|
||||
val destination = destinationUM?.addressTextField?.value ?: return
|
||||
val destination = destinationUM?.addressTextField?.actualAddress ?: return
|
||||
val memo = destinationUM?.memoTextField?.value
|
||||
val fee = feeSelectorUM?.selectedFee ?: return
|
||||
val ownerAddress = cryptoCurrencyStatus.value.networkAddress?.defaultAddress?.value ?: return
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ import com.tangem.features.send.v2.sendnft.ui.state.NFTSendUM
|
|||
import com.tangem.features.send.v2.subcomponents.destination.SendDestinationBlockComponent
|
||||
import com.tangem.features.send.v2.subcomponents.fee.SendFeeBlockComponent
|
||||
import com.tangem.features.send.v2.subcomponents.notifications
|
||||
import com.tangem.features.send.v2.subcomponents.notifications.NotificationsComponent
|
||||
import com.tangem.features.send.v2.subcomponents.notifications.DefaultSendNotificationsComponent
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
private const val BLOCKS_KEY = "BLOCKS_KEY"
|
||||
|
|
@ -40,7 +40,7 @@ internal fun NFTSendConfirmContent(
|
|||
destinationBlockComponent: SendDestinationBlockComponent,
|
||||
nftDetailsBlockComponent: NFTDetailsBlockComponent,
|
||||
feeBlockComponent: SendFeeBlockComponent,
|
||||
notificationsComponent: NotificationsComponent,
|
||||
notificationsComponent: DefaultSendNotificationsComponent,
|
||||
notificationsUM: ImmutableList<NotificationUM>,
|
||||
) {
|
||||
val confirmUM = nftSendUM.confirmUM as? ConfirmUM.Content
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ internal class NFTSendModel @Inject constructor(
|
|||
val destinationUM = uiState.value.destinationUM as? DestinationUM.Content ?: error("Invalid destination")
|
||||
val ownerAddress = cryptoCurrencyStatus.value.networkAddress?.defaultAddress?.value
|
||||
?: error("Invalid owner address")
|
||||
val enteredDestinationAddress = destinationUM.addressTextField.value
|
||||
val enteredDestinationAddress = destinationUM.addressTextField.actualAddress
|
||||
val enteredMemo = destinationUM.memoTextField?.value
|
||||
val nftAsset = NFTSdkAssetConverter.convertBack(params.nftAsset).second
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
import com.tangem.common.ui.amountScreen.ui.AmountBlock
|
||||
import com.tangem.common.ui.amountScreen.ui.AmountBlockV2
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
|
|
@ -37,11 +38,21 @@ internal class SendAmountBlockComponent(
|
|||
val state by model.uiState.collectAsStateWithLifecycle()
|
||||
val isClickEnabled by params.blockClickEnableFlow.collectAsStateWithLifecycle()
|
||||
|
||||
AmountBlock(
|
||||
amountState = state,
|
||||
isClickDisabled = !isClickEnabled,
|
||||
isEditingDisabled = params.predefinedValues is PredefinedValues.Content.Deeplink,
|
||||
onClick = onClick,
|
||||
)
|
||||
if (params.isRedesignEnabled) {
|
||||
AmountBlockV2(
|
||||
amountState = state,
|
||||
isClickDisabled = !isClickEnabled,
|
||||
isEditingDisabled = params.predefinedValues is PredefinedValues.Content.Deeplink,
|
||||
onClick = onClick,
|
||||
modifier = modifier,
|
||||
)
|
||||
} else {
|
||||
AmountBlock(
|
||||
amountState = state,
|
||||
isClickDisabled = !isClickEnabled,
|
||||
isEditingDisabled = params.predefinedValues is PredefinedValues.Content.Deeplink,
|
||||
onClick = onClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue