Updated on 2026-08-14
This commit is contained in:
commit
3fba2ae059
872 changed files with 17188 additions and 8108 deletions
1
features/details/api/.gitignore
vendored
Normal file
1
features/details/api/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
21
features/details/api/build.gradle.kts
Normal file
21
features/details/api/build.gradle.kts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
plugins {
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.tangem.features.details.api"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
/* Project - Domain */
|
||||
implementation(projects.domain.wallets.models)
|
||||
|
||||
/* Project - Core */
|
||||
implementation(projects.core.decompose)
|
||||
|
||||
/* AndroidX */
|
||||
implementation(deps.androidx.fragment.ktx)
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.features.details
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
|
||||
interface DetailsEntryPoint {
|
||||
|
||||
fun entryFragment(): Fragment
|
||||
|
||||
companion object {
|
||||
|
||||
const val USER_WALLET_ID_KEY = "user_wallet_id"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package com.tangem.features.details
|
||||
|
||||
interface DetailsFeatureToggles {
|
||||
|
||||
val isRedesignEnabled: Boolean
|
||||
}
|
||||
1
features/details/impl/.gitignore
vendored
Normal file
1
features/details/impl/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
50
features/details/impl/build.gradle.kts
Normal file
50
features/details/impl/build.gradle.kts
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
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.details.impl"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
/* Project - API */
|
||||
implementation(projects.features.details.api)
|
||||
implementation(projects.features.tester.api)
|
||||
|
||||
/* Project - Core */
|
||||
implementation(projects.core.decompose)
|
||||
implementation(projects.core.ui)
|
||||
implementation(projects.core.featuretoggles)
|
||||
implementation(projects.core.navigation)
|
||||
implementation(projects.core.analytics.models)
|
||||
|
||||
/* Project - Domain */
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.legacy)
|
||||
|
||||
/* AndroidX */
|
||||
implementation(deps.androidx.fragment.ktx)
|
||||
implementation(deps.androidx.activity.compose)
|
||||
implementation(deps.lifecycle.compose)
|
||||
|
||||
/* Compose */
|
||||
implementation(deps.compose.ui)
|
||||
implementation(deps.compose.ui.tooling)
|
||||
implementation(deps.compose.accompanist.systemUiController)
|
||||
implementation(deps.compose.foundation)
|
||||
implementation(deps.compose.material3)
|
||||
implementation(deps.compose.shimmer)
|
||||
|
||||
/* DI */
|
||||
implementation(deps.hilt.android)
|
||||
kapt(deps.hilt.kapt)
|
||||
|
||||
/* Other */
|
||||
implementation(deps.kotlin.immutable.collections)
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.features.details
|
||||
|
||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||
|
||||
internal class DefaultDetailsFeatureToggles(
|
||||
private val featureTogglesManager: FeatureTogglesManager,
|
||||
) : DetailsFeatureToggles {
|
||||
|
||||
override val isRedesignEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled("DETAILS_REDESIGN_ENABLED")
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
package com.tangem.features.details
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.di.RootAppComponentContext
|
||||
import com.tangem.core.ui.UiDependencies
|
||||
import com.tangem.core.ui.message.EventMessageEffect
|
||||
import com.tangem.core.ui.message.EventMessageHandler
|
||||
import com.tangem.core.ui.screen.ComposeFragment
|
||||
import com.tangem.features.details.component.DetailsComponent
|
||||
import com.tangem.features.details.component.preview.PreviewDetailsComponent
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
|
||||
// TODO: Remove after [REDACTED_JIRA]
|
||||
@AndroidEntryPoint
|
||||
internal class DetailsFragment : ComposeFragment() {
|
||||
|
||||
@Inject
|
||||
override lateinit var uiDependencies: UiDependencies
|
||||
|
||||
// @Inject
|
||||
// internal lateinit var componentFactory: DetailsComponent.Factory
|
||||
|
||||
@Inject
|
||||
internal lateinit var detailsRouter: DetailsRouter
|
||||
|
||||
@Inject
|
||||
@RootAppComponentContext
|
||||
internal lateinit var rootContext: AppComponentContext
|
||||
|
||||
private val component: DetailsComponent by lazy { initComponent() }
|
||||
|
||||
private val messageHandler = EventMessageHandler()
|
||||
|
||||
@Composable
|
||||
override fun ScreenContent(modifier: Modifier) {
|
||||
component.View(modifier = modifier)
|
||||
|
||||
EventMessageEffect(
|
||||
messageHandler = messageHandler,
|
||||
snackbarHostState = component.snackbarHostState,
|
||||
)
|
||||
}
|
||||
|
||||
private fun initComponent(): DetailsComponent {
|
||||
// TODO: Uncomment in [REDACTED_JIRA]
|
||||
// val selectedUserWalletId = arguments?.getString(DetailsEntryPoint.USER_WALLET_ID_KEY)
|
||||
// ?.let(::UserWalletId)
|
||||
//
|
||||
//
|
||||
// requireNotNull(selectedUserWalletId) { "UserWalletId must be provided" }
|
||||
//
|
||||
// val context = rootContext.childByContext(
|
||||
// componentContext = defaultComponentContext(requireActivity().onBackPressedDispatcher),
|
||||
// messageHandler = messageHandler,
|
||||
// router = detailsRouter,
|
||||
// )
|
||||
//
|
||||
// return componentFactory.create(
|
||||
// context = context,
|
||||
// params = DetailsComponent.Params(
|
||||
// selectedUserWalletId = selectedUserWalletId,
|
||||
// ),
|
||||
// )
|
||||
|
||||
return PreviewDetailsComponent()
|
||||
}
|
||||
|
||||
companion object : DetailsEntryPoint {
|
||||
|
||||
override fun entryFragment(): Fragment = DetailsFragment()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
package com.tangem.features.details
|
||||
|
||||
import com.tangem.core.decompose.navigation.Route
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.core.navigation.ReduxNavController
|
||||
import com.tangem.domain.redux.ReduxStateHolder
|
||||
import com.tangem.features.details.routing.DetailsRoute
|
||||
import com.tangem.features.tester.api.TesterRouter
|
||||
import javax.inject.Inject
|
||||
|
||||
// TODO: Remove after [REDACTED_JIRA]
|
||||
internal class DetailsRouter @Inject constructor(
|
||||
private val reduxNavController: ReduxNavController,
|
||||
private val reduxStateHolder: ReduxStateHolder,
|
||||
private val testerRouter: TesterRouter,
|
||||
) : Router {
|
||||
|
||||
override fun push(route: Route, onComplete: (isSuccess: Boolean) -> Unit) {
|
||||
if (route is DetailsRoute) {
|
||||
when (route) {
|
||||
is DetailsRoute.Screen -> {
|
||||
reduxNavController.navigate(NavigationAction.NavigateTo(route.screen, bundle = route.params))
|
||||
}
|
||||
is DetailsRoute.Feedback -> {
|
||||
reduxStateHolder.sendFeedbackEmail()
|
||||
}
|
||||
is DetailsRoute.TesterMenu -> {
|
||||
testerRouter.startTesterScreen()
|
||||
}
|
||||
is DetailsRoute.Url -> {
|
||||
reduxNavController.navigate(NavigationAction.OpenUrl(route.url))
|
||||
}
|
||||
}
|
||||
onComplete(true)
|
||||
} else {
|
||||
onComplete(false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun pop(onComplete: (isSuccess: Boolean) -> Unit) {
|
||||
reduxNavController.popBackStack()
|
||||
onComplete(true)
|
||||
}
|
||||
|
||||
override fun popTo(route: Route, onComplete: (isSuccess: Boolean) -> Unit) {
|
||||
if (route is DetailsRoute.Screen) {
|
||||
reduxNavController.popBackStack(route.screen)
|
||||
onComplete(true)
|
||||
} else {
|
||||
reduxNavController.getBackStack()
|
||||
onComplete(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package com.tangem.features.details.component
|
||||
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
interface DetailsComponent {
|
||||
|
||||
val snackbarHostState: SnackbarHostState
|
||||
|
||||
@Composable
|
||||
@Suppress("TopLevelComposableFunctions") // TODO: Remove this check
|
||||
fun View(modifier: Modifier)
|
||||
|
||||
interface Factory {
|
||||
|
||||
fun create(context: AppComponentContext, params: Params): DetailsComponent
|
||||
}
|
||||
|
||||
data class Params(
|
||||
val selectedUserWalletId: UserWalletId,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.features.details.component
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
|
||||
interface UserWalletListComponent {
|
||||
|
||||
@Composable
|
||||
@Suppress("TopLevelComposableFunctions") // TODO: Remove this check
|
||||
fun View(modifier: Modifier)
|
||||
|
||||
interface Factory {
|
||||
fun create(context: AppComponentContext): UserWalletListComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.features.details.component
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
interface WalletConnectComponent {
|
||||
|
||||
suspend fun checkIsAvailable(): Boolean
|
||||
|
||||
@Composable
|
||||
@Suppress("TopLevelComposableFunctions") // TODO: Remove this check
|
||||
fun View(modifier: Modifier)
|
||||
|
||||
interface Factory {
|
||||
fun create(context: AppComponentContext, params: Params): WalletConnectComponent
|
||||
}
|
||||
|
||||
data class Params(
|
||||
val userWalletId: UserWalletId,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package com.tangem.features.details.component.preview
|
||||
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.features.details.component.DetailsComponent
|
||||
import com.tangem.features.details.entity.DetailsFooterUM
|
||||
import com.tangem.features.details.entity.DetailsUM
|
||||
import com.tangem.features.details.ui.DetailsScreen
|
||||
import com.tangem.features.details.utils.ItemsBuilder
|
||||
import com.tangem.features.details.utils.SocialsBuilder
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
internal class PreviewDetailsComponent : DetailsComponent {
|
||||
|
||||
override val snackbarHostState: SnackbarHostState = SnackbarHostState()
|
||||
|
||||
private val previewBlocks = runBlocking {
|
||||
ItemsBuilder(
|
||||
walletConnectComponent = PreviewWalletConnectComponent(),
|
||||
userWalletListComponent = PreviewUserWalletListComponent(),
|
||||
router = PreviewRouter(),
|
||||
).buldAll()
|
||||
}
|
||||
|
||||
private val previewFooter = DetailsFooterUM(
|
||||
socials = SocialsBuilder(PreviewRouter()).buildAll(),
|
||||
appVersion = "1.0.0-preview",
|
||||
)
|
||||
|
||||
val previewState = DetailsUM(
|
||||
items = previewBlocks,
|
||||
footer = previewFooter,
|
||||
popBack = { /* no-op */ },
|
||||
)
|
||||
|
||||
@Composable
|
||||
@Suppress("TopLevelComposableFunctions") // TODO: Remove this check
|
||||
override fun View(modifier: Modifier) {
|
||||
DetailsScreen(
|
||||
modifier = modifier,
|
||||
state = previewState,
|
||||
snackbarHostState = snackbarHostState,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.features.details.component.preview
|
||||
|
||||
import com.tangem.core.decompose.navigation.Route
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
|
||||
internal class PreviewRouter : Router {
|
||||
override fun push(route: Route, onComplete: (isSuccess: Boolean) -> Unit) {
|
||||
/* no-op */
|
||||
}
|
||||
|
||||
override fun pop(onComplete: (isSuccess: Boolean) -> Unit) {
|
||||
/* no-op */
|
||||
}
|
||||
|
||||
override fun popTo(route: Route, onComplete: (isSuccess: Boolean) -> Unit) {
|
||||
/* no-op */
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
package com.tangem.features.details.component.preview
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.features.details.component.UserWalletListComponent
|
||||
import com.tangem.features.details.entity.UserWalletListUM
|
||||
import com.tangem.features.details.impl.R
|
||||
import com.tangem.features.details.ui.UserWalletListBlock
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
internal class PreviewUserWalletListComponent : UserWalletListComponent {
|
||||
|
||||
private val previewState = UserWalletListUM(
|
||||
userWallets = persistentListOf(
|
||||
UserWalletListUM.UserWalletUM(
|
||||
id = UserWalletId("user_wallet_1".encodeToByteArray()),
|
||||
name = "My Wallet",
|
||||
information = getInformation(3, "4 496,75 $"),
|
||||
imageResId = R.drawable.ill_card_wallet_2_211_343,
|
||||
onClick = {},
|
||||
),
|
||||
UserWalletListUM.UserWalletUM(
|
||||
id = UserWalletId("user_wallet_2".encodeToByteArray()),
|
||||
name = "Old wallet",
|
||||
information = getInformation(3, "4 496,75 $"),
|
||||
imageResId = R.drawable.ill_card_note_eth_211_343,
|
||||
onClick = {},
|
||||
),
|
||||
UserWalletListUM.UserWalletUM(
|
||||
id = UserWalletId("user_wallet_3".encodeToByteArray()),
|
||||
name = "Multi Card",
|
||||
information = getInformation(3, "4 496,75 $"),
|
||||
imageResId = R.drawable.ill_card_note_bnb_211_343,
|
||||
onClick = {},
|
||||
),
|
||||
),
|
||||
addNewWalletText = resourceReference(R.string.user_wallet_list_add_button),
|
||||
isWalletSavingInProgress = false,
|
||||
onAddNewWalletClick = {},
|
||||
)
|
||||
|
||||
@Composable
|
||||
@Suppress("TopLevelComposableFunctions") // TODO: Remove this check
|
||||
override fun View(modifier: Modifier) {
|
||||
UserWalletListBlock(state = previewState, modifier = modifier)
|
||||
}
|
||||
|
||||
private fun getInformation(cardCount: Int, totalBalance: String): TextReference {
|
||||
val t1 = TextReference.PluralRes(
|
||||
id = R.plurals.card_label_card_count,
|
||||
count = cardCount,
|
||||
formatArgs = wrappedList(cardCount),
|
||||
)
|
||||
val divider = stringReference(value = " • ")
|
||||
val t2 = stringReference(totalBalance)
|
||||
|
||||
return TextReference.Combined(wrappedList(t1, divider, t2))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.features.details.component.preview
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.features.details.component.WalletConnectComponent
|
||||
import com.tangem.features.details.ui.WalletConnectBlock
|
||||
|
||||
internal class PreviewWalletConnectComponent : WalletConnectComponent {
|
||||
|
||||
override suspend fun checkIsAvailable(): Boolean = true
|
||||
|
||||
@Composable
|
||||
@Suppress("TopLevelComposableFunctions") // TODO: Remove this check
|
||||
override fun View(modifier: Modifier) {
|
||||
WalletConnectBlock(onClick = { /* no-op */ }, modifier = modifier)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package com.tangem.features.details.di
|
||||
|
||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||
import com.tangem.features.details.DefaultDetailsFeatureToggles
|
||||
import com.tangem.features.details.DetailsEntryPoint
|
||||
import com.tangem.features.details.DetailsFeatureToggles
|
||||
import com.tangem.features.details.DetailsFragment
|
||||
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 FeatureModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideFeatureToggles(featureTogglesManager: FeatureTogglesManager): DetailsFeatureToggles {
|
||||
return DefaultDetailsFeatureToggles(featureTogglesManager)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideEntryPoint(): DetailsEntryPoint {
|
||||
return DetailsFragment.Companion
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.features.details.di
|
||||
|
||||
import com.tangem.core.decompose.di.DecomposeComponent
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.features.details.model.DetailsModel
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.multibindings.ClassKey
|
||||
import dagger.multibindings.IntoMap
|
||||
|
||||
@Module
|
||||
@InstallIn(DecomposeComponent::class)
|
||||
internal interface ModelModule {
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(DetailsModel::class)
|
||||
fun provideDetailsModel(model: DetailsModel): Model
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.features.details.entity
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
internal data class DetailsFooterUM(
|
||||
val appVersion: String,
|
||||
val socials: ImmutableList<Social>,
|
||||
) {
|
||||
|
||||
data class Social(
|
||||
val id: String,
|
||||
@DrawableRes
|
||||
val iconResId: Int,
|
||||
val onClick: () -> Unit,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package com.tangem.features.details.entity
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@Immutable
|
||||
internal sealed class DetailsItemUM {
|
||||
|
||||
abstract val id: String
|
||||
|
||||
data class Basic(
|
||||
override val id: String,
|
||||
val items: ImmutableList<Item>,
|
||||
) : DetailsItemUM() {
|
||||
|
||||
data class Item(
|
||||
val id: String,
|
||||
val title: TextReference,
|
||||
@DrawableRes
|
||||
val iconRes: Int,
|
||||
val onClick: () -> Unit,
|
||||
)
|
||||
}
|
||||
|
||||
data class Component(
|
||||
override val id: String,
|
||||
val content: Content,
|
||||
) : DetailsItemUM() {
|
||||
|
||||
fun interface Content {
|
||||
|
||||
@Composable
|
||||
@Suppress("TopLevelComposableFunctions", "ComposableFunctionName")
|
||||
operator fun invoke(modifier: Modifier)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.features.details.entity
|
||||
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
internal data class DetailsUM(
|
||||
val items: ImmutableList<DetailsItemUM>,
|
||||
val footer: DetailsFooterUM,
|
||||
val popBack: () -> Unit,
|
||||
)
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.features.details.entity
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
internal data class UserWalletListUM(
|
||||
val userWallets: ImmutableList<UserWalletUM>,
|
||||
val isWalletSavingInProgress: Boolean,
|
||||
val addNewWalletText: TextReference,
|
||||
val onAddNewWalletClick: () -> Unit,
|
||||
) {
|
||||
|
||||
data class UserWalletUM(
|
||||
val id: UserWalletId,
|
||||
val name: String,
|
||||
val information: TextReference,
|
||||
@DrawableRes
|
||||
val imageResId: Int,
|
||||
val onClick: () -> Unit,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.features.details.model
|
||||
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import javax.inject.Inject
|
||||
|
||||
// TODO: Will be implemented later
|
||||
internal class DetailsModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
) : Model()
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.features.details.routing
|
||||
|
||||
import android.os.Bundle
|
||||
import com.tangem.core.decompose.navigation.Route
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
|
||||
// TODO: Remove after [REDACTED_JIRA]
|
||||
internal sealed class DetailsRoute : Route {
|
||||
|
||||
data class Screen(
|
||||
val screen: AppScreen,
|
||||
val params: Bundle? = null,
|
||||
) : DetailsRoute()
|
||||
|
||||
data class Url(val url: String) : DetailsRoute()
|
||||
|
||||
data object Feedback : DetailsRoute()
|
||||
|
||||
data object TesterMenu : DetailsRoute()
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
package com.tangem.features.details.ui
|
||||
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardColors
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.ReadOnlyComposable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
@Composable
|
||||
internal fun BlockCard(
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
onClick: () -> Unit = {},
|
||||
content: @Composable ColumnScope.() -> Unit = {},
|
||||
) {
|
||||
Card(
|
||||
modifier = modifier,
|
||||
onClick = onClick,
|
||||
shape = TangemTheme.shapes.roundedCornersXMedium,
|
||||
colors = BlockColors,
|
||||
enabled = enabled,
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
||||
private val BlockColors: CardColors
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = CardColors(
|
||||
containerColor = TangemTheme.colors.background.primary,
|
||||
contentColor = TangemTheme.colors.text.primary1,
|
||||
disabledContainerColor = TangemTheme.colors.button.disabled,
|
||||
disabledContentColor = TangemTheme.colors.text.disabled,
|
||||
)
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
package com.tangem.features.details.ui
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.details.entity.DetailsItemUM
|
||||
|
||||
@Composable
|
||||
internal fun BlockItem(model: DetailsItemUM.Basic.Item, modifier: Modifier = Modifier) {
|
||||
BlockCard(
|
||||
modifier = modifier,
|
||||
onClick = model.onClick,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(all = TangemTheme.dimens.spacing12),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12, Alignment.Start),
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size24),
|
||||
painter = painterResource(id = model.iconRes),
|
||||
tint = TangemTheme.colors.icon.secondary,
|
||||
contentDescription = null,
|
||||
)
|
||||
|
||||
Text(
|
||||
text = model.title.resolveReference(),
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,228 @@
|
|||
package com.tangem.features.details.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.gestures.Orientation
|
||||
import androidx.compose.foundation.gestures.scrollable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.components.SystemBarsEffect
|
||||
import com.tangem.core.ui.components.snackbar.TangemSnackbarHost
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.details.component.preview.PreviewDetailsComponent
|
||||
import com.tangem.features.details.entity.DetailsFooterUM
|
||||
import com.tangem.features.details.entity.DetailsItemUM
|
||||
import com.tangem.features.details.entity.DetailsUM
|
||||
import com.tangem.features.details.impl.R
|
||||
|
||||
private const val COLLAPSED_APP_BAR_THRESHOLD = 0.4f
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
internal fun DetailsScreen(state: DetailsUM, snackbarHostState: SnackbarHostState, modifier: Modifier = Modifier) {
|
||||
val backgroundColor = TangemTheme.colors.background.secondary
|
||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
|
||||
|
||||
SystemBarsEffect {
|
||||
setSystemBarsColor(backgroundColor)
|
||||
}
|
||||
|
||||
BackHandler(onBack = state.popBack)
|
||||
|
||||
Scaffold(
|
||||
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
containerColor = backgroundColor,
|
||||
snackbarHost = {
|
||||
TangemSnackbarHost(
|
||||
modifier = Modifier.padding(all = TangemTheme.dimens.spacing16),
|
||||
hostState = snackbarHostState,
|
||||
)
|
||||
},
|
||||
topBar = { TopBar(state, scrollBehavior) },
|
||||
) { paddingValues ->
|
||||
Content(
|
||||
modifier = Modifier.padding(paddingValues),
|
||||
state = state,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun TopBar(state: DetailsUM, scrollBehavior: TopAppBarScrollBehavior, modifier: Modifier = Modifier) {
|
||||
MediumTopAppBar(
|
||||
modifier = modifier,
|
||||
scrollBehavior = scrollBehavior,
|
||||
colors = TopAppBarColors(
|
||||
containerColor = TangemTheme.colors.background.secondary,
|
||||
scrolledContainerColor = TangemTheme.colors.background.secondary,
|
||||
navigationIconContentColor = TangemTheme.colors.icon.primary1,
|
||||
titleContentColor = TangemTheme.colors.text.primary1,
|
||||
actionIconContentColor = TangemTheme.colors.icon.primary1,
|
||||
),
|
||||
title = {
|
||||
val collapsedStyle = TangemTheme.typography.subtitle1
|
||||
val expandedStyle = TangemTheme.typography.h1
|
||||
val style by remember(scrollBehavior.state.collapsedFraction) {
|
||||
derivedStateOf {
|
||||
if (scrollBehavior.state.collapsedFraction >= COLLAPSED_APP_BAR_THRESHOLD) {
|
||||
collapsedStyle
|
||||
} else {
|
||||
expandedStyle
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
text = stringResource(id = R.string.details_title),
|
||||
style = style,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
},
|
||||
navigationIcon = {
|
||||
IconButton(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size32),
|
||||
onClick = state.popBack,
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size24),
|
||||
painter = painterResource(id = R.drawable.ic_back_24),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Content(state: DetailsUM, modifier: Modifier = Modifier) {
|
||||
LazyColumn(
|
||||
modifier = modifier,
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing16),
|
||||
contentPadding = PaddingValues(
|
||||
top = TangemTheme.dimens.spacing16,
|
||||
bottom = TangemTheme.dimens.spacing16,
|
||||
),
|
||||
) {
|
||||
items(
|
||||
items = state.items,
|
||||
key = DetailsItemUM::id,
|
||||
) { block ->
|
||||
Block(
|
||||
modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing16),
|
||||
model = block,
|
||||
)
|
||||
}
|
||||
|
||||
item(key = "footer") {
|
||||
Footer(
|
||||
modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing12),
|
||||
model = state.footer,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Block(model: DetailsItemUM, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.background(
|
||||
shape = TangemTheme.shapes.roundedCornersXMedium,
|
||||
color = TangemTheme.colors.background.primary,
|
||||
),
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.Top,
|
||||
) {
|
||||
when (model) {
|
||||
is DetailsItemUM.Basic -> {
|
||||
model.items.forEach { item ->
|
||||
key(item.id) {
|
||||
BlockItem(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
model = item,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
is DetailsItemUM.Component -> {
|
||||
model.content(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Footer(model: DetailsFooterUM, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.padding(
|
||||
top = TangemTheme.dimens.spacing12,
|
||||
bottom = TangemTheme.dimens.spacing16,
|
||||
)
|
||||
.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing16),
|
||||
) {
|
||||
val socialsScrollState = rememberScrollState()
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.scrollable(socialsScrollState, orientation = Orientation.Horizontal),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
|
||||
) {
|
||||
model.socials.forEach { social ->
|
||||
key(social.id) {
|
||||
IconButton(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size32),
|
||||
onClick = social.onClick,
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size24),
|
||||
painter = painterResource(id = social.iconResId),
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing6),
|
||||
text = model.appVersion,
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Composable
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
private fun Preview_DetailsScreen() {
|
||||
TangemThemePreview {
|
||||
PreviewDetailsComponent().View(modifier = Modifier.fillMaxSize())
|
||||
}
|
||||
}
|
||||
// endregion Preview
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
package com.tangem.features.details.ui
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.details.entity.UserWalletListUM
|
||||
import com.tangem.features.details.impl.R
|
||||
|
||||
@Composable
|
||||
internal fun UserWalletListBlock(state: UserWalletListUM, modifier: Modifier = Modifier) {
|
||||
BlockCard(
|
||||
modifier = modifier,
|
||||
) {
|
||||
state.userWallets.forEach { model ->
|
||||
key(model.id) {
|
||||
UserWalletItem(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
model = model,
|
||||
)
|
||||
}
|
||||
}
|
||||
AddWalletButton(
|
||||
text = state.addNewWalletText,
|
||||
isInProgress = state.isWalletSavingInProgress,
|
||||
onClick = state.onAddNewWalletClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun UserWalletItem(model: UserWalletListUM.UserWalletUM, modifier: Modifier = Modifier) {
|
||||
BlockCard(
|
||||
modifier = modifier,
|
||||
onClick = model.onClick,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(min = TangemTheme.dimens.size68)
|
||||
.padding(all = TangemTheme.dimens.spacing12),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
Image(
|
||||
modifier = Modifier
|
||||
.width(TangemTheme.dimens.size24)
|
||||
.height(TangemTheme.dimens.size36),
|
||||
painter = painterResource(id = model.imageResId),
|
||||
contentScale = ContentScale.FillBounds,
|
||||
contentDescription = null,
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = Modifier.heightIn(min = TangemTheme.dimens.size40),
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.SpaceEvenly,
|
||||
) {
|
||||
Text(
|
||||
text = model.name,
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
Text(
|
||||
text = model.information.resolveReference(),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AddWalletButton(
|
||||
text: TextReference,
|
||||
isInProgress: Boolean,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
BlockCard(
|
||||
modifier = modifier,
|
||||
onClick = onClick,
|
||||
enabled = !isInProgress,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(all = TangemTheme.dimens.spacing12),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size24),
|
||||
painter = painterResource(id = R.drawable.ic_plus_24),
|
||||
tint = TangemTheme.colors.icon.accent,
|
||||
contentDescription = null,
|
||||
)
|
||||
|
||||
Text(
|
||||
text = text.resolveReference(),
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
color = TangemTheme.colors.text.accent,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
package com.tangem.features.details.ui
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.tangem.core.ui.res.TangemColorPalette
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.details.impl.R
|
||||
|
||||
@Composable
|
||||
internal fun WalletConnectBlock(onClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||
BlockCard(
|
||||
modifier = modifier,
|
||||
onClick = onClick,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(all = TangemTheme.dimens.spacing12),
|
||||
verticalAlignment = Alignment.Top,
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size24),
|
||||
painter = painterResource(id = R.drawable.ic_wallet_connect_24),
|
||||
tint = TangemColorPalette.Azure,
|
||||
contentDescription = null,
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = Modifier.heightIn(min = TangemTheme.dimens.size48),
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.SpaceAround,
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.wallet_connect_title),
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
Text(
|
||||
text = stringResource(id = R.string.wallet_connect_subtitle),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
package com.tangem.features.details.utils
|
||||
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.features.details.component.UserWalletListComponent
|
||||
import com.tangem.features.details.component.WalletConnectComponent
|
||||
import com.tangem.features.details.entity.DetailsItemUM
|
||||
import com.tangem.features.details.impl.BuildConfig
|
||||
import com.tangem.features.details.impl.R
|
||||
import com.tangem.features.details.routing.DetailsRoute
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
internal class ItemsBuilder(
|
||||
private val walletConnectComponent: WalletConnectComponent,
|
||||
private val userWalletListComponent: UserWalletListComponent,
|
||||
private val router: Router,
|
||||
) {
|
||||
|
||||
suspend fun buldAll(): ImmutableList<DetailsItemUM> = buildList {
|
||||
buildWalletConnectBlock()?.let(::add)
|
||||
buildUserWalletListBlock().let(::add)
|
||||
buildShopBlock().let(::add)
|
||||
buildSettingsBlock().let(::add)
|
||||
buildSupportBlock().let(::add)
|
||||
}.toImmutableList()
|
||||
|
||||
private suspend fun buildWalletConnectBlock(): DetailsItemUM? {
|
||||
return if (walletConnectComponent.checkIsAvailable()) {
|
||||
DetailsItemUM.Component(
|
||||
id = "wallet_connect",
|
||||
content = {
|
||||
walletConnectComponent.View(modifier = it)
|
||||
},
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildUserWalletListBlock(): DetailsItemUM = DetailsItemUM.Component(
|
||||
id = "user_wallet_list",
|
||||
content = {
|
||||
userWalletListComponent.View(modifier = it)
|
||||
},
|
||||
)
|
||||
|
||||
private fun buildShopBlock(): DetailsItemUM = DetailsItemUM.Basic(
|
||||
id = "shop",
|
||||
items = persistentListOf(
|
||||
DetailsItemUM.Basic.Item(
|
||||
id = "buy_tangem_wallet",
|
||||
title = stringReference("Buy Tangem Wallet"), // TODO: Move to resources in [REDACTED_TASK_KEY]
|
||||
iconRes = R.drawable.ic_tangem_24,
|
||||
onClick = { router.push(DetailsRoute.Url(BUY_TANGEM_URL)) },
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
private fun buildSettingsBlock(): DetailsItemUM = DetailsItemUM.Basic(
|
||||
id = "settings",
|
||||
items = buildList {
|
||||
DetailsItemUM.Basic.Item(
|
||||
id = "app_settings",
|
||||
title = resourceReference(R.string.app_settings_title),
|
||||
iconRes = R.drawable.ic_settings_24,
|
||||
onClick = { router.push(DetailsRoute.Screen(AppScreen.AppSettings)) },
|
||||
).let(::add)
|
||||
|
||||
if (BuildConfig.TESTER_MENU_ENABLED) {
|
||||
DetailsItemUM.Basic.Item(
|
||||
id = "tester_menu",
|
||||
title = stringReference(value = "Tester menu"),
|
||||
iconRes = R.drawable.ic_alert_24,
|
||||
onClick = { router.push(DetailsRoute.TesterMenu) },
|
||||
).let(::add)
|
||||
}
|
||||
}.toImmutableList(),
|
||||
)
|
||||
|
||||
private fun buildSupportBlock(): DetailsItemUM = DetailsItemUM.Basic(
|
||||
id = "support",
|
||||
items = persistentListOf(
|
||||
DetailsItemUM.Basic.Item(
|
||||
id = "send_feedback",
|
||||
title = stringReference("Send feedback"), // TODO: Move to resources in [REDACTED_TASK_KEY]
|
||||
iconRes = R.drawable.ic_comment_24,
|
||||
onClick = { router.push(DetailsRoute.Feedback) },
|
||||
),
|
||||
DetailsItemUM.Basic.Item(
|
||||
id = "disclaimer",
|
||||
title = resourceReference(R.string.disclaimer_title),
|
||||
iconRes = R.drawable.ic_text_24,
|
||||
onClick = { router.push(DetailsRoute.Screen(AppScreen.Disclaimer)) },
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
private companion object {
|
||||
const val BUY_TANGEM_URL = "https://buy.tangem.com/"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
package com.tangem.features.details.utils
|
||||
|
||||
import androidx.compose.ui.text.intl.Locale
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.features.details.entity.DetailsFooterUM
|
||||
import com.tangem.features.details.impl.R
|
||||
import com.tangem.features.details.routing.DetailsRoute
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
internal class SocialsBuilder(
|
||||
private val router: Router,
|
||||
) {
|
||||
|
||||
fun buildAll(): ImmutableList<DetailsFooterUM.Social> = Social.all.map { social ->
|
||||
DetailsFooterUM.Social(
|
||||
id = social.name,
|
||||
iconResId = social.iconResId,
|
||||
onClick = { openUrl(social) },
|
||||
)
|
||||
}.toImmutableList()
|
||||
|
||||
private fun openUrl(social: Social) {
|
||||
val locale = Locale.current.region
|
||||
|
||||
val url = if (locale == RUSSIA_LOCALE && social.urlRu != null) {
|
||||
social.urlRu
|
||||
} else {
|
||||
social.url
|
||||
}
|
||||
|
||||
router.push(DetailsRoute.Url(url))
|
||||
}
|
||||
|
||||
private enum class Social(
|
||||
val iconResId: Int,
|
||||
val url: String,
|
||||
val urlRu: String? = null,
|
||||
) {
|
||||
X(
|
||||
iconResId = R.drawable.ic_twitter_24,
|
||||
url = "https://x.com/tangem",
|
||||
),
|
||||
TELEGRAM(
|
||||
iconResId = R.drawable.ic_telegram_24,
|
||||
url = "https://t.me/tangem_chat",
|
||||
urlRu = "https://t.me/tangem_chat_ru",
|
||||
),
|
||||
DISCORD(
|
||||
iconResId = R.drawable.ic_discord_24,
|
||||
url = "https://discord.gg/tangem",
|
||||
),
|
||||
REDDIT(
|
||||
iconResId = R.drawable.ic_reddit_24,
|
||||
url = "https://www.reddit.com/r/Tangem/",
|
||||
),
|
||||
INSTAGRAM(
|
||||
iconResId = R.drawable.ic_instagram_24,
|
||||
url = "https://www.instagram.com/tangemwallet",
|
||||
),
|
||||
GIT_HUB(
|
||||
iconResId = R.drawable.ic_github_24,
|
||||
url = "https://github.com/tangem",
|
||||
),
|
||||
FACEBOOK(
|
||||
iconResId = R.drawable.ic_facebook_24,
|
||||
url = "https://www.facebook.com/tangemwallet",
|
||||
),
|
||||
LINKEDIN(
|
||||
iconResId = R.drawable.ic_linkedin_24,
|
||||
url = "https://www.linkedin.com/company/tangem",
|
||||
),
|
||||
YOUTUBE(
|
||||
iconResId = R.drawable.ic_youtube_24,
|
||||
url = "https://youtube.com/@tangem_official",
|
||||
),
|
||||
;
|
||||
|
||||
companion object {
|
||||
|
||||
val all = values()
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val RUSSIA_LOCALE = "ru"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
|
|
@ -56,6 +56,7 @@ dependencies {
|
|||
implementation(projects.domain.card)
|
||||
implementation(projects.domain.demo)
|
||||
implementation(projects.domain.legacy)
|
||||
implementation(projects.libs.blockchainSdk)
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.settings)
|
||||
implementation(projects.domain.tokens)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.managetokens.presentation.addcustomtoken.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
|
|
@ -24,6 +25,7 @@ import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
|||
import com.tangem.core.ui.components.keyboardAsState
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.managetokens.impl.R
|
||||
import com.tangem.managetokens.presentation.common.state.AlertState
|
||||
import com.tangem.managetokens.presentation.common.state.ChooseWalletState
|
||||
|
|
@ -287,17 +289,10 @@ private fun TokenTextFieldTitle(state: TextFieldState?, title: String) {
|
|||
}
|
||||
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_ChooseDerivationScreen_Light() {
|
||||
TangemTheme(isDark = false) {
|
||||
AddCustomTokenScreen(state = AddCustomTokenPreviewData.state)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_ChooseDerivationScreen_Dark() {
|
||||
TangemTheme(isDark = true) {
|
||||
private fun Preview_ChooseDerivationScreen() {
|
||||
TangemThemePreview {
|
||||
AddCustomTokenScreen(state = AddCustomTokenPreviewData.state)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.managetokens.presentation.addcustomtoken.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
|
|
@ -13,6 +14,7 @@ import androidx.compose.ui.res.painterResource
|
|||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.managetokens.impl.R
|
||||
import com.tangem.managetokens.presentation.common.ui.components.SimpleSelectionBlock
|
||||
|
|
@ -98,17 +100,10 @@ private fun DerivationsList(state: ChooseDerivationState) {
|
|||
}
|
||||
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_ChooseDerivationScreen_Light() {
|
||||
TangemTheme(isDark = false) {
|
||||
ChooseDerivationScreen(state = ChooseDerivationPreviewData.state)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_ChooseDerivationScreen_Dark() {
|
||||
TangemTheme(isDark = true) {
|
||||
private fun Preview_ChooseDerivationScreen() {
|
||||
TangemThemePreview {
|
||||
ChooseDerivationScreen(state = ChooseDerivationPreviewData.state)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.managetokens.presentation.addcustomtoken.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
|
|
@ -13,6 +14,7 @@ import androidx.compose.ui.res.painterResource
|
|||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.managetokens.impl.R
|
||||
import com.tangem.managetokens.presentation.common.ui.components.NetworkItem
|
||||
|
|
@ -78,17 +80,10 @@ internal fun ChooseNetworkCustomScreen(state: ChooseNetworkState, modifier: Modi
|
|||
}
|
||||
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_ChooseNetworkScreen_Light() {
|
||||
TangemTheme(isDark = false) {
|
||||
ChooseNetworkCustomScreen(ChooseNetworkCustomPreviewData.state)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_ChooseNetworkScreen_Dark() {
|
||||
TangemTheme(isDark = true) {
|
||||
private fun Preview_ChooseNetworkScreen() {
|
||||
TangemThemePreview {
|
||||
ChooseNetworkCustomScreen(ChooseNetworkCustomPreviewData.state)
|
||||
}
|
||||
}
|
||||
|
|
@ -43,10 +43,10 @@ internal sealed class AlertState {
|
|||
)
|
||||
}
|
||||
|
||||
class CannotHideNetworkWithTokens(tokenName: String, networkName: String) : AlertState() {
|
||||
class CannotHideNetworkWithTokens(tokenName: String, currencySymbol: String, networkName: String) : AlertState() {
|
||||
override val message: TextReference = resourceReference(
|
||||
id = R.string.token_details_unable_hide_alert_message,
|
||||
formatArgs = wrappedList(tokenName, networkName),
|
||||
formatArgs = wrappedList(tokenName, currencySymbol, networkName),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.managetokens.presentation.common.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
|
|
@ -23,6 +24,7 @@ import com.tangem.core.ui.components.SpacerW12
|
|||
import com.tangem.core.ui.components.SpacerWMax
|
||||
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.managetokens.impl.R
|
||||
import com.tangem.managetokens.presentation.common.state.ChooseWalletState
|
||||
import com.tangem.managetokens.presentation.common.state.WalletState
|
||||
|
|
@ -137,19 +139,10 @@ private fun WalletItem(wallet: WalletState, selectedWallet: WalletState?, modifi
|
|||
}
|
||||
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_ChooseWalletScreen_Light() {
|
||||
TangemTheme(isDark = false) {
|
||||
ChooseWalletScreen(
|
||||
state = ChooseWalletStatePreviewData.state,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_ChooseWalletScreen_Dark() {
|
||||
TangemTheme(isDark = false) {
|
||||
private fun Preview_ChooseWalletScreen() {
|
||||
TangemThemePreview {
|
||||
ChooseWalletScreen(
|
||||
state = ChooseWalletStatePreviewData.state,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.managetokens.presentation.common.ui.components
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
|
|
@ -18,6 +19,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
|||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import com.tangem.core.ui.components.SpacerW
|
||||
import com.tangem.core.ui.components.TangemSwitch
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.managetokens.impl.R
|
||||
import com.tangem.managetokens.presentation.common.state.NetworkItemState
|
||||
|
|
@ -122,17 +124,10 @@ internal fun NetworkIcon(model: NetworkItemState, modifier: Modifier = Modifier)
|
|||
}
|
||||
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_NetworkItem_Light(@PreviewParameter(NetworkItemStateProvider::class) state: NetworkItemState) {
|
||||
TangemTheme(isDark = false) {
|
||||
NetworkItem(state, tokenState = TokenItemStatePreviewData.loadedPriceDown as TokenItemState.Loaded)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_NetworkItem_Dark(@PreviewParameter(NetworkItemStateProvider::class) state: NetworkItemState) {
|
||||
TangemTheme(isDark = true) {
|
||||
private fun Preview_NetworkItem(@PreviewParameter(NetworkItemStateProvider::class) state: NetworkItemState) {
|
||||
TangemThemePreview {
|
||||
NetworkItem(state, tokenState = TokenItemStatePreviewData.loadedPriceDown as TokenItemState.Loaded)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.managetokens.presentation.common.ui.components
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
|
@ -12,6 +13,7 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.components.SpacerH
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
@Composable
|
||||
|
|
@ -54,17 +56,10 @@ fun SimpleSelectionBlock(
|
|||
}
|
||||
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_SimpleSelectionBlock_Light() {
|
||||
TangemTheme(isDark = false) {
|
||||
SimpleSelectionBlock(title = "Wallet", subtitle = "Family Wallet", onClick = { })
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_SimpleSelectionBlock_Dark() {
|
||||
TangemTheme(isDark = true) {
|
||||
private fun Preview_SimpleSelectionBlock() {
|
||||
TangemThemePreview {
|
||||
SimpleSelectionBlock(title = "Wallet", subtitle = "Family Wallet", onClick = { })
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.managetokens.presentation.managetokens.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
|
|
@ -17,6 +18,7 @@ import com.tangem.core.ui.components.SpacerH
|
|||
import com.tangem.core.ui.components.SpacerW
|
||||
import com.tangem.core.ui.components.WarningCardTitleOnly
|
||||
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.managetokens.impl.R
|
||||
import com.tangem.managetokens.presentation.common.state.ChooseWalletState
|
||||
|
|
@ -187,20 +189,10 @@ private fun NonNativeNetworksHeader(onNonNativeNetworkHintClick: () -> Unit) {
|
|||
}
|
||||
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_ChooseNetworkScreen_Light() {
|
||||
TangemTheme(isDark = false) {
|
||||
ChooseNetworkScreen(
|
||||
state = TokenItemStatePreviewData.loadedPriceDown as TokenItemState.Loaded,
|
||||
walletState = ChooseWalletStatePreviewData.state,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_ChooseNetworkScreen_Dark() {
|
||||
TangemTheme(isDark = true) {
|
||||
private fun Preview_ChooseNetworkScreen() {
|
||||
TangemThemePreview {
|
||||
ChooseNetworkScreen(
|
||||
state = TokenItemStatePreviewData.loadedPriceDown as TokenItemState.Loaded,
|
||||
walletState = ChooseWalletStatePreviewData.state,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.managetokens.presentation.managetokens.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
|
|
@ -22,6 +23,7 @@ import com.tangem.core.ui.components.Keyboard
|
|||
import com.tangem.core.ui.components.SpacerH18
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.keyboardAsState
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.managetokens.presentation.addcustomtoken.ui.AddCustomTokenBottomSheet
|
||||
import com.tangem.managetokens.presentation.common.state.AlertState
|
||||
|
|
@ -177,23 +179,13 @@ private fun ManageTokensBottomSheet(selectedToken: TokenItemState.Loaded, state:
|
|||
}
|
||||
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_ManageTokensScreen_LightTheme(
|
||||
private fun Preview_ManageTokensScreen(
|
||||
@PreviewParameter(ManageTokensConfigProvider::class)
|
||||
state: ManageTokensState,
|
||||
) {
|
||||
TangemTheme(isDark = false) {
|
||||
ManageTokensScreen(state) {}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_ManageTokensScreen_DarkTheme(
|
||||
@PreviewParameter(ManageTokensConfigProvider::class)
|
||||
state: ManageTokensState,
|
||||
) {
|
||||
TangemTheme(isDark = true) {
|
||||
TangemThemePreview {
|
||||
ManageTokensScreen(state) {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.managetokens.presentation.managetokens.ui.components
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
|
|
@ -14,6 +15,7 @@ import androidx.compose.ui.res.stringResource
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.components.SpacerW
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.managetokens.impl.R
|
||||
|
||||
@Composable
|
||||
|
|
@ -49,17 +51,10 @@ internal fun AddCustomTokenButton(onButtonClick: () -> Unit, modifier: Modifier
|
|||
}
|
||||
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun AddCustomTokenButton_Preview_Light() {
|
||||
TangemTheme(isDark = false) {
|
||||
AddCustomTokenButton(onButtonClick = { })
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun AddCustomTokenButton_Preview_Dark() {
|
||||
TangemTheme(isDark = true) {
|
||||
private fun AddCustomTokenButton_Preview() {
|
||||
TangemThemePreview {
|
||||
AddCustomTokenButton(onButtonClick = { })
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.managetokens.presentation.managetokens.ui.components
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
|
|
@ -18,6 +19,7 @@ import com.tangem.core.ui.components.SpacerW
|
|||
import com.tangem.core.ui.components.notifications.NotificationConfig
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.managetokens.impl.R
|
||||
import com.tangem.managetokens.presentation.managetokens.state.previewdata.DerivationNotificationStatePreviewData
|
||||
|
|
@ -127,17 +129,10 @@ private fun TextsBlock(title: TextReference, subtitle: TextReference) {
|
|||
}
|
||||
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_ManageTokensScreen_LightTheme() {
|
||||
TangemTheme(isDark = false) {
|
||||
DerivationNotification(DerivationNotificationStatePreviewData.state.config)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_ManageTokensScreen_DarkTheme() {
|
||||
TangemTheme(isDark = true) {
|
||||
private fun Preview_ManageTokensScreen() {
|
||||
TangemThemePreview {
|
||||
DerivationNotification(DerivationNotificationStatePreviewData.state.config)
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@ import androidx.compose.ui.graphics.drawscope.DrawScope
|
|||
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
|
|
@ -105,7 +106,7 @@ private fun getValuePercentageForRange(value: Float, max: Float, min: Float): Fl
|
|||
@Preview(widthDp = 150, heightDp = 150, showBackground = true)
|
||||
@Composable
|
||||
private fun Chart_Positive_Preview() {
|
||||
TangemTheme(isDark = true) {
|
||||
TangemThemePreview(isDark = true) {
|
||||
PriceChangesChart(
|
||||
persistentListOf(1f, 2f, 4f, 1f, 5f),
|
||||
)
|
||||
|
|
@ -115,7 +116,7 @@ private fun Chart_Positive_Preview() {
|
|||
@Preview(widthDp = 150, heightDp = 150, showBackground = true)
|
||||
@Composable
|
||||
private fun Chart_Negative_Preview() {
|
||||
TangemTheme(isDark = true) {
|
||||
TangemThemePreview(isDark = true) {
|
||||
PriceChangesChart(
|
||||
persistentListOf(10f, 2f, 4f, 1f, 5f),
|
||||
)
|
||||
|
|
@ -125,7 +126,7 @@ private fun Chart_Negative_Preview() {
|
|||
@Preview(widthDp = 150, heightDp = 150, showBackground = true)
|
||||
@Composable
|
||||
private fun Chart_Neutral_Preview() {
|
||||
TangemTheme(isDark = true) {
|
||||
TangemThemePreview(isDark = true) {
|
||||
PriceChangesChart(
|
||||
persistentListOf(5f, 2f, 4f, 1f, 5f),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.managetokens.presentation.managetokens.ui.components
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
|
|
@ -20,6 +21,7 @@ import androidx.compose.ui.text.input.KeyboardType
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.managetokens.impl.R
|
||||
import com.tangem.managetokens.presentation.managetokens.state.SearchBarState
|
||||
|
|
@ -107,20 +109,13 @@ private fun searchbarTextFieldColors(): TextFieldColors {
|
|||
}
|
||||
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_TokensSearchBar_Light(
|
||||
private fun Preview_TokensSearchBar(
|
||||
@PreviewParameter(SearchBarkConfigProvider::class)
|
||||
state: SearchBarState,
|
||||
) {
|
||||
TangemTheme(isDark = false) {
|
||||
TokensSearchBar(state)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_TokensSearchBar_Dark(@PreviewParameter(SearchBarkConfigProvider::class) state: SearchBarState) {
|
||||
TangemTheme(isDark = true) {
|
||||
TangemThemePreview {
|
||||
TokensSearchBar(state)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.managetokens.presentation.managetokens.ui.components
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Box
|
||||
|
|
@ -18,6 +19,7 @@ import com.tangem.core.ui.components.buttons.PrimarySmallButton
|
|||
import com.tangem.core.ui.components.buttons.SecondarySmallButton
|
||||
import com.tangem.core.ui.components.buttons.SmallButtonConfig
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.managetokens.impl.R
|
||||
import com.tangem.managetokens.presentation.managetokens.state.TokenButtonType
|
||||
|
|
@ -66,17 +68,10 @@ internal fun TokenButton(type: TokenButtonType, onClick: () -> Unit, modifier: M
|
|||
}
|
||||
|
||||
@Preview(backgroundColor = 0xffffff, showBackground = true)
|
||||
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun TokenButton_Light_Preview(@PreviewParameter(TokenButtonTypeProvider::class) type: TokenButtonType) {
|
||||
TangemTheme(isDark = false) {
|
||||
TokenButton(type = type, {})
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun TokenButton_Dark_Preview(@PreviewParameter(TokenButtonTypeProvider::class) type: TokenButtonType) {
|
||||
TangemTheme(isDark = true) {
|
||||
private fun TokenButton_Preview(@PreviewParameter(TokenButtonTypeProvider::class) type: TokenButtonType) {
|
||||
TangemThemePreview {
|
||||
TokenButton(type = type, {})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.managetokens.presentation.managetokens.ui.components
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.Surface
|
||||
|
|
@ -14,6 +15,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
|||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import com.tangem.core.ui.components.*
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.managetokens.presentation.managetokens.state.QuotesState
|
||||
import com.tangem.managetokens.presentation.managetokens.state.TokenItemState
|
||||
|
|
@ -182,17 +184,10 @@ private fun BaseSurface(modifier: Modifier = Modifier, content: @Composable () -
|
|||
|
||||
// region Preview
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_Tokens_LightTheme(@PreviewParameter(TokenConfigProvider::class) state: TokenItemState) {
|
||||
TangemTheme(isDark = false) {
|
||||
TokenRowItem(state)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun Preview_Tokens_DarkTheme(@PreviewParameter(TokenConfigProvider::class) state: TokenItemState) {
|
||||
TangemTheme(isDark = true) {
|
||||
private fun Preview_Tokens(@PreviewParameter(TokenConfigProvider::class) state: TokenItemState) {
|
||||
TangemThemePreview {
|
||||
TokenRowItem(state)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -391,6 +391,7 @@ internal class ManageTokensViewModel @Inject constructor(
|
|||
event = Event.ShowAlert(
|
||||
AlertState.CannotHideNetworkWithTokens(
|
||||
tokenName = cryptoCurrency.name,
|
||||
currencySymbol = cryptoCurrency.symbol,
|
||||
networkName = cryptoCurrency.network.name,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.feature.onboarding.presentation.wallet2.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
|
|
@ -21,6 +22,7 @@ import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameter
|
|||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import com.tangem.core.ui.components.*
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.onboarding.R
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.model.ButtonState
|
||||
|
|
@ -204,11 +206,12 @@ private fun Modifier.rowPadding(index: Int, rowSize: Int, outSide: Dp, inSide: D
|
|||
}
|
||||
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun SuggestionsBlockPreview_Light(
|
||||
private fun SuggestionsBlockPreview(
|
||||
@PreviewParameter(SuggestionsPreviewParamsProvider::class) suggestions: ImmutableList<String>,
|
||||
) {
|
||||
TangemTheme(isDark = false) {
|
||||
TangemThemePreview {
|
||||
SuggestionsBlock(
|
||||
suggestionsList = suggestions,
|
||||
onClick = {},
|
||||
|
|
@ -217,24 +220,12 @@ private fun SuggestionsBlockPreview_Light(
|
|||
}
|
||||
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun SuggestionsBlockPreview_Dark(
|
||||
private fun ImportSeedPhraseScreenPreview(
|
||||
@PreviewParameter(SuggestionsPreviewParamsProvider::class) suggestions: ImmutableList<String>,
|
||||
) {
|
||||
TangemTheme(isDark = true) {
|
||||
SuggestionsBlock(
|
||||
suggestionsList = suggestions,
|
||||
onClick = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun ImportSeedPhraseScreenPreview_Light(
|
||||
@PreviewParameter(SuggestionsPreviewParamsProvider::class) suggestions: ImmutableList<String>,
|
||||
) {
|
||||
TangemTheme(isDark = false) {
|
||||
TangemThemePreview {
|
||||
Box(modifier = Modifier.background(TangemTheme.colors.background.primary)) {
|
||||
ImportSeedPhraseScreen(
|
||||
ImportSeedPhraseState(
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import com.tangem.core.ui.components.PrimaryButton
|
|||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.feature.onboarding.R
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.model.ShowPassphraseInfoBottomSheetContent
|
||||
|
||||
|
|
@ -83,7 +84,7 @@ fun PassphraseInfoBottomSheetContent(content: ShowPassphraseInfoBottomSheetConte
|
|||
@Preview
|
||||
@Composable
|
||||
private fun PassphraseInfoBottomSheetContentPreview() {
|
||||
TangemTheme {
|
||||
TangemThemePreview {
|
||||
PassphraseInfoBottomSheetContent(ShowPassphraseInfoBottomSheetContent { })
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.feature.onboarding.presentation.wallet2.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
|
|
@ -16,6 +17,7 @@ import com.tangem.core.ui.components.PrimaryButton
|
|||
import com.tangem.core.ui.components.SpacerH16
|
||||
import com.tangem.core.ui.components.buttons.segmentedbutton.SegmentedButtons
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.feature.onboarding.R
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.model.*
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.ui.components.DescriptionSubTitleText
|
||||
|
|
@ -105,7 +107,7 @@ private fun SegmentSeedBlock(state: SegmentSeedState, modifier: Modifier = Modif
|
|||
it.count,
|
||||
),
|
||||
modifier = Modifier
|
||||
.padding(vertical = TangemTheme.dimens.spacing16)
|
||||
.padding(vertical = TangemTheme.dimens.spacing10)
|
||||
.fillMaxWidth(),
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
|
|
@ -163,10 +165,11 @@ private inline fun <T> VerticalGrid(
|
|||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Preview(widthDp = 360, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun YourSeedPhraseScreenPreview_Light() {
|
||||
TangemTheme(isDark = false) {
|
||||
TangemThemePreview {
|
||||
YourSeedPhraseScreen(
|
||||
state = YourSeedPhraseState(
|
||||
segmentSeedState = SegmentSeedState(
|
||||
|
|
|
|||
|
|
@ -20,9 +20,9 @@ import androidx.lifecycle.lifecycleScope
|
|||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||
import com.google.mlkit.vision.common.InputImage
|
||||
import com.tangem.core.ui.UiDependencies
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.screen.ComposeFragment
|
||||
import com.tangem.core.ui.theme.AppThemeModeHolder
|
||||
import com.tangem.feature.qrscanning.inner.MLKitBarcodeAnalyzer
|
||||
import com.tangem.feature.qrscanning.navigation.QrScanningInnerRouter
|
||||
import com.tangem.feature.qrscanning.presentation.QrScanningContent
|
||||
|
|
@ -39,7 +39,7 @@ import kotlin.properties.Delegates
|
|||
internal class QrScanningFragment : ComposeFragment() {
|
||||
|
||||
@Inject
|
||||
override lateinit var appThemeModeHolder: AppThemeModeHolder
|
||||
override lateinit var uiDependencies: UiDependencies
|
||||
|
||||
@Inject
|
||||
lateinit var router: QrScanningRouter
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ dependencies {
|
|||
|
||||
/** Domain modules */
|
||||
implementation(projects.domain.legacy)
|
||||
implementation(projects.libs.blockchainSdk)
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.wallets.models)
|
||||
|
|
|
|||
|
|
@ -2,12 +2,13 @@ package com.tangem.feature.referral.data
|
|||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.data.tokens.utils.CryptoCurrencyFactory
|
||||
import com.tangem.datasource.api.common.AuthProvider
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.api.tangemTech.models.StartReferralBody
|
||||
import com.tangem.datasource.demo.DemoModeDatasource
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.common.extensions.fromNetworkId
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
|
@ -15,7 +16,6 @@ import com.tangem.feature.referral.converters.ReferralConverter
|
|||
import com.tangem.feature.referral.domain.ReferralRepository
|
||||
import com.tangem.feature.referral.domain.models.ReferralData
|
||||
import com.tangem.feature.referral.domain.models.TokenData
|
||||
import com.tangem.lib.auth.AuthProvider
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
package com.tangem.feature.referral.di
|
||||
|
||||
import com.tangem.datasource.api.common.AuthProvider
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.demo.DemoModeDatasource
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.feature.referral.converters.ReferralConverter
|
||||
import com.tangem.feature.referral.data.ReferralRepositoryImpl
|
||||
import com.tangem.feature.referral.domain.ReferralRepository
|
||||
import com.tangem.lib.auth.AuthProvider
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@ import androidx.compose.foundation.layout.systemBarsPadding
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.fragment.app.viewModels
|
||||
import com.tangem.core.ui.UiDependencies
|
||||
import com.tangem.core.ui.components.SystemBarsEffect
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.screen.ComposeFragment
|
||||
import com.tangem.core.ui.theme.AppThemeModeHolder
|
||||
import com.tangem.feature.referral.router.ReferralRouter
|
||||
import com.tangem.feature.referral.ui.ReferralScreen
|
||||
import com.tangem.feature.referral.viewmodels.ReferralViewModel
|
||||
|
|
@ -20,7 +20,7 @@ import javax.inject.Inject
|
|||
class ReferralFragment : ComposeFragment() {
|
||||
|
||||
@Inject
|
||||
override lateinit var appThemeModeHolder: AppThemeModeHolder
|
||||
override lateinit var uiDependencies: UiDependencies
|
||||
|
||||
private val viewModel by viewModels<ReferralViewModel>()
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.feature.referral.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
|
|
@ -16,6 +17,7 @@ import androidx.compose.ui.unit.dp
|
|||
import com.google.accompanist.web.WebView
|
||||
import com.google.accompanist.web.rememberWebViewState
|
||||
import com.tangem.core.ui.components.appbar.AppBarWithAdditionalButtons
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.referral.presentation.R
|
||||
|
||||
|
|
@ -58,17 +60,10 @@ private fun AgreementHtmlView(url: String) {
|
|||
}
|
||||
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_AgreementBottomSheet_InLightTheme() {
|
||||
TangemTheme(isDark = false) {
|
||||
AgreementBottomSheetContent(url = "https://tangem.com/en/")
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_AgreementBottomSheet_InDarkTheme() {
|
||||
TangemTheme(isDark = true) {
|
||||
private fun Preview_AgreementBottomSheet() {
|
||||
TangemThemePreview {
|
||||
AgreementBottomSheetContent(url = "https://tangem.com/en/")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.feature.referral.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
|
|
@ -15,6 +16,7 @@ import androidx.compose.ui.text.buildAnnotatedString
|
|||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.referral.presentation.R
|
||||
|
||||
|
|
@ -53,19 +55,10 @@ private fun annotatedAgreementString(firstPart: String): AnnotatedString {
|
|||
}
|
||||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Preview(widthDp = 360, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_AgreementText_InLightTheme() {
|
||||
TangemTheme(isDark = false) {
|
||||
Box(modifier = Modifier.background(TangemTheme.colors.background.primary)) {
|
||||
AgreementText(firstPartResId = R.string.referral_tos_not_enroled_prefix, onClick = {})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Composable
|
||||
private fun Preview_AgreementText_InDarkTheme() {
|
||||
TangemTheme(isDark = true) {
|
||||
private fun Preview_AgreementText() {
|
||||
TangemThemePreview {
|
||||
Box(modifier = Modifier.background(TangemTheme.colors.background.primary)) {
|
||||
AgreementText(firstPartResId = R.string.referral_tos_not_enroled_prefix, onClick = {})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import androidx.compose.ui.graphics.Color
|
|||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
|
|
@ -56,8 +57,8 @@ internal fun AwardText(
|
|||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Composable
|
||||
private fun Preview_AwardItem_Light() {
|
||||
TangemTheme {
|
||||
private fun Preview_AwardItem() {
|
||||
TangemThemePreview {
|
||||
AwardText(
|
||||
startText = "startText",
|
||||
startTextColor = TangemTheme.colors.text.tertiary,
|
||||
|
|
|
|||
|
|
@ -1,14 +1,17 @@
|
|||
package com.tangem.feature.referral.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.components.PrimaryEndIconButton
|
||||
import com.tangem.core.ui.components.PrimaryButtonIconEnd
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.feature.referral.presentation.R
|
||||
|
||||
@Composable
|
||||
|
|
@ -18,29 +21,23 @@ internal fun NonParticipateBottomBlock(onAgreementClick: () -> Unit, onParticipa
|
|||
firstPartResId = R.string.referral_tos_not_enroled_prefix,
|
||||
onClick = onAgreementClick,
|
||||
)
|
||||
PrimaryEndIconButton(
|
||||
modifier = Modifier.padding(all = TangemTheme.dimens.spacing16),
|
||||
|
||||
PrimaryButtonIconEnd(
|
||||
text = stringResource(id = R.string.referral_button_participate),
|
||||
iconResId = R.drawable.ic_tangem_24,
|
||||
onClick = onParticipateClick,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(all = TangemTheme.dimens.spacing16),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Preview(widthDp = 360, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_NonParticipateBottomBlock_InLightTheme() {
|
||||
TangemTheme(isDark = false) {
|
||||
Column(Modifier.background(TangemTheme.colors.background.primary)) {
|
||||
NonParticipateBottomBlock(onAgreementClick = {}, onParticipateClick = {})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Composable
|
||||
private fun Preview_NonParticipateBottomBlock_InDarkTheme() {
|
||||
TangemTheme(isDark = true) {
|
||||
private fun Preview_NonParticipateBottomBlock() {
|
||||
TangemThemePreview {
|
||||
Column(Modifier.background(TangemTheme.colors.background.primary)) {
|
||||
NonParticipateBottomBlock(onAgreementClick = {}, onParticipateClick = {})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,19 +2,14 @@ package com.tangem.feature.referral.ui
|
|||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Divider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.shadow
|
||||
|
|
@ -30,11 +25,13 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import androidx.core.content.ContextCompat.startActivity
|
||||
import com.tangem.core.ui.components.*
|
||||
import com.tangem.core.ui.components.PrimaryButtonIconStart
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.feature.referral.domain.models.ExpectedAward
|
||||
import com.tangem.feature.referral.domain.models.ExpectedAwards
|
||||
import com.tangem.feature.referral.presentation.R
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
|
|
@ -43,8 +40,8 @@ internal fun ParticipateBottomBlock(
|
|||
code: String,
|
||||
shareLink: String,
|
||||
expectedAwards: ExpectedAwards?,
|
||||
snackbarHostState: SnackbarHostState,
|
||||
onAgreementClick: () -> Unit,
|
||||
onShowCopySnackbar: () -> Unit,
|
||||
onCopyClick: () -> Unit,
|
||||
onShareClick: () -> Unit,
|
||||
) {
|
||||
|
|
@ -61,7 +58,7 @@ internal fun ParticipateBottomBlock(
|
|||
AdditionalButtons(
|
||||
code = code,
|
||||
shareLink = shareLink,
|
||||
onShowCopySnackbar = onShowCopySnackbar,
|
||||
snackbarHostState = snackbarHostState,
|
||||
onCopyClick = onCopyClick,
|
||||
onShareClick = onShareClick,
|
||||
)
|
||||
|
|
@ -110,9 +107,9 @@ private fun Awards(expectedAwards: ExpectedAwards) {
|
|||
val elementsCountToShowInLessMode = 3
|
||||
val isExpanded = remember { mutableStateOf(false) }
|
||||
|
||||
Divider(
|
||||
color = TangemTheme.colors.stroke.primary,
|
||||
HorizontalDivider(
|
||||
thickness = TangemTheme.dimens.size0_5,
|
||||
color = TangemTheme.colors.stroke.primary,
|
||||
)
|
||||
AwardText(
|
||||
startText = if (expectedAwards.expectedAwards.isNotEmpty()) {
|
||||
|
|
@ -249,7 +246,7 @@ private fun PersonalCodeCard(code: String) {
|
|||
.fillMaxWidth()
|
||||
.padding(vertical = TangemTheme.dimens.spacing12),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4),
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.referral_promo_code_title),
|
||||
|
|
@ -270,32 +267,40 @@ private fun PersonalCodeCard(code: String) {
|
|||
private fun AdditionalButtons(
|
||||
code: String,
|
||||
shareLink: String,
|
||||
onShowCopySnackbar: () -> Unit,
|
||||
snackbarHostState: SnackbarHostState,
|
||||
onCopyClick: () -> Unit,
|
||||
onShareClick: () -> Unit,
|
||||
) {
|
||||
val clipboardManager = LocalClipboardManager.current
|
||||
val hapticFeedback = LocalHapticFeedback.current
|
||||
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val resources = LocalContext.current.resources
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing16),
|
||||
) {
|
||||
PrimaryStartIconButton(
|
||||
modifier = Modifier.weight(1f),
|
||||
PrimaryButtonIconStart(
|
||||
text = stringResource(id = R.string.common_copy),
|
||||
iconResId = R.drawable.ic_copy_24,
|
||||
onClick = {
|
||||
onCopyClick.invoke()
|
||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
clipboardManager.setText(AnnotatedString(code))
|
||||
onShowCopySnackbar()
|
||||
|
||||
coroutineScope.launch {
|
||||
snackbarHostState.showSnackbar(
|
||||
message = resources.getString(R.string.referral_promo_code_copied),
|
||||
duration = SnackbarDuration.Short,
|
||||
)
|
||||
}
|
||||
},
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
|
||||
val context = LocalContext.current
|
||||
PrimaryStartIconButton(
|
||||
modifier = Modifier.weight(1f),
|
||||
PrimaryButtonIconStart(
|
||||
text = stringResource(id = R.string.common_share),
|
||||
iconResId = R.drawable.ic_share_24,
|
||||
onClick = {
|
||||
|
|
@ -303,6 +308,7 @@ private fun AdditionalButtons(
|
|||
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
context.shareText(context.getString(R.string.referral_share_link, shareLink))
|
||||
},
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -318,11 +324,12 @@ private fun Context.shareText(text: String) {
|
|||
}
|
||||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Preview(widthDp = 360, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun ParticipateBottomBlockPreview_Light(
|
||||
private fun ParticipateBottomBlockPreview(
|
||||
@PreviewParameter(ParticipateBottomBlockDataProvider::class) data: ParticipateBottomBlockData,
|
||||
) {
|
||||
TangemTheme(isDark = false) {
|
||||
TangemThemePreview {
|
||||
Column(Modifier.background(TangemTheme.colors.background.secondary)) {
|
||||
ParticipateBottomBlock(
|
||||
purchasedWalletCount = data.purchasedWalletCount,
|
||||
|
|
@ -330,7 +337,7 @@ private fun ParticipateBottomBlockPreview_Light(
|
|||
shareLink = data.shareLink,
|
||||
expectedAwards = data.expectedAwards,
|
||||
onAgreementClick = data.onAgreementClick,
|
||||
onShowCopySnackbar = data.onShowCopySnackbar,
|
||||
snackbarHostState = SnackbarHostState(),
|
||||
onCopyClick = data.onCopyClick,
|
||||
onShareClick = data.onShareClick,
|
||||
)
|
||||
|
|
@ -339,42 +346,10 @@ private fun ParticipateBottomBlockPreview_Light(
|
|||
}
|
||||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Preview(widthDp = 360, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun ParticipateBottomBlockPreview_Dark(
|
||||
@PreviewParameter(ParticipateBottomBlockDataProvider::class) state: ParticipateBottomBlockData,
|
||||
) {
|
||||
TangemTheme(isDark = true) {
|
||||
Column(Modifier.background(TangemTheme.colors.background.secondary)) {
|
||||
ParticipateBottomBlock(
|
||||
purchasedWalletCount = state.purchasedWalletCount,
|
||||
code = state.code,
|
||||
shareLink = state.shareLink,
|
||||
expectedAwards = state.expectedAwards,
|
||||
onAgreementClick = state.onAgreementClick,
|
||||
onShowCopySnackbar = state.onShowCopySnackbar,
|
||||
onCopyClick = state.onCopyClick,
|
||||
onShareClick = state.onShareClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Composable
|
||||
private fun LessMoreButton_Light() {
|
||||
TangemTheme(isDark = false) {
|
||||
LessMoreButton(
|
||||
isExpanded = remember {
|
||||
mutableStateOf(false)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Composable
|
||||
private fun LessMoreButton_Dark() {
|
||||
TangemTheme(isDark = true) {
|
||||
private fun LessMoreButtonPreview() {
|
||||
TangemThemePreview {
|
||||
LessMoreButton(
|
||||
isExpanded = remember {
|
||||
mutableStateOf(false)
|
||||
|
|
@ -420,7 +395,6 @@ private class ParticipateBottomBlockDataProvider : CollectionPreviewParameterPro
|
|||
purchasedWalletCount = 0,
|
||||
expectedAwards = null,
|
||||
),
|
||||
|
||||
),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package com.tangem.feature.referral.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import android.content.res.Resources
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
|
|
@ -10,9 +12,7 @@ import androidx.compose.material3.*
|
|||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
|
|
@ -21,13 +21,15 @@ import androidx.compose.ui.text.buildAnnotatedString
|
|||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
import com.tangem.core.ui.components.SpacerH16
|
||||
import com.tangem.core.ui.components.SpacerH24
|
||||
import com.tangem.core.ui.components.SpacerH32
|
||||
import com.tangem.core.ui.components.appbar.AppBarWithBackButton
|
||||
import com.tangem.core.ui.components.snackbar.CopiedTextSnackbar
|
||||
import com.tangem.core.ui.components.snackbar.TangemSnackbar
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.feature.referral.domain.models.ExpectedAward
|
||||
import com.tangem.feature.referral.domain.models.ExpectedAwards
|
||||
import com.tangem.feature.referral.models.DemoModeException
|
||||
|
|
@ -48,6 +50,8 @@ internal fun ReferralScreen(stateHolder: ReferralStateHolder, modifier: Modifier
|
|||
var isBottomSheetVisible by remember { mutableStateOf(value = false) }
|
||||
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
||||
|
||||
val snackbarHostState = remember(::SnackbarHostState)
|
||||
|
||||
Scaffold(
|
||||
modifier = modifier,
|
||||
topBar = {
|
||||
|
|
@ -56,10 +60,26 @@ internal fun ReferralScreen(stateHolder: ReferralStateHolder, modifier: Modifier
|
|||
onBackClick = stateHolder.headerState.onBackClicked,
|
||||
)
|
||||
},
|
||||
snackbarHost = {
|
||||
SnackbarHost(
|
||||
hostState = snackbarHostState,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||
.padding(bottom = TangemTheme.dimens.spacing108),
|
||||
) {
|
||||
// TODO: use StateEvent
|
||||
if (stateHolder.errorSnackbar != null) {
|
||||
TangemSnackbar(data = it, actionOnNewLine = true)
|
||||
} else {
|
||||
CopiedTextSnackbar(it)
|
||||
}
|
||||
}
|
||||
},
|
||||
containerColor = TangemTheme.colors.background.secondary,
|
||||
) {
|
||||
ReferralContent(
|
||||
stateHolder = stateHolder,
|
||||
snackbarHostState = snackbarHostState,
|
||||
onAgreementClick = {
|
||||
stateHolder.analytics.onAgreementClicked.invoke()
|
||||
isBottomSheetVisible = true
|
||||
|
|
@ -68,6 +88,26 @@ internal fun ReferralScreen(stateHolder: ReferralStateHolder, modifier: Modifier
|
|||
)
|
||||
}
|
||||
|
||||
val errorSnackbar = stateHolder.errorSnackbar
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val resources = LocalContext.current.resources
|
||||
|
||||
SideEffect {
|
||||
if (errorSnackbar != null) {
|
||||
coroutineScope.launch {
|
||||
val result = snackbarHostState.showSnackbar(
|
||||
message = resources.getMessageForErrorSnackbar(errorSnackbar.throwable),
|
||||
actionLabel = resources.getString(R.string.warning_button_ok),
|
||||
duration = SnackbarDuration.Indefinite,
|
||||
)
|
||||
|
||||
if (result == SnackbarResult.ActionPerformed) {
|
||||
errorSnackbar.onOkClicked()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ReferralBottomSheet(
|
||||
sheetState = sheetState,
|
||||
isVisible = isBottomSheetVisible,
|
||||
|
|
@ -79,11 +119,10 @@ internal fun ReferralScreen(stateHolder: ReferralStateHolder, modifier: Modifier
|
|||
@Composable
|
||||
private fun ReferralContent(
|
||||
stateHolder: ReferralStateHolder,
|
||||
snackbarHostState: SnackbarHostState,
|
||||
onAgreementClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val isCopyButtonPressed = remember { mutableStateOf(value = false) }
|
||||
|
||||
Box(modifier = modifier) {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
|
|
@ -93,14 +132,11 @@ private fun ReferralContent(
|
|||
item {
|
||||
ReferralInfo(
|
||||
stateHolder = stateHolder,
|
||||
snackbarHostState = snackbarHostState,
|
||||
onAgreementClick = onAgreementClick,
|
||||
onShowCopySnackbar = { isCopyButtonPressed.value = true },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
ErrorSnackbarHost(errorSnackbar = stateHolder.errorSnackbar)
|
||||
CopySnackbarHost(isCopyButtonPressed = isCopyButtonPressed)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -131,8 +167,8 @@ private fun Header() {
|
|||
@Composable
|
||||
private fun ReferralInfo(
|
||||
stateHolder: ReferralStateHolder,
|
||||
snackbarHostState: SnackbarHostState,
|
||||
onAgreementClick: () -> Unit,
|
||||
onShowCopySnackbar: () -> Unit,
|
||||
) {
|
||||
when (val state = stateHolder.referralInfoState) {
|
||||
is ReferralInfoState.ParticipantContent -> {
|
||||
|
|
@ -142,8 +178,8 @@ private fun ReferralInfo(
|
|||
code = state.code,
|
||||
shareLink = state.shareLink,
|
||||
expectedAwards = state.expectedAwards,
|
||||
snackbarHostState = snackbarHostState,
|
||||
onAgreementClick = onAgreementClick,
|
||||
onShowCopySnackbar = onShowCopySnackbar,
|
||||
onCopyClick = stateHolder.analytics.onCopyClicked,
|
||||
onShareClick = stateHolder.analytics.onShareClicked,
|
||||
)
|
||||
|
|
@ -333,122 +369,19 @@ private fun ShimmerInfo() {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO() Replace component with component from ds
|
||||
@Composable
|
||||
private fun BoxScope.ErrorSnackbarHost(errorSnackbar: ErrorSnackbar?) {
|
||||
if (errorSnackbar != null) {
|
||||
val snackbarHostState by remember { mutableStateOf(SnackbarHostState()) }
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
SnackbarHost(
|
||||
hostState = snackbarHostState,
|
||||
modifier = Modifier.align(Alignment.BottomCenter),
|
||||
snackbar = {
|
||||
Snackbar(
|
||||
snackbarData = it,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
actionOnNewLine = true,
|
||||
shape = RoundedCornerShape(size = TangemTheme.dimens.radius8),
|
||||
containerColor = TangemTheme.colors.button.primary,
|
||||
contentColor = TangemTheme.colors.text.primary2,
|
||||
actionColor = TangemTheme.colors.text.primary2,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
val actionLabel = stringResource(id = R.string.warning_button_ok)
|
||||
val message = getMessageForErrorSnackbar(errorSnackbar)
|
||||
SideEffect {
|
||||
coroutineScope.launch {
|
||||
val result = snackbarHostState.showSnackbar(
|
||||
message = message,
|
||||
actionLabel = actionLabel,
|
||||
duration = SnackbarDuration.Indefinite,
|
||||
)
|
||||
if (result == SnackbarResult.ActionPerformed) {
|
||||
errorSnackbar.onOkClicked()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO() Replace component with component from ds
|
||||
@Composable
|
||||
private fun BoxScope.CopySnackbarHost(isCopyButtonPressed: MutableState<Boolean>) {
|
||||
if (isCopyButtonPressed.value) {
|
||||
val snackbarHostState by remember { mutableStateOf(SnackbarHostState()) }
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
var snackbarSize by remember { mutableIntStateOf(value = 0) }
|
||||
val width = LocalConfiguration.current.screenWidthDp.dp
|
||||
val snackbarWidth = with(LocalDensity.current) { snackbarSize.toDp() }
|
||||
|
||||
SnackbarHost(
|
||||
hostState = snackbarHostState,
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.padding(start = (width - snackbarWidth).div(2), bottom = TangemTheme.dimens.spacing12)
|
||||
.fillMaxWidth(),
|
||||
snackbar = {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.onSizeChanged { snackbarSize = it.width }
|
||||
.background(
|
||||
color = TangemTheme.colors.icon.primary1,
|
||||
shape = RoundedCornerShape(size = TangemTheme.dimens.radius8),
|
||||
)
|
||||
.padding(
|
||||
horizontal = TangemTheme.dimens.spacing16,
|
||||
vertical = TangemTheme.dimens.spacing14,
|
||||
),
|
||||
) {
|
||||
Text(
|
||||
text = it.visuals.message,
|
||||
color = TangemTheme.colors.text.primary2,
|
||||
style = TangemTheme.typography.body2,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
val message = stringResource(id = R.string.referral_promo_code_copied)
|
||||
SideEffect {
|
||||
coroutineScope.launch {
|
||||
snackbarHostState.showSnackbar(
|
||||
message = message,
|
||||
duration = SnackbarDuration.Short,
|
||||
)
|
||||
isCopyButtonPressed.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun getMessageForErrorSnackbar(errorSnackbar: ErrorSnackbar): String {
|
||||
return when (errorSnackbar.throwable) {
|
||||
is DemoModeException -> {
|
||||
stringResource(id = R.string.alert_demo_feature_disabled)
|
||||
}
|
||||
|
||||
else -> {
|
||||
if (errorSnackbar.throwable.cause != null) {
|
||||
String.format(
|
||||
format = stringResource(id = R.string.referral_error_failed_to_load_info_with_reason),
|
||||
errorSnackbar.throwable.cause,
|
||||
)
|
||||
} else {
|
||||
stringResource(id = R.string.referral_error_failed_to_load_info)
|
||||
}
|
||||
}
|
||||
private fun Resources.getMessageForErrorSnackbar(throwable: Throwable): String {
|
||||
return when {
|
||||
throwable is DemoModeException -> getString(R.string.alert_demo_feature_disabled)
|
||||
throwable.cause != null -> getString(R.string.referral_error_failed_to_load_info_with_reason, throwable.cause)
|
||||
else -> getString(R.string.referral_error_failed_to_load_info)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Preview(widthDp = 360, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_ReferralScreen_Participant_InLightTheme() {
|
||||
TangemTheme(isDark = false) {
|
||||
private fun Preview_ReferralScreen_Participant() {
|
||||
TangemThemePreview {
|
||||
ReferralScreen(
|
||||
stateHolder = ReferralStateHolder(
|
||||
headerState = HeaderState(onBackClicked = {}),
|
||||
|
|
@ -475,38 +408,10 @@ private fun Preview_ReferralScreen_Participant_InLightTheme() {
|
|||
}
|
||||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Preview(widthDp = 360, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_ReferralScreen_Participant_InDarkTheme() {
|
||||
TangemTheme(isDark = true) {
|
||||
ReferralScreen(
|
||||
stateHolder = ReferralStateHolder(
|
||||
headerState = HeaderState(onBackClicked = {}),
|
||||
referralInfoState = ReferralInfoState.ParticipantContent(
|
||||
award = "10 USDT",
|
||||
networkName = "Tron",
|
||||
address = "ma80...zk8q2",
|
||||
discount = "10%",
|
||||
purchasedWalletCount = 3,
|
||||
code = "x4JdK",
|
||||
shareLink = "",
|
||||
url = "",
|
||||
expectedAwards = null,
|
||||
),
|
||||
errorSnackbar = ErrorSnackbar(DemoModeException()) {},
|
||||
analytics = Analytics(
|
||||
onAgreementClicked = {},
|
||||
onCopyClicked = {},
|
||||
onShareClicked = {},
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Composable
|
||||
private fun Preview_ReferralScreen_Participant_With_Referrals_InLightTheme() {
|
||||
TangemTheme(isDark = false) {
|
||||
private fun Preview_ReferralScreen_Participant_With_Referrals() {
|
||||
TangemThemePreview {
|
||||
ReferralScreen(
|
||||
stateHolder = ReferralStateHolder(
|
||||
headerState = HeaderState(onBackClicked = {}),
|
||||
|
|
@ -549,9 +454,10 @@ private fun Preview_ReferralScreen_Participant_With_Referrals_InLightTheme() {
|
|||
}
|
||||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Preview(widthDp = 360, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_ReferralScreen_NonParticipant_InLightTheme() {
|
||||
TangemTheme(isDark = false) {
|
||||
private fun Preview_ReferralScreen_NonParticipant() {
|
||||
TangemThemePreview {
|
||||
ReferralScreen(
|
||||
stateHolder = ReferralStateHolder(
|
||||
headerState = HeaderState(onBackClicked = {}),
|
||||
|
|
@ -574,53 +480,10 @@ private fun Preview_ReferralScreen_NonParticipant_InLightTheme() {
|
|||
}
|
||||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Preview(widthDp = 360, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_ReferralScreen_NonParticipant_InDarkTheme() {
|
||||
TangemTheme(isDark = true) {
|
||||
ReferralScreen(
|
||||
stateHolder = ReferralStateHolder(
|
||||
headerState = HeaderState(onBackClicked = {}),
|
||||
referralInfoState = ReferralInfoState.NonParticipantContent(
|
||||
award = "10 USDT",
|
||||
networkName = "Tron",
|
||||
discount = "10%",
|
||||
url = "",
|
||||
onParticipateClicked = {},
|
||||
),
|
||||
errorSnackbar = null,
|
||||
analytics = Analytics(
|
||||
onAgreementClicked = {},
|
||||
onCopyClicked = {},
|
||||
onShareClicked = {},
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Composable
|
||||
private fun Preview_ReferralScreen_Loading_InLightTheme() {
|
||||
TangemTheme(isDark = false) {
|
||||
ReferralScreen(
|
||||
stateHolder = ReferralStateHolder(
|
||||
headerState = HeaderState(onBackClicked = {}),
|
||||
referralInfoState = ReferralInfoState.Loading,
|
||||
errorSnackbar = null,
|
||||
analytics = Analytics(
|
||||
onAgreementClicked = {},
|
||||
onCopyClicked = {},
|
||||
onShareClicked = {},
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Composable
|
||||
private fun Preview_ReferralScreen_Loading_InDarkTheme() {
|
||||
TangemTheme(isDark = true) {
|
||||
private fun Preview_ReferralScreen_Loading() {
|
||||
TangemThemePreview {
|
||||
ReferralScreen(
|
||||
stateHolder = ReferralStateHolder(
|
||||
headerState = HeaderState(onBackClicked = {}),
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ dependencies {
|
|||
/** Domain modules */
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.legacy)
|
||||
implementation(projects.libs.blockchainSdk)
|
||||
implementation(projects.domain.tokens)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.wallets)
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.ui.UiDependencies
|
||||
import com.tangem.core.ui.components.SystemBarsEffect
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.screen.ComposeFragment
|
||||
import com.tangem.core.ui.theme.AppThemeModeHolder
|
||||
import com.tangem.features.send.api.navigation.SendRouter
|
||||
import com.tangem.features.send.impl.navigation.InnerSendRouter
|
||||
import com.tangem.features.send.impl.presentation.state.StateRouter
|
||||
|
|
@ -26,7 +26,7 @@ import javax.inject.Inject
|
|||
internal class SendFragment : ComposeFragment() {
|
||||
|
||||
@Inject
|
||||
override lateinit var appThemeModeHolder: AppThemeModeHolder
|
||||
override lateinit var uiDependencies: UiDependencies
|
||||
|
||||
@Inject
|
||||
lateinit var router: SendRouter
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
package com.tangem.features.send.impl.presentation.state
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.core.ui.components.notifications.NotificationConfig
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.features.send.impl.R
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal sealed class SendNotification(val config: NotificationConfig) {
|
||||
|
||||
|
|
@ -162,4 +165,70 @@ internal sealed class SendNotification(val config: NotificationConfig) {
|
|||
),
|
||||
)
|
||||
}
|
||||
|
||||
sealed interface Cardano {
|
||||
|
||||
data class MinAdaValueCharged(val tokenName: String, val minAdaValue: String) : Warning(
|
||||
title = resourceReference(id = R.string.cardano_coin_will_be_send_with_token_title),
|
||||
subtitle = resourceReference(
|
||||
id = R.string.cardano_coin_will_be_send_with_token_description,
|
||||
formatArgs = wrappedList(minAdaValue, tokenName),
|
||||
),
|
||||
)
|
||||
|
||||
data object InsufficientBalanceToTransferCoin : Error(
|
||||
title = resourceReference(id = R.string.cardano_max_amount_has_token_title),
|
||||
subtitle = resourceReference(id = R.string.cardano_max_amount_has_token_description),
|
||||
)
|
||||
|
||||
data class InsufficientBalanceToTransferToken(val tokenName: String) : Error(
|
||||
title = resourceReference(id = R.string.cardano_insufficient_balance_to_send_token_title),
|
||||
subtitle = resourceReference(
|
||||
id = R.string.cardano_insufficient_balance_to_send_token_description,
|
||||
formatArgs = wrappedList(tokenName),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
sealed interface Koinos {
|
||||
data class InsufficientRecoverableMana(
|
||||
val mana: BigDecimal,
|
||||
val maxMana: BigDecimal,
|
||||
) : Error(
|
||||
title = resourceReference(R.string.koinos_insufficient_mana_to_send_koin_title),
|
||||
subtitle = resourceReference(
|
||||
R.string.koinos_insufficient_mana_to_send_koin_description,
|
||||
formatArgs = wrappedList(
|
||||
BigDecimalFormatter.formatCryptoAmountShorted(mana, "", Blockchain.Koinos.decimals()),
|
||||
BigDecimalFormatter.formatCryptoAmountShorted(maxMana, "", Blockchain.Koinos.decimals()),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
data object InsufficientBalance : Error(
|
||||
title = resourceReference(R.string.koinos_insufficient_balance_to_send_koin_title),
|
||||
subtitle = resourceReference(R.string.koinos_insufficient_balance_to_send_koin_description),
|
||||
)
|
||||
|
||||
data class ManaExceedsBalance(
|
||||
val availableKoinForTransfer: BigDecimal,
|
||||
val onReduceClick: () -> Unit,
|
||||
) : Error(
|
||||
title = resourceReference(R.string.koinos_mana_exceeds_koin_balance_title),
|
||||
subtitle = resourceReference(
|
||||
R.string.koinos_mana_exceeds_koin_balance_description,
|
||||
formatArgs = wrappedList(
|
||||
BigDecimalFormatter.formatCryptoAmount(
|
||||
availableKoinForTransfer,
|
||||
Blockchain.Koinos.currency,
|
||||
Blockchain.Koinos.decimals(),
|
||||
),
|
||||
),
|
||||
),
|
||||
buttonState = NotificationConfig.ButtonsState.PrimaryButtonConfig(
|
||||
text = resourceReference(R.string.send_notification_reduce_to, wrappedList(availableKoinForTransfer)),
|
||||
onClick = onReduceClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +1,11 @@
|
|||
package com.tangem.features.send.impl.presentation.state
|
||||
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.blockchain.common.TransactionData
|
||||
import com.tangem.core.ui.components.currency.tokenicon.converter.CryptoCurrencyToIconStateConverter
|
||||
import com.tangem.core.ui.event.consumedEvent
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.txhistory.models.TxHistoryItem
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.usecase.ValidateWalletMemoUseCase
|
||||
import com.tangem.features.send.impl.R
|
||||
import com.tangem.features.send.impl.presentation.domain.AvailableWallet
|
||||
import com.tangem.features.send.impl.presentation.state.amount.SendAmountStateConverter
|
||||
import com.tangem.features.send.impl.presentation.state.common.SendSyncEditConverter
|
||||
import com.tangem.features.send.impl.presentation.state.confirm.SendConfirmStateConverter
|
||||
|
|
@ -20,18 +13,14 @@ import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState
|
|||
import com.tangem.features.send.impl.presentation.state.fee.SendFeeStateConverter
|
||||
import com.tangem.features.send.impl.presentation.state.fee.checkFeeCoverage
|
||||
import com.tangem.features.send.impl.presentation.state.fields.SendAmountFieldConverter
|
||||
import com.tangem.features.send.impl.presentation.state.recipient.SendRecipientHistoryListConverter
|
||||
import com.tangem.features.send.impl.presentation.state.recipient.SendRecipientStateConverter
|
||||
import com.tangem.features.send.impl.presentation.state.recipient.SendRecipientWalletListConverter
|
||||
import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents
|
||||
import com.tangem.utils.Provider
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
|
||||
@Suppress("LongParameterList", "LargeClass")
|
||||
@Suppress("LongParameterList")
|
||||
internal class SendStateFactory(
|
||||
private val clickIntents: SendClickIntents,
|
||||
private val stateRouterProvider: Provider<StateRouter>,
|
||||
|
|
@ -41,7 +30,6 @@ internal class SendStateFactory(
|
|||
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus>,
|
||||
private val feeCryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus?>,
|
||||
private val isTapHelpPreviewEnabledProvider: Provider<Boolean>,
|
||||
private val validateWalletMemoUseCase: ValidateWalletMemoUseCase,
|
||||
) {
|
||||
private val iconStateConverter by lazy(::CryptoCurrencyToIconStateConverter)
|
||||
|
||||
|
|
@ -72,6 +60,7 @@ internal class SendStateFactory(
|
|||
SendFeeStateConverter(
|
||||
appCurrencyProvider = appCurrencyProvider,
|
||||
feeCryptoCurrencyStatusProvider = feeCryptoCurrencyStatusProvider,
|
||||
cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider,
|
||||
)
|
||||
}
|
||||
private val confirmStateConverter by lazy(LazyThreadSafetyMode.NONE) {
|
||||
|
|
@ -79,14 +68,6 @@ internal class SendStateFactory(
|
|||
isTapHelpPreviewEnabledProvider = isTapHelpPreviewEnabledProvider,
|
||||
)
|
||||
}
|
||||
private val recipientWalletListStateConverter by lazy(LazyThreadSafetyMode.NONE) {
|
||||
SendRecipientWalletListConverter()
|
||||
}
|
||||
private val recipientHistoryListStateConverter by lazy(LazyThreadSafetyMode.NONE) {
|
||||
SendRecipientHistoryListConverter(
|
||||
cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider,
|
||||
)
|
||||
}
|
||||
private val sendSyncEditConverter by lazy(LazyThreadSafetyMode.NONE) {
|
||||
SendSyncEditConverter(currentStateProvider = currentStateProvider)
|
||||
}
|
||||
|
|
@ -132,156 +113,6 @@ internal class SendStateFactory(
|
|||
}
|
||||
//endregion
|
||||
|
||||
//region recipient
|
||||
fun onLoadedWalletsList(wallets: List<AvailableWallet?>): SendUiState {
|
||||
val state = currentStateProvider()
|
||||
return state.copy(
|
||||
recipientState = state.recipientState?.copy(
|
||||
wallets = recipientWalletListStateConverter.convert(wallets),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun onLoadedHistoryList(txHistory: List<TxHistoryItem>): SendUiState {
|
||||
val state = currentStateProvider()
|
||||
return state.copy(
|
||||
recipientState = state.recipientState?.copy(
|
||||
recent = recipientHistoryListStateConverter.convert(txHistory),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun onRecipientAddressValueChange(value: String, isXAddress: Boolean = false): SendUiState {
|
||||
val state = currentStateProvider()
|
||||
val isEditState = stateRouterProvider().isEditState
|
||||
val recipientState = state.getRecipientState(isEditState) ?: return state
|
||||
return state.copyWrapped(
|
||||
isEditState = isEditState,
|
||||
recipientState = recipientState.copy(
|
||||
addressTextField = recipientState.addressTextField.copy(value = value),
|
||||
memoTextField = recipientState.memoTextField?.copy(isEnabled = !isXAddress),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun getOnRecipientAddressValidState(value: String, isValidAddress: Boolean): SendUiState {
|
||||
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
|
||||
val state = currentStateProvider()
|
||||
val isEditState = stateRouterProvider().isEditState
|
||||
val recipientState = state.getRecipientState(isEditState) ?: return state
|
||||
|
||||
val isValidMemo = validateWalletMemoUseCase(
|
||||
memo = recipientState.memoTextField?.value.orEmpty(),
|
||||
network = cryptoCurrencyStatus.currency.network,
|
||||
).getOrElse {
|
||||
Timber.e("Failed to validateWalletMemoUseCase: $it")
|
||||
false
|
||||
}
|
||||
val isAddressInWallet = cryptoCurrencyStatus.value.networkAddress?.availableAddresses
|
||||
?.any { it.value == value } ?: true
|
||||
|
||||
return state.copyWrapped(
|
||||
isEditState = isEditState,
|
||||
recipientState = recipientState.copy(
|
||||
isPrimaryButtonEnabled = isValidMemo && isValidAddress && !isAddressInWallet,
|
||||
isValidating = false,
|
||||
addressTextField = recipientState.addressTextField.copy(
|
||||
error = when {
|
||||
!isValidAddress -> resourceReference(R.string.send_recipient_address_error)
|
||||
isAddressInWallet -> resourceReference(R.string.send_error_address_same_as_wallet)
|
||||
else -> null
|
||||
},
|
||||
isError = value.isNotEmpty() && !isValidAddress || isAddressInWallet,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun getOnRecipientAddressValidationStarted(): SendUiState {
|
||||
val state = currentStateProvider()
|
||||
val isEditState = stateRouterProvider().isEditState
|
||||
val recipientState = state.getRecipientState(isEditState) ?: return state
|
||||
return state.copyWrapped(
|
||||
isEditState = isEditState,
|
||||
recipientState = recipientState.copy(isValidating = true),
|
||||
)
|
||||
}
|
||||
|
||||
fun getOnRecipientMemoValueChange(value: String): SendUiState {
|
||||
val state = currentStateProvider()
|
||||
val isEditState = stateRouterProvider().isEditState
|
||||
val recipientState = state.getRecipientState(isEditState) ?: return state
|
||||
return state.copyWrapped(
|
||||
isEditState = isEditState,
|
||||
recipientState = recipientState.copy(
|
||||
memoTextField = recipientState.memoTextField?.copy(value = value),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun getOnRecipientMemoValidState(value: String, isValidAddress: Boolean): SendUiState {
|
||||
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
|
||||
val state = currentStateProvider()
|
||||
val isEditState = stateRouterProvider().isEditState
|
||||
val recipientState = state.getRecipientState(isEditState) ?: return state
|
||||
|
||||
val isValidMemo = validateWalletMemoUseCase(
|
||||
memo = value,
|
||||
network = cryptoCurrencyStatus.currency.network,
|
||||
).getOrElse {
|
||||
Timber.e("Failed to validateWalletMemoUseCase: $it")
|
||||
false
|
||||
}
|
||||
val isAddressInWallet = cryptoCurrencyStatus.value.networkAddress?.availableAddresses
|
||||
?.any { it.value == value } ?: true
|
||||
|
||||
return state.copyWrapped(
|
||||
isEditState = isEditState,
|
||||
recipientState = recipientState.copy(
|
||||
isPrimaryButtonEnabled = isValidMemo && isValidAddress && !isAddressInWallet,
|
||||
isValidating = false,
|
||||
memoTextField = recipientState.memoTextField?.copy(
|
||||
isError = value.isNotEmpty() && !isValidMemo,
|
||||
isEnabled = true,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun getOnXAddressMemoState(): SendUiState {
|
||||
val state = currentStateProvider()
|
||||
val isEditState = stateRouterProvider().isEditState
|
||||
val recipientState = state.getRecipientState(isEditState) ?: return state
|
||||
return state.copyWrapped(
|
||||
isEditState = isEditState,
|
||||
recipientState = recipientState.copy(
|
||||
memoTextField = recipientState.memoTextField?.copy(
|
||||
value = "",
|
||||
isEnabled = false,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun getHiddenRecentListState(isAddressInWallet: Boolean, isValidAddress: Boolean): SendUiState {
|
||||
val state = currentStateProvider()
|
||||
val isEditState = stateRouterProvider().isEditState
|
||||
val recipientState = state.getRecipientState(isEditState) ?: return state
|
||||
val isNotValid = isAddressInWallet || !isValidAddress
|
||||
return state.copyWrapped(
|
||||
isEditState = isEditState,
|
||||
recipientState = recipientState.copy(
|
||||
recent = recipientState.recent.map { recent ->
|
||||
recent.copy(isVisible = isNotValid && (recent.isLoading || recent.title != TextReference.EMPTY))
|
||||
}.toPersistentList(),
|
||||
wallets = recipientState.wallets.map { wallet ->
|
||||
wallet.copy(isVisible = isNotValid && (wallet.isLoading || wallet.title != TextReference.EMPTY))
|
||||
}.toPersistentList(),
|
||||
),
|
||||
)
|
||||
}
|
||||
//endregion
|
||||
|
||||
//region send
|
||||
fun getIsAmountSubtractedState(isAmountSubtractAvailable: Boolean): SendUiState {
|
||||
val state = currentStateProvider()
|
||||
|
|
@ -335,7 +166,8 @@ internal class SendStateFactory(
|
|||
val reducedBy = sendState.reduceAmountBy.takeIf {
|
||||
notifications.none {
|
||||
it is SendNotification.Error.ExistentialDeposit ||
|
||||
it is SendNotification.Error.TransactionLimitError
|
||||
it is SendNotification.Error.TransactionLimitError ||
|
||||
it is SendNotification.Warning.HighFeeError
|
||||
}
|
||||
}
|
||||
return state.copy(
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ internal data class SendUiState(
|
|||
editAmountState = amountState,
|
||||
editFeeState = feeState,
|
||||
editRecipientState = recipientState,
|
||||
sendState = sendState,
|
||||
)
|
||||
} else {
|
||||
copy(
|
||||
|
|
@ -125,6 +126,7 @@ internal sealed class SendStates {
|
|||
val feeSelectorState: FeeSelectorState,
|
||||
val fee: Fee?,
|
||||
val rate: BigDecimal?,
|
||||
val isFeeConvertibleToFiat: Boolean,
|
||||
val appCurrency: AppCurrency,
|
||||
val isFeeApproximate: Boolean,
|
||||
val isCustomSelected: Boolean,
|
||||
|
|
|
|||
|
|
@ -1,20 +1,24 @@
|
|||
package com.tangem.features.send.impl.presentation.state.confirm
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.BlockchainSdkError
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.blockchainsdk.utils.minimalAmount
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.ui.extensions.networkIconResId
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.core.ui.utils.parseBigDecimal
|
||||
import com.tangem.core.ui.utils.parseToBigDecimal
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.common.extensions.fromNetworkId
|
||||
import com.tangem.domain.common.extensions.minimalAmount
|
||||
import com.tangem.domain.tokens.GetBalanceNotEnoughForFeeWarningUseCase
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning
|
||||
import com.tangem.domain.tokens.repository.CurrencyChecksRepository
|
||||
import com.tangem.domain.tokens.utils.convertToAmount
|
||||
import com.tangem.domain.transaction.usecase.ValidateTransactionUseCase
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.features.send.impl.R
|
||||
import com.tangem.features.send.impl.presentation.analytics.SendAnalyticEvents
|
||||
|
|
@ -23,6 +27,7 @@ import com.tangem.features.send.impl.presentation.state.fee.*
|
|||
import com.tangem.features.send.impl.presentation.state.fields.SendTextField
|
||||
import com.tangem.features.send.impl.presentation.utils.getFiatString
|
||||
import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents
|
||||
import com.tangem.lib.crypto.BlockchainUtils
|
||||
import com.tangem.lib.crypto.BlockchainUtils.isTezos
|
||||
import com.tangem.utils.Provider
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
|
@ -46,6 +51,7 @@ internal class SendNotificationFactory(
|
|||
private val clickIntents: SendClickIntents,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val getBalanceNotEnoughForFeeWarningUseCase: GetBalanceNotEnoughForFeeWarningUseCase,
|
||||
private val validateTransactionUseCase: ValidateTransactionUseCase,
|
||||
) {
|
||||
|
||||
fun create(): Flow<ImmutableList<SendNotification>> = stateRouterProvider().currentState
|
||||
|
|
@ -80,8 +86,9 @@ internal class SendNotificationFactory(
|
|||
addFeeUnreachableNotification(feeState.feeSelectorState)
|
||||
addExceedBalanceNotification(feeValue, sendingAmount)
|
||||
addExceedsBalanceNotification(feeState.fee)
|
||||
addDustWarningNotification(feeValue, sendingAmount)
|
||||
addDustWarningNotificationForSpecificBlockchains(feeValue, sendingAmount)
|
||||
addTransactionLimitErrorNotification(feeValue, sendingAmount)
|
||||
|
||||
// warnings
|
||||
addExistentialWarningNotification(feeValue, amountValue)
|
||||
addFeeCoverageNotification(
|
||||
|
|
@ -92,6 +99,13 @@ internal class SendNotificationFactory(
|
|||
addHighFeeWarningNotification(amountValue, sendState.ignoreAmountReduce)
|
||||
addTooHighNotification(feeState.feeSelectorState)
|
||||
addTooLowNotification(feeState)
|
||||
|
||||
// blockchain specific
|
||||
addValidateTransactionNotifications(
|
||||
sendingAmount = sendingAmount,
|
||||
fee = feeState.fee,
|
||||
state = state,
|
||||
)
|
||||
}.toImmutableList()
|
||||
}
|
||||
|
||||
|
|
@ -280,23 +294,35 @@ internal class SendNotificationFactory(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun MutableList<SendNotification>.addDustWarningNotification(
|
||||
feeAmount: BigDecimal,
|
||||
receivedAmount: BigDecimal,
|
||||
private suspend fun MutableList<SendNotification>.addDustWarningNotificationForSpecificBlockchains(
|
||||
feeValue: BigDecimal,
|
||||
sendingAmount: BigDecimal,
|
||||
) {
|
||||
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
|
||||
val dustValue = currencyChecksRepository.getDustValue(
|
||||
userWalletProvider().walletId,
|
||||
cryptoCurrencyStatus.currency.network,
|
||||
) ?: return
|
||||
val isCardano = BlockchainUtils.isCardano(cryptoCurrencyStatusProvider().currency.network.id.value)
|
||||
|
||||
if (checkDustLimits(feeAmount, receivedAmount, dustValue)) {
|
||||
add(
|
||||
SendNotification.Error.MinimumAmountError(dustValue.toPlainString()),
|
||||
)
|
||||
if (!isCardano) {
|
||||
addDustWarningNotification(feeValue, sendingAmount)
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkDustLimits(feeAmount: BigDecimal, receivedAmount: BigDecimal, dustValue: BigDecimal): Boolean {
|
||||
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
|
||||
|
||||
val change = when (cryptoCurrencyStatus.currency) {
|
||||
is CryptoCurrency.Coin -> {
|
||||
val balance = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO
|
||||
balance - (feeAmount + receivedAmount)
|
||||
}
|
||||
is CryptoCurrency.Token -> {
|
||||
val balance = coinCryptoCurrencyStatusProvider().value.amount ?: BigDecimal.ZERO
|
||||
balance - feeAmount
|
||||
}
|
||||
}
|
||||
|
||||
val isChangeLowerThanDust = change < dustValue && change > BigDecimal.ZERO
|
||||
return receivedAmount < dustValue || isChangeLowerThanDust
|
||||
}
|
||||
|
||||
private fun MutableList<SendNotification>.addTooLowNotification(feeState: SendStates.FeeState) {
|
||||
val feeSelectorState = feeState.feeSelectorState as? FeeSelectorState.Content ?: return
|
||||
val multipleFees = feeSelectorState.fees as? TransactionFee.Choosable ?: return
|
||||
|
|
@ -398,13 +424,123 @@ internal class SendNotificationFactory(
|
|||
return Blockchain.fromNetworkId(this.currency.network.backendId) == Blockchain.Arbitrum
|
||||
}
|
||||
|
||||
private fun checkDustLimits(feeAmount: BigDecimal, receivedAmount: BigDecimal, dustValue: BigDecimal): Boolean {
|
||||
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
|
||||
val balance = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO
|
||||
private suspend fun MutableList<SendNotification>.addValidateTransactionNotifications(
|
||||
sendingAmount: BigDecimal,
|
||||
fee: Fee?,
|
||||
state: SendUiState,
|
||||
) {
|
||||
val sendingCurrency = cryptoCurrencyStatusProvider().currency
|
||||
|
||||
val totalAmount = feeAmount + receivedAmount
|
||||
val change = balance - totalAmount
|
||||
val isChangeLowerThanDust = change < dustValue && change > BigDecimal.ZERO
|
||||
return receivedAmount < dustValue || isChangeLowerThanDust
|
||||
validateTransactionUseCase(
|
||||
amount = sendingAmount.convertToAmount(sendingCurrency),
|
||||
fee = fee ?: return,
|
||||
memo = state.recipientState?.memoTextField?.value,
|
||||
destination = requireNotNull(state.recipientState?.addressTextField?.value),
|
||||
userWalletId = userWalletProvider().walletId,
|
||||
network = sendingCurrency.network,
|
||||
).fold(
|
||||
ifLeft = {
|
||||
when (it) {
|
||||
is BlockchainSdkError.Cardano -> addCardanoTransactionValidationError(
|
||||
error = it,
|
||||
sendingCurrency = sendingCurrency,
|
||||
)
|
||||
is BlockchainSdkError.Koinos -> addKoinosTransactionValidationError(error = it)
|
||||
else -> return
|
||||
}
|
||||
},
|
||||
ifRight = {
|
||||
(fee as? Fee.CardanoToken)?.let {
|
||||
add(
|
||||
SendNotification.Cardano.MinAdaValueCharged(
|
||||
tokenName = sendingCurrency.name,
|
||||
minAdaValue = it.minAdaValue.parseBigDecimal(sendingCurrency.decimals),
|
||||
),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun MutableList<SendNotification>.addCardanoTransactionValidationError(
|
||||
error: BlockchainSdkError.Cardano,
|
||||
sendingCurrency: CryptoCurrency,
|
||||
) {
|
||||
when (error) {
|
||||
BlockchainSdkError.Cardano.InsufficientMinAdaBalanceToSendToken -> {
|
||||
add(SendNotification.Cardano.InsufficientBalanceToTransferToken(sendingCurrency.name))
|
||||
}
|
||||
BlockchainSdkError.Cardano.InsufficientRemainingBalanceToWithdrawTokens -> {
|
||||
when (sendingCurrency) {
|
||||
is CryptoCurrency.Coin -> SendNotification.Cardano.InsufficientBalanceToTransferCoin
|
||||
is CryptoCurrency.Token -> {
|
||||
SendNotification.Cardano.InsufficientBalanceToTransferToken(sendingCurrency.name)
|
||||
}
|
||||
}.let(::add)
|
||||
}
|
||||
BlockchainSdkError.Cardano.InsufficientRemainingBalance,
|
||||
BlockchainSdkError.Cardano.InsufficientSendingAdaAmount,
|
||||
-> {
|
||||
val dustValue = currencyChecksRepository.getDustValue(
|
||||
userWalletId = userWalletProvider().walletId,
|
||||
network = sendingCurrency.network,
|
||||
) ?: return
|
||||
|
||||
add(
|
||||
SendNotification.Error.MinimumAmountError(
|
||||
amount = dustValue.parseBigDecimal(sendingCurrency.decimals),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableList<SendNotification>.addKoinosTransactionValidationError(error: BlockchainSdkError.Koinos) {
|
||||
when (error) {
|
||||
is BlockchainSdkError.Koinos.InsufficientBalance -> {
|
||||
add(SendNotification.Koinos.InsufficientBalance)
|
||||
}
|
||||
is BlockchainSdkError.Koinos.InsufficientMana -> {
|
||||
add(
|
||||
SendNotification.Koinos.InsufficientRecoverableMana(
|
||||
mana = error.manaBalance ?: BigDecimal.ZERO,
|
||||
maxMana = error.maxMana ?: BigDecimal.ZERO,
|
||||
),
|
||||
)
|
||||
}
|
||||
is BlockchainSdkError.Koinos.ManaFeeExceedsBalance -> {
|
||||
add(
|
||||
SendNotification.Koinos.ManaExceedsBalance(
|
||||
availableKoinForTransfer = error.availableKoinForTransfer,
|
||||
onReduceClick = {
|
||||
clickIntents.onAmountReduceClick(
|
||||
reduceAmountTo = error.availableKoinForTransfer,
|
||||
clazz = SendNotification.Koinos.InsufficientRecoverableMana::class.java,
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun MutableList<SendNotification>.addDustWarningNotification(
|
||||
feeValue: BigDecimal,
|
||||
sendingAmount: BigDecimal,
|
||||
) {
|
||||
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
|
||||
val dustValue = currencyChecksRepository.getDustValue(
|
||||
userWalletProvider().walletId,
|
||||
cryptoCurrencyStatus.currency.network,
|
||||
) ?: return
|
||||
|
||||
if (checkDustLimits(feeValue, sendingAmount, dustValue)) {
|
||||
add(
|
||||
SendNotification.Error.MinimumAmountError(
|
||||
amount = dustValue.parseBigDecimal(cryptoCurrencyStatus.currency.decimals),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ import kotlinx.collections.immutable.persistentListOf
|
|||
internal class SendFeeStateConverter(
|
||||
private val appCurrencyProvider: Provider<AppCurrency>,
|
||||
private val feeCryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus?>,
|
||||
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus>,
|
||||
) : Converter<Unit, SendStates.FeeState> {
|
||||
|
||||
override fun convert(value: Unit): SendStates.FeeState {
|
||||
|
|
@ -21,6 +22,7 @@ internal class SendFeeStateConverter(
|
|||
appCurrency = appCurrencyProvider(),
|
||||
isFeeApproximate = false,
|
||||
isCustomSelected = false,
|
||||
isFeeConvertibleToFiat = cryptoCurrencyStatusProvider().currency.network.hasFiatFeeRate,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -50,6 +50,7 @@ internal class SendAmountFieldChangeConverter(
|
|||
val isZero = if (amountTextField.isFiatValue) decimalFiatValue.isNullOrZero() else decimalCryptoValue.isZero()
|
||||
return state.copyWrapped(
|
||||
isEditState = isEditState,
|
||||
sendState = state.sendState?.copy(reduceAmountBy = null),
|
||||
amountState = amountState.copy(
|
||||
isPrimaryButtonEnabled = !isExceedBalance && !isZero,
|
||||
amountTextField = amountTextField.copy(
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ internal class SendAmountFieldMaxAmountConverter(
|
|||
val fiatValue = decimalFiatValue?.parseBigDecimal(fiatDecimals, roundingMode = RoundingMode.HALF_UP).orEmpty()
|
||||
return state.copyWrapped(
|
||||
isEditState = isEditState,
|
||||
sendState = state.sendState?.copy(reduceAmountBy = null),
|
||||
amountState = amountState.copy(
|
||||
isPrimaryButtonEnabled = true,
|
||||
amountTextField = amountTextField.copy(
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ internal sealed class SendTextField {
|
|||
val label: TextReference,
|
||||
val isError: Boolean = false,
|
||||
val error: TextReference? = null,
|
||||
val isValuePasted: Boolean,
|
||||
) : SendTextField()
|
||||
|
||||
data class RecipientMemo(
|
||||
|
|
@ -54,6 +55,7 @@ internal sealed class SendTextField {
|
|||
val error: TextReference? = null,
|
||||
val disabledText: TextReference,
|
||||
val isEnabled: Boolean,
|
||||
val isValuePasted: Boolean,
|
||||
) : SendTextField()
|
||||
|
||||
data class CustomFee(
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ internal object FeeStatePreviewData {
|
|||
isFeeApproximate = false,
|
||||
notifications = persistentListOf(),
|
||||
isCustomSelected = false,
|
||||
isFeeConvertibleToFiat = true,
|
||||
)
|
||||
|
||||
val feeChoosableState = feeState.copy(
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ internal object RecipientStatePreviewData {
|
|||
label = stringReference("Recipient"),
|
||||
isError = false,
|
||||
error = null,
|
||||
isValuePasted = false,
|
||||
),
|
||||
memoTextField = SendTextField.RecipientMemo(
|
||||
value = "",
|
||||
|
|
@ -39,6 +40,7 @@ internal object RecipientStatePreviewData {
|
|||
error = null,
|
||||
disabledText = stringReference("Already included in the entered address"),
|
||||
isEnabled = true,
|
||||
isValuePasted = false,
|
||||
),
|
||||
recent = persistentListOf(),
|
||||
wallets = persistentListOf(),
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ internal object SendClickIntentsStub : SendClickIntents {
|
|||
|
||||
override fun onRecipientAddressValueChange(value: String, type: EnterAddressSource?) {}
|
||||
|
||||
override fun onRecipientMemoValueChange(value: String) {}
|
||||
override fun onRecipientMemoValueChange(value: String, isValuePasted: Boolean) {}
|
||||
|
||||
override fun feeReload() {}
|
||||
|
||||
|
|
@ -65,7 +65,8 @@ internal object SendClickIntentsStub : SendClickIntents {
|
|||
reduceAmountByDiff: BigDecimal?,
|
||||
reduceAmountTo: BigDecimal?,
|
||||
clazz: Class<out SendNotification>,
|
||||
) {}
|
||||
) {
|
||||
}
|
||||
|
||||
override fun onNotificationCancel(clazz: Class<out SendNotification>) {}
|
||||
}
|
||||
|
|
@ -0,0 +1,195 @@
|
|||
package com.tangem.features.send.impl.presentation.state.recipient
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.txhistory.models.TxHistoryItem
|
||||
import com.tangem.domain.transaction.error.ValidateAddressError
|
||||
import com.tangem.domain.transaction.usecase.ValidateWalletMemoUseCase
|
||||
import com.tangem.features.send.impl.R
|
||||
import com.tangem.features.send.impl.presentation.domain.AvailableWallet
|
||||
import com.tangem.features.send.impl.presentation.state.SendUiState
|
||||
import com.tangem.features.send.impl.presentation.state.StateRouter
|
||||
import com.tangem.utils.Provider
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import timber.log.Timber
|
||||
|
||||
internal class RecipientSendFactory(
|
||||
private val stateRouterProvider: Provider<StateRouter>,
|
||||
private val currentStateProvider: Provider<SendUiState>,
|
||||
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus>,
|
||||
private val isUtxoConsolidationAvailableProvider: Provider<Boolean>,
|
||||
private val validateWalletMemoUseCase: ValidateWalletMemoUseCase,
|
||||
) {
|
||||
private val recipientWalletListStateConverter by lazy(LazyThreadSafetyMode.NONE) {
|
||||
SendRecipientWalletListConverter(
|
||||
cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider,
|
||||
isUtxoConsolidationAvailableProvider = isUtxoConsolidationAvailableProvider,
|
||||
)
|
||||
}
|
||||
private val recipientHistoryListStateConverter by lazy(LazyThreadSafetyMode.NONE) {
|
||||
SendRecipientHistoryListConverter(
|
||||
cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider,
|
||||
)
|
||||
}
|
||||
|
||||
fun onLoadedWalletsList(wallets: List<AvailableWallet?>): SendUiState {
|
||||
val state = currentStateProvider()
|
||||
return state.copy(
|
||||
recipientState = state.recipientState?.copy(
|
||||
wallets = recipientWalletListStateConverter.convert(wallets),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun onLoadedHistoryList(txHistory: List<TxHistoryItem>): SendUiState {
|
||||
val state = currentStateProvider()
|
||||
return state.copy(
|
||||
recipientState = state.recipientState?.copy(
|
||||
recent = recipientHistoryListStateConverter.convert(txHistory),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun onRecipientAddressValueChange(value: String, isXAddress: Boolean = false, isValuePasted: Boolean): SendUiState {
|
||||
val state = currentStateProvider()
|
||||
val isEditState = stateRouterProvider().isEditState
|
||||
val recipientState = state.getRecipientState(isEditState) ?: return state
|
||||
return state.copyWrapped(
|
||||
isEditState = isEditState,
|
||||
recipientState = recipientState.copy(
|
||||
addressTextField = recipientState.addressTextField.copy(value = value, isValuePasted = isValuePasted),
|
||||
memoTextField = recipientState.memoTextField?.copy(isEnabled = !isXAddress),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun getOnRecipientAddressValidState(
|
||||
value: String,
|
||||
maybeValidAddress: Either<ValidateAddressError, Unit>,
|
||||
): SendUiState {
|
||||
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
|
||||
val state = currentStateProvider()
|
||||
val isEditState = stateRouterProvider().isEditState
|
||||
val recipientState = state.getRecipientState(isEditState) ?: return state
|
||||
|
||||
val isValidMemo = validateWalletMemoUseCase(
|
||||
memo = recipientState.memoTextField?.value.orEmpty(),
|
||||
network = cryptoCurrencyStatus.currency.network,
|
||||
).getOrElse {
|
||||
Timber.e("Failed to validateWalletMemoUseCase: $it")
|
||||
false
|
||||
}
|
||||
|
||||
return state.copyWrapped(
|
||||
isEditState = isEditState,
|
||||
recipientState = recipientState.copy(
|
||||
isPrimaryButtonEnabled = isValidMemo && maybeValidAddress.isRight(),
|
||||
isValidating = false,
|
||||
addressTextField = recipientState.addressTextField.copy(
|
||||
error = maybeValidAddress.fold(
|
||||
ifLeft = {
|
||||
when (it) {
|
||||
ValidateAddressError.InvalidAddress -> resourceReference(
|
||||
R.string.send_recipient_address_error,
|
||||
)
|
||||
ValidateAddressError.AddressInWallet -> resourceReference(
|
||||
R.string.send_error_address_same_as_wallet,
|
||||
)
|
||||
else -> null
|
||||
}
|
||||
},
|
||||
ifRight = { null },
|
||||
),
|
||||
isError = value.isNotEmpty() && maybeValidAddress.isLeft(),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun getOnRecipientAddressValidationStarted(): SendUiState {
|
||||
val state = currentStateProvider()
|
||||
val isEditState = stateRouterProvider().isEditState
|
||||
val recipientState = state.getRecipientState(isEditState) ?: return state
|
||||
return state.copyWrapped(
|
||||
isEditState = isEditState,
|
||||
recipientState = recipientState.copy(isValidating = true),
|
||||
)
|
||||
}
|
||||
|
||||
fun getOnRecipientMemoValueChange(value: String, isValuePasted: Boolean): SendUiState {
|
||||
val state = currentStateProvider()
|
||||
val isEditState = stateRouterProvider().isEditState
|
||||
val recipientState = state.getRecipientState(isEditState) ?: return state
|
||||
return state.copyWrapped(
|
||||
isEditState = isEditState,
|
||||
recipientState = recipientState.copy(
|
||||
memoTextField = recipientState.memoTextField?.copy(
|
||||
value = value,
|
||||
isValuePasted = isValuePasted,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun getOnRecipientMemoValidState(value: String, isValidAddress: Boolean): SendUiState {
|
||||
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
|
||||
val state = currentStateProvider()
|
||||
val isEditState = stateRouterProvider().isEditState
|
||||
val recipientState = state.getRecipientState(isEditState) ?: return state
|
||||
|
||||
val isValidMemo = validateWalletMemoUseCase(
|
||||
memo = value,
|
||||
network = cryptoCurrencyStatus.currency.network,
|
||||
).getOrElse {
|
||||
Timber.e("Failed to validateWalletMemoUseCase: $it")
|
||||
false
|
||||
}
|
||||
|
||||
return state.copyWrapped(
|
||||
isEditState = isEditState,
|
||||
recipientState = recipientState.copy(
|
||||
isPrimaryButtonEnabled = isValidMemo && isValidAddress,
|
||||
isValidating = false,
|
||||
memoTextField = recipientState.memoTextField?.copy(
|
||||
isError = value.isNotEmpty() && !isValidMemo,
|
||||
isEnabled = true,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun getOnXAddressMemoState(): SendUiState {
|
||||
val state = currentStateProvider()
|
||||
val isEditState = stateRouterProvider().isEditState
|
||||
val recipientState = state.getRecipientState(isEditState) ?: return state
|
||||
return state.copyWrapped(
|
||||
isEditState = isEditState,
|
||||
recipientState = recipientState.copy(
|
||||
memoTextField = recipientState.memoTextField?.copy(
|
||||
value = "",
|
||||
isEnabled = false,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun getHiddenRecentListState(isNotValid: Boolean): SendUiState {
|
||||
val state = currentStateProvider()
|
||||
val isEditState = stateRouterProvider().isEditState
|
||||
val recipientState = state.getRecipientState(isEditState) ?: return state
|
||||
return state.copyWrapped(
|
||||
isEditState = isEditState,
|
||||
recipientState = recipientState.copy(
|
||||
recent = recipientState.recent.map { recent ->
|
||||
recent.copy(isVisible = isNotValid && (recent.isLoading || recent.title != TextReference.EMPTY))
|
||||
}.toPersistentList(),
|
||||
wallets = recipientState.wallets.map { wallet ->
|
||||
wallet.copy(isVisible = isNotValid && (wallet.isLoading || wallet.title != TextReference.EMPTY))
|
||||
}.toPersistentList(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -24,6 +24,7 @@ internal class SendRecipientAddressFieldConverter(
|
|||
error = resourceReference(R.string.send_recipient_address_error),
|
||||
placeholder = resourceReference(R.string.send_enter_address_field),
|
||||
label = resourceReference(R.string.send_recipient),
|
||||
isValuePasted = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -58,6 +58,7 @@ internal class SendRecipientMemoFieldConverter(
|
|||
error = resourceReference(R.string.send_memo_destination_tag_error),
|
||||
disabledText = resourceReference(R.string.send_additional_field_already_included),
|
||||
isEnabled = true,
|
||||
isValuePasted = false,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +1,22 @@
|
|||
package com.tangem.features.send.impl.presentation.state.recipient
|
||||
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.features.send.impl.presentation.domain.AvailableWallet
|
||||
import com.tangem.features.send.impl.presentation.domain.SendRecipientListContent
|
||||
import com.tangem.features.send.impl.presentation.state.recipient.utils.WALLET_DEFAULT_COUNT
|
||||
import com.tangem.features.send.impl.presentation.state.recipient.utils.WALLET_KEY_TAG
|
||||
import com.tangem.features.send.impl.presentation.state.recipient.utils.emptyListState
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.converter.Converter
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
internal class SendRecipientWalletListConverter :
|
||||
internal class SendRecipientWalletListConverter(
|
||||
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus>,
|
||||
private val isUtxoConsolidationAvailableProvider: Provider<Boolean>,
|
||||
) :
|
||||
Converter<List<AvailableWallet?>, PersistentList<SendRecipientListContent>> {
|
||||
override fun convert(value: List<AvailableWallet?>): PersistentList<SendRecipientListContent> {
|
||||
return value.filterWallets().ifEmpty {
|
||||
|
|
@ -20,8 +26,18 @@ internal class SendRecipientWalletListConverter :
|
|||
|
||||
private fun List<AvailableWallet?>.filterWallets(): PersistentList<SendRecipientListContent> {
|
||||
var walletsCounter = 0
|
||||
val currentAddress: String = runCatching {
|
||||
cryptoCurrencyStatusProvider().value.networkAddress?.defaultAddress?.value
|
||||
}.getOrNull().orEmpty()
|
||||
|
||||
return this.filterNotNull()
|
||||
.filter { it.address.isNotBlank() }
|
||||
.filter {
|
||||
val isCoin = it.cryptoCurrency is CryptoCurrency.Coin
|
||||
val isNotSameAddress = it.address != currentAddress
|
||||
val isNotBlankAddress = it.address.isNotBlank()
|
||||
|
||||
isNotBlankAddress && isCoin && (isNotSameAddress || isUtxoConsolidationAvailableProvider())
|
||||
}
|
||||
.groupBy { item -> item.name }
|
||||
.values.map { wallets ->
|
||||
val groupedByWallet = wallets.groupBy { it.userWalletId }
|
||||
|
|
|
|||
|
|
@ -29,16 +29,15 @@ 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.TangemButtonsDefaults
|
||||
import com.tangem.core.ui.components.keyboardAsState
|
||||
import com.tangem.core.ui.extensions.resolveAnnotatedReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.shareText
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.domain.tokens.model.Amount
|
||||
import com.tangem.features.send.impl.presentation.state.SendUiCurrentScreen
|
||||
import com.tangem.features.send.impl.presentation.state.SendUiState
|
||||
import com.tangem.features.send.impl.presentation.state.SendUiStateType
|
||||
import com.tangem.features.send.impl.presentation.utils.getFiatFormatted
|
||||
import com.tangem.features.send.impl.presentation.utils.getFiatString
|
||||
import com.tangem.features.send.impl.presentation.utils.getCryptoReference
|
||||
|
||||
@Composable
|
||||
internal fun SendNavigationButtons(
|
||||
|
|
@ -175,23 +174,36 @@ private fun SendingText(
|
|||
val sendingFiat = if (uiState.isSubtracted) {
|
||||
fiatAmount?.value
|
||||
} else {
|
||||
feeFiat?.let { fiatAmount?.value?.plus(it) }
|
||||
if (feeState?.isFeeConvertibleToFiat == true) {
|
||||
feeFiat?.let { fiatAmount?.value?.plus(it) }
|
||||
} else {
|
||||
fiatAmount?.value
|
||||
}
|
||||
}
|
||||
|
||||
if (feeFiat != null && sendingFiat != null) {
|
||||
val sendingValue = getFiatFormatted(
|
||||
value = sendingFiat,
|
||||
currencySymbol = feeState.appCurrency.symbol,
|
||||
currencyCode = feeState.appCurrency.code,
|
||||
)
|
||||
val feeValue = getFiatString(
|
||||
value = feeState.fee?.amount?.value,
|
||||
rate = feeState.rate,
|
||||
appCurrency = feeState.appCurrency,
|
||||
val sendingValue = BigDecimalFormatter.formatFiatAmount(
|
||||
fiatAmount = sendingFiat,
|
||||
fiatCurrencySymbol = feeState.appCurrency.symbol,
|
||||
fiatCurrencyCode = feeState.appCurrency.code,
|
||||
)
|
||||
val feeValue = if (feeState.isFeeConvertibleToFiat) {
|
||||
getFiatString(
|
||||
value = feeState.fee?.amount?.value,
|
||||
rate = feeState.rate,
|
||||
appCurrency = feeState.appCurrency,
|
||||
)
|
||||
} else {
|
||||
getCryptoReference(feeState.fee?.amount, feeState.isFeeApproximate)?.resolveReference().orEmpty()
|
||||
}
|
||||
|
||||
val textResource = remember(uiState) {
|
||||
resourceReference(
|
||||
id = R.string.send_summary_transaction_description,
|
||||
id = if (feeState.isFeeConvertibleToFiat) {
|
||||
R.string.send_summary_transaction_description
|
||||
} else {
|
||||
R.string.send_summary_transaction_description_no_fiat_fee
|
||||
},
|
||||
formatArgs = wrappedList(sendingValue, feeValue),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.features.send.impl.presentation.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.AnimatedContentTransitionScope
|
||||
|
|
@ -12,15 +13,21 @@ import androidx.compose.material3.SnackbarHostState
|
|||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import com.tangem.core.ui.components.appbar.AppBarWithBackButtonAndIcon
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.send.impl.R
|
||||
import com.tangem.features.send.impl.presentation.state.SendUiCurrentScreen
|
||||
import com.tangem.features.send.impl.presentation.state.SendUiState
|
||||
import com.tangem.features.send.impl.presentation.state.SendUiStateType
|
||||
import com.tangem.features.send.impl.presentation.state.previewdata.ConfirmStatePreviewData
|
||||
import com.tangem.features.send.impl.presentation.state.previewdata.SendStatesPreviewData
|
||||
import com.tangem.features.send.impl.presentation.ui.amount.SendAmountContent
|
||||
import com.tangem.features.send.impl.presentation.ui.fee.SendSpeedAndFeeContent
|
||||
import com.tangem.features.send.impl.presentation.ui.recipient.SendRecipientContent
|
||||
|
|
@ -182,4 +189,49 @@ private fun SendScreenContent(uiState: SendUiState, currentState: SendUiCurrentS
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Preview(showBackground = true, widthDp = 360, heightDp = 736)
|
||||
@Preview(showBackground = true, widthDp = 360, heightDp = 736, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun SendScreen_Preview(@PreviewParameter(SendScreenPreviewProvider::class) data: SendScreenPreview) {
|
||||
TangemThemePreview {
|
||||
SendScreen(
|
||||
uiState = data.uiState,
|
||||
currentState = data.currentState,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class SendScreenPreviewProvider : PreviewParameterProvider<SendScreenPreview> {
|
||||
override val values: Sequence<SendScreenPreview>
|
||||
get() = sequenceOf(
|
||||
SendScreenPreview(
|
||||
uiState = SendStatesPreviewData.uiState,
|
||||
currentState = SendUiCurrentScreen(type = SendUiStateType.Recipient, isFromConfirmation = false),
|
||||
),
|
||||
SendScreenPreview(
|
||||
uiState = SendStatesPreviewData.uiState,
|
||||
currentState = SendUiCurrentScreen(type = SendUiStateType.Amount, isFromConfirmation = false),
|
||||
),
|
||||
SendScreenPreview(
|
||||
uiState = SendStatesPreviewData.uiState,
|
||||
currentState = SendUiCurrentScreen(type = SendUiStateType.Send, isFromConfirmation = false),
|
||||
),
|
||||
SendScreenPreview(
|
||||
uiState = SendStatesPreviewData.uiState,
|
||||
currentState = SendUiCurrentScreen(type = SendUiStateType.EditFee, isFromConfirmation = true),
|
||||
),
|
||||
SendScreenPreview(
|
||||
uiState = SendStatesPreviewData.uiState.copy(sendState = ConfirmStatePreviewData.sendDoneState),
|
||||
currentState = SendUiCurrentScreen(type = SendUiStateType.Send, isFromConfirmation = false),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private data class SendScreenPreview(
|
||||
val uiState: SendUiState,
|
||||
val currentState: SendUiCurrentScreen,
|
||||
)
|
||||
// endregion
|
||||
|
|
@ -94,19 +94,20 @@ private fun SendAmountCurrencyButton(button: SendAmountSegmentedButtonsConfig, i
|
|||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
val iconModifier = Modifier.size(TangemTheme.dimens.size18)
|
||||
.padding(horizontal = TangemTheme.dimens.spacing1)
|
||||
if (button.isFiat) {
|
||||
FiatIcon(
|
||||
url = button.iconUrl,
|
||||
size = TangemTheme.dimens.size18,
|
||||
isGrayscale = !isSegmentedButtonsEnabled,
|
||||
modifier = Modifier.size(TangemTheme.dimens.size18),
|
||||
modifier = iconModifier,
|
||||
)
|
||||
} else if (button.iconState != null) {
|
||||
TokenIcon(
|
||||
state = button.iconState,
|
||||
shouldDisplayNetwork = false,
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens.size18),
|
||||
modifier = iconModifier,
|
||||
)
|
||||
}
|
||||
Text(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.features.send.impl.presentation.ui.amount
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
|
|
@ -8,6 +9,7 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.send.impl.presentation.state.SendStates
|
||||
import com.tangem.features.send.impl.presentation.state.previewdata.AmountStatePreviewData
|
||||
|
|
@ -42,12 +44,13 @@ internal fun SendAmountContent(
|
|||
}
|
||||
|
||||
// region Preview
|
||||
@Preview
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun AmountFieldPreview_Light(
|
||||
@PreviewParameter(AmountStatePreviewProvider::class) amountState: SendStates.AmountState,
|
||||
private fun SendAmountContentPreview(
|
||||
@PreviewParameter(SendAmountContentPreviewProvider::class) amountState: SendStates.AmountState,
|
||||
) {
|
||||
TangemTheme {
|
||||
TangemThemePreview {
|
||||
SendAmountContent(
|
||||
amountState = amountState,
|
||||
isBalanceHiding = false,
|
||||
|
|
@ -56,21 +59,7 @@ private fun AmountFieldPreview_Light(
|
|||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun AmountFieldPreview_Dark(
|
||||
@PreviewParameter(AmountStatePreviewProvider::class) amountState: SendStates.AmountState,
|
||||
) {
|
||||
TangemTheme(isDark = true) {
|
||||
SendAmountContent(
|
||||
amountState = amountState,
|
||||
isBalanceHiding = false,
|
||||
clickIntents = SendClickIntentsStub,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class AmountStatePreviewProvider : PreviewParameterProvider<SendStates.AmountState> {
|
||||
private class SendAmountContentPreviewProvider : PreviewParameterProvider<SendStates.AmountState> {
|
||||
override val values: Sequence<SendStates.AmountState>
|
||||
get() = sequenceOf(
|
||||
AmountStatePreviewData.amountState,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.features.send.impl.presentation.ui.fee
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
|
|
@ -8,10 +9,16 @@ import androidx.compose.foundation.lazy.LazyColumn
|
|||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.send.impl.presentation.state.SendStates
|
||||
import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState
|
||||
import com.tangem.features.send.impl.presentation.state.fee.FeeType
|
||||
import com.tangem.features.send.impl.presentation.state.previewdata.FeeStatePreviewData
|
||||
import com.tangem.features.send.impl.presentation.state.previewdata.SendClickIntentsStub
|
||||
import com.tangem.features.send.impl.presentation.ui.common.notifications
|
||||
import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents
|
||||
|
||||
|
|
@ -75,4 +82,30 @@ internal fun LazyListScope.customFee(
|
|||
.padding(top = TangemTheme.dimens.spacing12),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun SendSpeedAndFeeContent_Preview(
|
||||
@PreviewParameter(FeeStatePreviewProvider::class) feeState: SendStates.FeeState,
|
||||
) {
|
||||
TangemThemePreview {
|
||||
SendSpeedAndFeeContent(
|
||||
state = feeState,
|
||||
clickIntents = SendClickIntentsStub,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class FeeStatePreviewProvider : PreviewParameterProvider<SendStates.FeeState> {
|
||||
override val values: Sequence<SendStates.FeeState>
|
||||
get() = sequenceOf(
|
||||
FeeStatePreviewData.feeState,
|
||||
FeeStatePreviewData.feeChoosableState,
|
||||
FeeStatePreviewData.feeCustomState,
|
||||
FeeStatePreviewData.errorFeeState,
|
||||
)
|
||||
}
|
||||
// endregion
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.features.send.impl.presentation.ui.fee
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
|
|
@ -18,6 +19,7 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.send.impl.R
|
||||
import com.tangem.features.send.impl.presentation.state.SendStates
|
||||
import com.tangem.features.send.impl.presentation.state.fee.FeeType
|
||||
|
|
@ -107,21 +109,12 @@ private fun FooterText(onReadMoreClick: () -> Unit) {
|
|||
|
||||
// region Preview
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun SendSpeedSelectorPreview_Light(
|
||||
private fun SendSpeedSelectorPreview(
|
||||
@PreviewParameter(SendSpeedSelectorPreviewProvider::class) feeState: SendStates.FeeState,
|
||||
) {
|
||||
TangemTheme {
|
||||
SendSpeedSelector(state = feeState, clickIntents = SendClickIntentsStub)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun SendSpeedSelectorPreview_Dark(
|
||||
@PreviewParameter(SendSpeedSelectorPreviewProvider::class) feeState: SendStates.FeeState,
|
||||
) {
|
||||
TangemTheme(isDark = true) {
|
||||
TangemThemePreview {
|
||||
SendSpeedSelector(state = feeState, clickIntents = SendClickIntentsStub)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,7 +54,11 @@ internal fun SendSpeedSelectorItem(
|
|||
onSelect = onSelect,
|
||||
modifier = modifier,
|
||||
preDot = getCryptoReference(amount, state.isFeeApproximate),
|
||||
postDot = getFiatReference(amount?.value, state.rate, state.appCurrency),
|
||||
postDot = if (state.isFeeConvertibleToFiat) {
|
||||
getFiatReference(amount?.value, state.rate, state.appCurrency)
|
||||
} else {
|
||||
null
|
||||
},
|
||||
ellipsizeOffset = amount?.currencySymbol?.length,
|
||||
isSelected = content?.selectedFee == feeType,
|
||||
showDivider = showDivider,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.features.send.impl.presentation.ui.recipient
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.fadeIn
|
||||
|
|
@ -27,6 +28,7 @@ import com.tangem.core.ui.components.atoms.text.EllipsisText
|
|||
import com.tangem.core.ui.components.atoms.text.TextEllipsis
|
||||
import com.tangem.core.ui.components.icons.identicon.IdentIcon
|
||||
import com.tangem.core.ui.extensions.rememberHapticFeedback
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.send.impl.R
|
||||
|
||||
|
|
@ -183,28 +185,12 @@ private fun ListItemLoading(modifier: Modifier = Modifier) {
|
|||
|
||||
// region preview
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun ListItemWithIconPreview_Light(
|
||||
private fun ListItemWithIconPreview(
|
||||
@PreviewParameter(ListItemWithIconPreviewProvider::class) config: ListItemWithIconPreviewConfig,
|
||||
) {
|
||||
TangemTheme {
|
||||
ListItemWithIcon(
|
||||
title = config.title,
|
||||
subtitle = config.subtitle,
|
||||
subtitleEndOffset = config.subtitleEndOffset,
|
||||
subtitleIconRes = config.iconRes,
|
||||
onClick = {},
|
||||
isLoading = config.isLoading,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun ListItemWithIconPreview_Dark(
|
||||
@PreviewParameter(ListItemWithIconPreviewProvider::class) config: ListItemWithIconPreviewConfig,
|
||||
) {
|
||||
TangemTheme(isDark = true) {
|
||||
TangemThemePreview {
|
||||
ListItemWithIcon(
|
||||
title = config.title,
|
||||
subtitle = config.subtitle,
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import com.tangem.common.Strings.STARS
|
|||
import com.tangem.core.ui.components.inputrow.InputRowRecipient
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.send.impl.R
|
||||
import com.tangem.features.send.impl.presentation.analytics.EnterAddressSource
|
||||
import com.tangem.features.send.impl.presentation.domain.SendRecipientListContent
|
||||
|
|
@ -70,7 +71,7 @@ internal fun SendRecipientContent(
|
|||
)
|
||||
memoField(
|
||||
memoField = memoField,
|
||||
onMemoChange = clickIntents::onRecipientMemoValueChange,
|
||||
onMemoChange = { clickIntents.onRecipientMemoValueChange(it, true) },
|
||||
)
|
||||
listHeaderItem(
|
||||
titleRes = R.string.send_recipient_wallets_title,
|
||||
|
|
@ -117,6 +118,7 @@ private fun LazyListScope.addressItem(
|
|||
isError = isError,
|
||||
isLoading = isValidating,
|
||||
error = address.error,
|
||||
isValuePasted = address.isValuePasted,
|
||||
modifier = Modifier
|
||||
.background(
|
||||
color = TangemTheme.colors.background.action,
|
||||
|
|
@ -143,6 +145,7 @@ private fun LazyListScope.memoField(memoField: SendTextField.RecipientMemo?, onM
|
|||
isError = memoField.isError,
|
||||
error = memoField.error,
|
||||
isReadOnly = !memoField.isEnabled,
|
||||
isValuePasted = memoField.isValuePasted,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -258,7 +261,7 @@ private fun AnimateRecentAppearance(isVisible: Boolean, content: @Composable ()
|
|||
private fun SendRecipientContent_Preview(
|
||||
@PreviewParameter(SendRecipientContentPreviewProvider::class) recipientState: SendStates.RecipientState,
|
||||
) {
|
||||
TangemTheme(isDark = false) {
|
||||
TangemThemePreview {
|
||||
SendRecipientContent(
|
||||
uiState = recipientState,
|
||||
clickIntents = SendClickIntentsStub,
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ internal fun TextFieldWithPaste(
|
|||
error: TextReference? = null,
|
||||
isError: Boolean = false,
|
||||
isReadOnly: Boolean = false,
|
||||
isValuePasted: Boolean = false,
|
||||
) {
|
||||
val (title, color) = when {
|
||||
isError && error != null -> error to TangemTheme.colors.text.warning
|
||||
|
|
@ -65,6 +66,7 @@ internal fun TextFieldWithPaste(
|
|||
placeholderColor = placeholderColor,
|
||||
onValueChange = onValueChange,
|
||||
readOnly = isReadOnly,
|
||||
isValuePasted = isValuePasted,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = TangemTheme.dimens.spacing8),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.features.send.impl.presentation.ui.send
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
|
@ -16,6 +17,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
|||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import com.tangem.core.ui.components.ResizableText
|
||||
import com.tangem.core.ui.components.currency.tokenicon.TokenIcon
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.features.send.impl.presentation.state.SendStates
|
||||
|
|
@ -54,10 +56,7 @@ internal fun AmountBlock(
|
|||
.clip(TangemTheme.shapes.roundedCornersXMedium)
|
||||
.background(backgroundColor)
|
||||
.clickable(enabled = !isClickDisabled && !isEditingDisabled, onClick = onClick)
|
||||
.padding(
|
||||
vertical = TangemTheme.dimens.spacing14,
|
||||
horizontal = TangemTheme.dimens.spacing16,
|
||||
),
|
||||
.padding(TangemTheme.dimens.spacing16),
|
||||
) {
|
||||
TokenIcon(state = amountState.tokenIconState)
|
||||
ResizableText(
|
||||
|
|
@ -77,21 +76,17 @@ internal fun AmountBlock(
|
|||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
top = TangemTheme.dimens.spacing8,
|
||||
bottom = TangemTheme.dimens.spacing2,
|
||||
),
|
||||
.padding(top = TangemTheme.dimens.spacing8),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun AmountBlockPreview_Light(
|
||||
@PreviewParameter(AmountBlockPreviewProvider::class) value: SendStates.AmountState,
|
||||
) {
|
||||
TangemTheme {
|
||||
private fun AmountBlockPreview(@PreviewParameter(AmountBlockPreviewProvider::class) value: SendStates.AmountState) {
|
||||
TangemThemePreview {
|
||||
AmountBlock(
|
||||
amountState = value,
|
||||
isClickDisabled = false,
|
||||
|
|
@ -101,21 +96,6 @@ private fun AmountBlockPreview_Light(
|
|||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun AmountBlockPreview_Dark(
|
||||
@PreviewParameter(AmountBlockPreviewProvider::class) value: SendStates.AmountState,
|
||||
) {
|
||||
TangemTheme(isDark = true) {
|
||||
AmountBlock(
|
||||
amountState = value,
|
||||
isClickDisabled = true,
|
||||
isEditingDisabled = false,
|
||||
onClick = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class AmountBlockPreviewProvider : PreviewParameterProvider<SendStates.AmountState> {
|
||||
override val values: Sequence<SendStates.AmountState>
|
||||
get() = sequenceOf(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.features.send.impl.presentation.ui.send
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
|
|
@ -15,6 +16,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
|||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
import com.tangem.core.ui.components.rows.SelectorRowItem
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.features.send.impl.R
|
||||
|
|
@ -60,7 +62,11 @@ internal fun FeeBlock(feeState: SendStates.FeeState, isClickDisabled: Boolean, o
|
|||
titleRes = title,
|
||||
iconRes = icon,
|
||||
preDot = getCryptoReference(feeAmount, feeState.isFeeApproximate),
|
||||
postDot = getFiatReference(feeAmount?.value, feeState.rate, feeState.appCurrency),
|
||||
postDot = if (feeState.isFeeConvertibleToFiat) {
|
||||
getFiatReference(feeAmount?.value, feeState.rate, feeState.appCurrency)
|
||||
} else {
|
||||
null
|
||||
},
|
||||
ellipsizeOffset = feeAmount?.currencySymbol?.length,
|
||||
isSelected = true,
|
||||
showDivider = false,
|
||||
|
|
@ -111,21 +117,10 @@ private fun BoxScope.FeeError(feeSelectorState: FeeSelectorState) {
|
|||
|
||||
// region Preview
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun FeeBlockPreview_Light(@PreviewParameter(FeeBlockPreviewProvider::class) value: SendStates.FeeState) {
|
||||
TangemTheme {
|
||||
FeeBlock(
|
||||
feeState = value,
|
||||
isClickDisabled = true,
|
||||
onClick = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun FeeBlockPreview_Dark(@PreviewParameter(FeeBlockPreviewProvider::class) value: SendStates.FeeState) {
|
||||
TangemTheme(isDark = true) {
|
||||
private fun FeeBlockPreview(@PreviewParameter(FeeBlockPreviewProvider::class) value: SendStates.FeeState) {
|
||||
TangemThemePreview {
|
||||
FeeBlock(
|
||||
feeState = value,
|
||||
isClickDisabled = true,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.features.send.impl.presentation.ui.send
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
|
|
@ -15,6 +16,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
|||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import com.tangem.core.ui.components.icons.identicon.IdentIcon
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.send.impl.presentation.state.SendStates
|
||||
import com.tangem.features.send.impl.presentation.state.fields.SendTextField
|
||||
|
|
@ -97,26 +99,12 @@ private fun MemoBlock(memo: SendTextField.RecipientMemo?) {
|
|||
|
||||
// region Preview
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun RecipientBlockPreview_Light(
|
||||
private fun RecipientBlockPreview(
|
||||
@PreviewParameter(RecipientBlockPreviewProvider::class) value: SendStates.RecipientState,
|
||||
) {
|
||||
TangemTheme {
|
||||
RecipientBlock(
|
||||
recipientState = value,
|
||||
isClickDisabled = true,
|
||||
isEditingDisabled = false,
|
||||
onClick = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun RecipientBlockPreview_Dark(
|
||||
@PreviewParameter(RecipientBlockPreviewProvider::class) value: SendStates.RecipientState,
|
||||
) {
|
||||
TangemTheme(isDark = true) {
|
||||
TangemThemePreview {
|
||||
RecipientBlock(
|
||||
recipientState = value,
|
||||
isClickDisabled = true,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.features.send.impl.presentation.ui.send
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.animation.core.MutableTransitionState
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
|
|
@ -20,10 +21,16 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import com.tangem.core.ui.components.transactions.TransactionDoneTitle
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.send.impl.R
|
||||
import com.tangem.features.send.impl.presentation.state.SendUiState
|
||||
import com.tangem.features.send.impl.presentation.state.previewdata.ConfirmStatePreviewData
|
||||
import com.tangem.features.send.impl.presentation.state.previewdata.SendStatesPreviewData
|
||||
import com.tangem.features.send.impl.presentation.ui.common.notifications
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
|
|
@ -133,4 +140,25 @@ private fun LazyListScope.tapHelp(isDisplay: Boolean, modifier: Modifier = Modif
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun SendContent_Preview(@PreviewParameter(SendContentPreviewProvider::class) uiState: SendUiState) {
|
||||
TangemThemePreview {
|
||||
SendContent(
|
||||
uiState = uiState,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class SendContentPreviewProvider : PreviewParameterProvider<SendUiState> {
|
||||
override val values: Sequence<SendUiState>
|
||||
get() = sequenceOf(
|
||||
SendStatesPreviewData.uiState,
|
||||
SendStatesPreviewData.uiState.copy(sendState = ConfirmStatePreviewData.sendDoneState),
|
||||
)
|
||||
}
|
||||
// endregion
|
||||
|
|
@ -8,11 +8,8 @@ import com.tangem.core.ui.utils.BigDecimalFormatter
|
|||
import com.tangem.core.ui.utils.BigDecimalFormatter.EMPTY_BALANCE_SIGN
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import java.math.BigDecimal
|
||||
import java.math.RoundingMode
|
||||
|
||||
private const val FIAT_DECIMALS = 2
|
||||
private const val CRYPTO_FEE_DECIMALS = 6
|
||||
private const val FEE_MINIMUM_VALUE = 0.01
|
||||
|
||||
internal fun getCryptoReference(amount: Amount?, isFeeApproximate: Boolean): TextReference? {
|
||||
if (amount == null) return null
|
||||
|
|
@ -37,27 +34,9 @@ internal fun getFiatReference(value: BigDecimal?, rate: BigDecimal?, appCurrency
|
|||
internal fun getFiatString(value: BigDecimal?, rate: BigDecimal?, appCurrency: AppCurrency): String {
|
||||
if (value == null || rate == null) return EMPTY_BALANCE_SIGN
|
||||
val feeValue = value.multiply(rate)
|
||||
return getFiatFormatted(feeValue, appCurrency.code, appCurrency.symbol)
|
||||
}
|
||||
|
||||
internal fun getFiatFormatted(value: BigDecimal?, currencyCode: String, currencySymbol: String): String {
|
||||
val scaled = value?.setScale(FIAT_DECIMALS, RoundingMode.UP) ?: BigDecimal.ZERO
|
||||
return if (scaled < BigDecimal(FEE_MINIMUM_VALUE)) {
|
||||
buildString {
|
||||
append(BigDecimalFormatter.CAN_BE_LOWER_SIGN)
|
||||
append(
|
||||
BigDecimalFormatter.formatFiatAmount(
|
||||
fiatAmount = BigDecimal(FEE_MINIMUM_VALUE),
|
||||
fiatCurrencyCode = currencyCode,
|
||||
fiatCurrencySymbol = currencySymbol,
|
||||
),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
BigDecimalFormatter.formatFiatAmount(
|
||||
fiatAmount = value,
|
||||
fiatCurrencyCode = currencyCode,
|
||||
fiatCurrencySymbol = currencySymbol,
|
||||
)
|
||||
}
|
||||
return BigDecimalFormatter.formatFiatAmount(
|
||||
fiatAmount = feeValue,
|
||||
fiatCurrencyCode = appCurrency.code,
|
||||
fiatCurrencySymbol = appCurrency.symbol,
|
||||
)
|
||||
}
|
||||
|
|
@ -39,7 +39,7 @@ internal interface SendClickIntents {
|
|||
// region Recipient
|
||||
fun onRecipientAddressValueChange(value: String, type: EnterAddressSource? = null)
|
||||
|
||||
fun onRecipientMemoValueChange(value: String)
|
||||
fun onRecipientMemoValueChange(value: String, isValuePasted: Boolean = false)
|
||||
// endregion
|
||||
|
||||
// region Fee
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@ import android.os.SystemClock
|
|||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.util.fastDistinctBy
|
||||
import androidx.lifecycle.*
|
||||
import arrow.core.Either
|
||||
import arrow.core.getOrElse
|
||||
import arrow.core.left
|
||||
import com.tangem.blockchain.common.TransactionData
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
|
|
@ -30,18 +30,14 @@ import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
|||
import com.tangem.domain.tokens.repository.CurrencyChecksRepository
|
||||
import com.tangem.domain.tokens.utils.convertToAmount
|
||||
import com.tangem.domain.transaction.error.GetFeeError
|
||||
import com.tangem.domain.transaction.usecase.CreateTransactionUseCase
|
||||
import com.tangem.domain.transaction.usecase.GetFeeUseCase
|
||||
import com.tangem.domain.transaction.usecase.IsFeeApproximateUseCase
|
||||
import com.tangem.domain.transaction.usecase.SendTransactionUseCase
|
||||
import com.tangem.domain.transaction.error.ValidateAddressError
|
||||
import com.tangem.domain.transaction.usecase.*
|
||||
import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
|
||||
import com.tangem.domain.txhistory.usecase.GetFixedTxHistoryItemsUseCase
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
||||
import com.tangem.domain.wallets.usecase.ValidateWalletAddressUseCase
|
||||
import com.tangem.domain.wallets.usecase.ValidateWalletMemoUseCase
|
||||
import com.tangem.features.send.api.navigation.SendRouter
|
||||
import com.tangem.features.send.impl.navigation.InnerSendRouter
|
||||
import com.tangem.features.send.impl.presentation.analytics.EnterAddressSource
|
||||
|
|
@ -53,12 +49,10 @@ import com.tangem.features.send.impl.presentation.state.*
|
|||
import com.tangem.features.send.impl.presentation.state.amount.AmountStateFactory
|
||||
import com.tangem.features.send.impl.presentation.state.confirm.SendNotificationFactory
|
||||
import com.tangem.features.send.impl.presentation.state.fee.*
|
||||
import com.tangem.features.send.impl.presentation.state.recipient.RecipientSendFactory
|
||||
import com.tangem.lib.crypto.BlockchainUtils
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.DelayedWork
|
||||
import com.tangem.utils.coroutines.JobHolder
|
||||
import com.tangem.utils.coroutines.saveIn
|
||||
import com.tangem.utils.coroutines.*
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
|
@ -74,11 +68,10 @@ import kotlin.properties.Delegates
|
|||
internal class SendViewModel @Inject constructor(
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||
private val getCurrencyStatusUpdatesUseCase: GetCurrencyStatusUpdatesUseCase,
|
||||
private val getCryptoCurrencyStatusSyncUseCase: GetCryptoCurrencyStatusSyncUseCase,
|
||||
private val getNetworkCoinStatusUseCase: GetNetworkCoinStatusUseCase,
|
||||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val getWalletsUseCase: GetWalletsUseCase,
|
||||
private val getPrimaryCurrencyStatusUpdatesUseCase: GetPrimaryCurrencyStatusUpdatesUseCase,
|
||||
private val getFeePaidCryptoCurrencyStatusSyncUseCase: GetFeePaidCryptoCurrencyStatusSyncUseCase,
|
||||
private val getCryptoCurrencyUseCase: GetCryptoCurrencyUseCase,
|
||||
private val getNetworkAddressesUseCase: GetNetworkAddressesUseCase,
|
||||
|
|
@ -99,7 +92,9 @@ internal class SendViewModel @Inject constructor(
|
|||
private val addCryptoCurrenciesUseCase: AddCryptoCurrenciesUseCase,
|
||||
private val updateDelayedCurrencyStatusUseCase: UpdateDelayedNetworkStatusUseCase,
|
||||
private val fetchPendingTransactionsUseCase: FetchPendingTransactionsUseCase,
|
||||
private val isUtxoConsolidationAvailableUseCase: IsUtxoConsolidationAvailableUseCase,
|
||||
@DelayedWork private val coroutineScope: CoroutineScope,
|
||||
validateTransactionUseCase: ValidateTransactionUseCase,
|
||||
currencyChecksRepository: CurrencyChecksRepository,
|
||||
isFeeApproximateUseCase: IsFeeApproximateUseCase,
|
||||
validateWalletMemoUseCase: ValidateWalletMemoUseCase,
|
||||
|
|
@ -133,10 +128,17 @@ internal class SendViewModel @Inject constructor(
|
|||
appCurrencyProvider = Provider(selectedAppCurrencyFlow::value),
|
||||
cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
|
||||
feeCryptoCurrencyStatusProvider = Provider { feeCryptoCurrencyStatus },
|
||||
validateWalletMemoUseCase = validateWalletMemoUseCase,
|
||||
isTapHelpPreviewEnabledProvider = Provider { isTapHelpPreviewEnabled },
|
||||
)
|
||||
|
||||
private val recipientStateFactory = RecipientSendFactory(
|
||||
stateRouterProvider = Provider { stateRouter },
|
||||
currentStateProvider = Provider { uiState },
|
||||
cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
|
||||
isUtxoConsolidationAvailableProvider = Provider { isUtxoConsolidationAvailable },
|
||||
validateWalletMemoUseCase = validateWalletMemoUseCase,
|
||||
)
|
||||
|
||||
private val amountStateFactory = AmountStateFactory(
|
||||
stateRouterProvider = Provider { stateRouter },
|
||||
currentStateProvider = Provider { uiState },
|
||||
|
|
@ -178,6 +180,7 @@ internal class SendViewModel @Inject constructor(
|
|||
clickIntents = this,
|
||||
analyticsEventHandler = analyticsEventHandler,
|
||||
getBalanceNotEnoughForFeeWarningUseCase = getBalanceNotEnoughForFeeWarningUseCase,
|
||||
validateTransactionUseCase = validateTransactionUseCase,
|
||||
)
|
||||
|
||||
private val sendScreenAnalyticSender by lazy(LazyThreadSafetyMode.NONE) {
|
||||
|
|
@ -196,6 +199,7 @@ internal class SendViewModel @Inject constructor(
|
|||
private var userWallet: UserWallet by Delegates.notNull()
|
||||
private var userWallets: List<AvailableWallet> = emptyList()
|
||||
private var isAmountSubtractAvailable: Boolean = false
|
||||
private var isUtxoConsolidationAvailable: Boolean = false
|
||||
private var isTapHelpPreviewEnabled: Boolean = false
|
||||
private var coinCryptoCurrencyStatus: CryptoCurrencyStatus by Delegates.notNull()
|
||||
private var cryptoCurrencyStatus: CryptoCurrencyStatus by Delegates.notNull()
|
||||
|
|
@ -241,11 +245,12 @@ internal class SendViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun subscribeOnCurrencyStatusUpdates() {
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
viewModelScope.launch {
|
||||
getUserWalletUseCase(userWalletId).fold(
|
||||
ifRight = { wallet ->
|
||||
userWallet = wallet
|
||||
checkIfSubtractAvailable()
|
||||
checkIfUtxoConsolidationAvailable()
|
||||
|
||||
val isSingleWalletWithToken = wallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()
|
||||
val isMultiCurrency = wallet.isMultiCurrency
|
||||
|
|
@ -255,9 +260,7 @@ internal class SendViewModel @Inject constructor(
|
|||
)
|
||||
},
|
||||
ifLeft = {
|
||||
uiState = eventStateFactory.getGenericErrorState(
|
||||
onConsume = { uiState = eventStateFactory.onConsumeEventState() },
|
||||
)
|
||||
showErrorAlert()
|
||||
return@launch
|
||||
},
|
||||
)
|
||||
|
|
@ -275,73 +278,62 @@ internal class SendViewModel @Inject constructor(
|
|||
.saveIn(balanceHidingJobHolder)
|
||||
}
|
||||
|
||||
// TODO [REDACTED_JIRA]
|
||||
private fun getCurrenciesStatusUpdates(isSingleWalletWithToken: Boolean, isMultiCurrency: Boolean) {
|
||||
if (cryptoCurrency is CryptoCurrency.Coin) {
|
||||
getCurrencyStatusUpdates(
|
||||
isSingleWalletWithToken = isSingleWalletWithToken,
|
||||
isMultiCurrency = isMultiCurrency,
|
||||
).onEach { currencyStatus ->
|
||||
currencyStatus.onRight {
|
||||
onDataLoaded(
|
||||
currencyStatus = it,
|
||||
coinCurrencyStatus = it,
|
||||
feeCurrencyStatus = getFeeCurrencyStatusSync(it, isMultiCurrency),
|
||||
)
|
||||
}
|
||||
}
|
||||
.flowOn(dispatchers.main)
|
||||
.launchIn(viewModelScope)
|
||||
.saveIn(balanceJobHolder)
|
||||
private suspend fun getCurrenciesStatusUpdates(isSingleWalletWithToken: Boolean, isMultiCurrency: Boolean) {
|
||||
val maybeCurrencyStatus = getCurrencyStatus(
|
||||
isSingleWalletWithToken = isSingleWalletWithToken,
|
||||
isMultiCurrency = isMultiCurrency,
|
||||
)
|
||||
val maybeCoinStatus = if (cryptoCurrency is CryptoCurrency.Coin) {
|
||||
maybeCurrencyStatus
|
||||
} else {
|
||||
combine(
|
||||
flow = getCoinCurrencyStatusUpdates(isSingleWalletWithToken),
|
||||
flow2 = getCurrencyStatusUpdates(
|
||||
isSingleWalletWithToken = isSingleWalletWithToken,
|
||||
isMultiCurrency = isMultiCurrency,
|
||||
),
|
||||
) { maybeCoinStatus, maybeCurrencyStatus ->
|
||||
if (maybeCoinStatus.isRight() && maybeCurrencyStatus.isRight()) {
|
||||
val currencyStatus = maybeCurrencyStatus.getOrElse { error("Currency status is unreachable") }
|
||||
val coinStatus = maybeCoinStatus.getOrElse { error("Coin status is unreachable") }
|
||||
onDataLoaded(
|
||||
currencyStatus = currencyStatus,
|
||||
coinCurrencyStatus = coinStatus,
|
||||
feeCurrencyStatus = getFeeCurrencyStatusSync(currencyStatus, isMultiCurrency),
|
||||
)
|
||||
}
|
||||
getCoinCurrencyStatusUpdates(isSingleWalletWithToken)
|
||||
}
|
||||
|
||||
if (maybeCoinStatus.isRight() && maybeCurrencyStatus.isRight()) {
|
||||
val currencyStatus = maybeCurrencyStatus.getOrElse {
|
||||
showErrorAlert()
|
||||
return Timber.e("Currency status is unreachable")
|
||||
}
|
||||
.flowOn(dispatchers.main)
|
||||
.launchIn(viewModelScope)
|
||||
.saveIn(balanceJobHolder)
|
||||
val coinStatus = maybeCoinStatus.getOrElse {
|
||||
showErrorAlert()
|
||||
return Timber.e("Coin status is unreachable")
|
||||
}
|
||||
onDataLoaded(
|
||||
currencyStatus = currencyStatus,
|
||||
coinCurrencyStatus = coinStatus,
|
||||
feeCurrencyStatus = getFeeCurrencyStatusSync(currencyStatus, isMultiCurrency),
|
||||
)
|
||||
} else {
|
||||
showErrorAlert()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getTapHelpPreviewAvailability() {
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
viewModelScope.launch {
|
||||
isTapHelpPreviewEnabled = isSendTapHelpEnabledUseCase().getOrElse { false }
|
||||
}
|
||||
}
|
||||
|
||||
private fun getCoinCurrencyStatusUpdates(isSingleWalletWithToken: Boolean) = getNetworkCoinStatusUseCase(
|
||||
userWalletId = userWalletId,
|
||||
networkId = cryptoCurrency.network.id,
|
||||
derivationPath = cryptoCurrency.network.derivationPath,
|
||||
isSingleWalletWithTokens = isSingleWalletWithToken,
|
||||
).conflate().distinctUntilChanged()
|
||||
private suspend fun getCoinCurrencyStatusUpdates(isSingleWalletWithToken: Boolean) = getNetworkCoinStatusUseCase
|
||||
.invokeSync(
|
||||
userWalletId = userWalletId,
|
||||
networkId = cryptoCurrency.network.id,
|
||||
derivationPath = cryptoCurrency.network.derivationPath,
|
||||
isSingleWalletWithTokens = isSingleWalletWithToken,
|
||||
)
|
||||
|
||||
private fun getCurrencyStatusUpdates(
|
||||
private suspend fun getCurrencyStatus(
|
||||
isSingleWalletWithToken: Boolean,
|
||||
isMultiCurrency: Boolean,
|
||||
): Flow<Either<CurrencyStatusError, CryptoCurrencyStatus>> {
|
||||
): Either<CurrencyStatusError, CryptoCurrencyStatus> {
|
||||
return if (isMultiCurrency) {
|
||||
getCurrencyStatusUpdatesUseCase(
|
||||
getCryptoCurrencyStatusSyncUseCase(
|
||||
userWalletId = userWalletId,
|
||||
currencyId = cryptoCurrency.id,
|
||||
cryptoCurrencyId = cryptoCurrency.id,
|
||||
isSingleWalletWithTokens = isSingleWalletWithToken,
|
||||
).conflate().distinctUntilChanged()
|
||||
)
|
||||
} else {
|
||||
getPrimaryCurrencyStatusUpdatesUseCase(userWalletId = userWalletId)
|
||||
getCryptoCurrencyStatusSyncUseCase(userWalletId = userWalletId)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -399,30 +391,28 @@ internal class SendViewModel @Inject constructor(
|
|||
|
||||
private fun getWalletsAndRecent() {
|
||||
getUserWallets()
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
viewModelScope.launch {
|
||||
getTxHistory()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getUserWallets() {
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
viewModelScope.launch {
|
||||
runCatching {
|
||||
getWalletsUseCase.invokeSync()
|
||||
?.toAvailableWallets()
|
||||
.orEmpty()
|
||||
waitForDelay(delay = RECENT_LOAD_DELAY) {
|
||||
getWalletsUseCase.invokeSync()
|
||||
.toAvailableWallets()
|
||||
}
|
||||
}.onSuccess { result ->
|
||||
userWallets = result
|
||||
uiState = stateFactory.onLoadedWalletsList(wallets = userWallets)
|
||||
uiState = recipientStateFactory.onLoadedWalletsList(wallets = userWallets)
|
||||
}.onFailure {
|
||||
uiState = stateFactory.onLoadedWalletsList(wallets = emptyList())
|
||||
uiState = recipientStateFactory.onLoadedWalletsList(wallets = emptyList())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun List<UserWallet>.toAvailableWallets(): List<AvailableWallet> {
|
||||
val currentAddress: String = kotlin.runCatching {
|
||||
cryptoCurrencyStatus.value.networkAddress?.defaultAddress?.value
|
||||
}.getOrNull().orEmpty()
|
||||
return filterNot { it.isLocked }
|
||||
.mapNotNull { wallet ->
|
||||
val addresses = if (!wallet.isMultiCurrency) {
|
||||
|
|
@ -436,26 +426,26 @@ internal class SendViewModel @Inject constructor(
|
|||
} else {
|
||||
getNetworkAddressesUseCase.invokeSync(wallet.walletId, cryptoCurrency.network)
|
||||
}
|
||||
addresses
|
||||
?.filter { it.address != currentAddress }
|
||||
?.map { (cryptoCurrency, address) ->
|
||||
AvailableWallet(
|
||||
name = wallet.name,
|
||||
address = address,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
userWalletId = wallet.walletId,
|
||||
)
|
||||
}?.fastDistinctBy { it.address }
|
||||
addresses?.map { (cryptoCurrency, address) ->
|
||||
AvailableWallet(
|
||||
name = wallet.name,
|
||||
address = address,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
userWalletId = wallet.walletId,
|
||||
)
|
||||
}
|
||||
}.flatten()
|
||||
}
|
||||
|
||||
private suspend fun getTxHistory() {
|
||||
val txHistoryList = getFixedTxHistoryItemsUseCase.getSync(
|
||||
userWalletId = userWalletId,
|
||||
currency = cryptoCurrency,
|
||||
pageSize = RECENT_TX_SIZE,
|
||||
).getOrElse { emptyList() }
|
||||
uiState = stateFactory.onLoadedHistoryList(txHistory = txHistoryList)
|
||||
val txHistoryList = waitForDelay(delay = RECENT_LOAD_DELAY) {
|
||||
getFixedTxHistoryItemsUseCase.getSync(
|
||||
userWalletId = userWalletId,
|
||||
currency = cryptoCurrency,
|
||||
pageSize = RECENT_TX_SIZE,
|
||||
).getOrElse { emptyList() }
|
||||
}
|
||||
uiState = recipientStateFactory.onLoadedHistoryList(txHistory = txHistoryList)
|
||||
}
|
||||
|
||||
private fun onStateActive() {
|
||||
|
|
@ -592,7 +582,7 @@ internal class SendViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun cancelFeeRequest() {
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
viewModelScope.launch {
|
||||
feeJobHolder.cancel()
|
||||
}
|
||||
}
|
||||
|
|
@ -613,7 +603,7 @@ internal class SendViewModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
// endregion
|
||||
// endregion
|
||||
|
||||
// region amount state clicks
|
||||
override fun onCurrencyChangeClick(isFiat: Boolean) {
|
||||
|
|
@ -639,54 +629,59 @@ internal class SendViewModel @Inject constructor(
|
|||
override fun onRecipientAddressValueChange(value: String, type: EnterAddressSource?) {
|
||||
viewModelScope.launch {
|
||||
if (!checkIfXrpAddressValue(value)) {
|
||||
uiState = stateFactory.onRecipientAddressValueChange(value)
|
||||
uiState = stateFactory.getOnRecipientAddressValidationStarted()
|
||||
uiState = recipientStateFactory.onRecipientAddressValueChange(value, isValuePasted = type != null)
|
||||
uiState = recipientStateFactory.getOnRecipientAddressValidationStarted()
|
||||
val isValidAddress = validateAddress(value)
|
||||
uiState = stateFactory.getOnRecipientAddressValidState(value, isValidAddress)
|
||||
type?.let { analyticsEventHandler.send(SendAnalyticEvents.AddressEntered(it, isValidAddress)) }
|
||||
autoNextFromRecipient(type, isValidAddress)
|
||||
uiState = recipientStateFactory.getOnRecipientAddressValidState(value, isValidAddress)
|
||||
type?.let {
|
||||
analyticsEventHandler.send(
|
||||
SendAnalyticEvents.AddressEntered(
|
||||
it,
|
||||
isValidAddress.isRight(),
|
||||
),
|
||||
)
|
||||
}
|
||||
autoNextFromRecipient(type, isValidAddress.isRight())
|
||||
}
|
||||
}.saveIn(addressValidationJobHolder)
|
||||
}
|
||||
|
||||
override fun onRecipientMemoValueChange(value: String) {
|
||||
override fun onRecipientMemoValueChange(value: String, isValuePasted: Boolean) {
|
||||
viewModelScope.launch {
|
||||
if (!checkIfXrpAddressValue(value)) {
|
||||
uiState = stateFactory.getOnRecipientMemoValueChange(value)
|
||||
uiState = stateFactory.getOnRecipientAddressValidationStarted()
|
||||
val isValidAddress = validateAddress(uiState.recipientState?.addressTextField?.value.orEmpty())
|
||||
uiState = stateFactory.getOnRecipientMemoValidState(value, isValidAddress)
|
||||
uiState = recipientStateFactory.getOnRecipientMemoValueChange(value, isValuePasted)
|
||||
uiState = recipientStateFactory.getOnRecipientAddressValidationStarted()
|
||||
val recipientState = uiState.getRecipientState(stateRouter.isEditState)
|
||||
val maybeValidAddress = validateAddress(recipientState?.addressTextField?.value.orEmpty())
|
||||
uiState = recipientStateFactory.getOnRecipientMemoValidState(value, maybeValidAddress.isRight())
|
||||
}
|
||||
}.saveIn(memoValidationJobHolder)
|
||||
}
|
||||
|
||||
private suspend fun validateAddress(value: String): Boolean = runCatching {
|
||||
val isValidAddress = validateWalletAddressUseCase(
|
||||
private suspend fun validateAddress(value: String): Either<ValidateAddressError, Unit> = runCatching {
|
||||
val maybeValidAddress = validateWalletAddressUseCase(
|
||||
userWalletId = userWalletId,
|
||||
network = cryptoCurrency.network,
|
||||
address = value,
|
||||
).getOrElse { false }
|
||||
val isAddressInWallet = cryptoCurrencyStatus.value.networkAddress?.availableAddresses
|
||||
?.any { it.value == value } ?: true
|
||||
onEnteredValidAddress(isValidAddress, isAddressInWallet)
|
||||
return isValidAddress
|
||||
}.getOrElse { false }
|
||||
currencyAddress = cryptoCurrencyStatus.value.networkAddress?.availableAddresses,
|
||||
)
|
||||
onEnteredValidAddress(maybeValidAddress.isLeft())
|
||||
maybeValidAddress
|
||||
}.getOrElse { ValidateAddressError.DataError(it).left() }
|
||||
|
||||
private suspend fun checkIfXrpAddressValue(value: String): Boolean {
|
||||
return BlockchainUtils.decodeRippleXAddress(value, cryptoCurrency.network.id.value)?.let { decodedAddress ->
|
||||
uiState = stateFactory.onRecipientAddressValueChange(value, isXAddress = true)
|
||||
uiState = stateFactory.getOnXAddressMemoState()
|
||||
uiState =
|
||||
recipientStateFactory.onRecipientAddressValueChange(value, isXAddress = true, isValuePasted = true)
|
||||
uiState = recipientStateFactory.getOnXAddressMemoState()
|
||||
val isValidAddress = validateAddress(decodedAddress.address)
|
||||
uiState = stateFactory.getOnRecipientAddressValidState(decodedAddress.address, isValidAddress)
|
||||
uiState = recipientStateFactory.getOnRecipientAddressValidState(decodedAddress.address, isValidAddress)
|
||||
true
|
||||
} ?: false
|
||||
}
|
||||
|
||||
private fun onEnteredValidAddress(isValidAddress: Boolean, isAddressInWallet: Boolean) {
|
||||
uiState = stateFactory.getHiddenRecentListState(
|
||||
isAddressInWallet = isAddressInWallet,
|
||||
isValidAddress = isValidAddress,
|
||||
)
|
||||
private fun onEnteredValidAddress(isNotValid: Boolean) {
|
||||
uiState = recipientStateFactory.getHiddenRecentListState(isNotValid = isNotValid)
|
||||
}
|
||||
|
||||
private fun autoNextFromRecipient(type: EnterAddressSource?, isValidAddress: Boolean) {
|
||||
|
|
@ -722,7 +717,7 @@ internal class SendViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun loadFee() {
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
viewModelScope.launch {
|
||||
val isShowStatus = uiState.feeState?.fee == null
|
||||
if (isShowStatus) {
|
||||
uiState = feeStateFactory.onFeeOnLoadingState()
|
||||
|
|
@ -756,6 +751,13 @@ internal class SendViewModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private suspend fun checkIfUtxoConsolidationAvailable() {
|
||||
isUtxoConsolidationAvailable = isUtxoConsolidationAvailableUseCase.invokeSync(
|
||||
userWalletId = userWalletId,
|
||||
network = cryptoCurrency.network,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun callFeeUseCase(): Either<GetFeeError, TransactionFee>? {
|
||||
val isFromConfirmation = stateRouter.currentState.value.isFromConfirmation
|
||||
val amountState = uiState.getAmountState(isFromConfirmation) ?: return null
|
||||
|
|
@ -860,7 +862,7 @@ internal class SendViewModel @Inject constructor(
|
|||
reduceAmountBy = uiState.sendState?.reduceAmountBy ?: BigDecimal.ZERO,
|
||||
)
|
||||
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
viewModelScope.launch {
|
||||
createTransactionUseCase(
|
||||
amount = receivingAmount.convertToAmount(cryptoCurrency),
|
||||
fee = fee,
|
||||
|
|
@ -914,10 +916,14 @@ internal class SendViewModel @Inject constructor(
|
|||
val recipientState = uiState.getRecipientState(stateRouter.isEditState) ?: return
|
||||
val destinationAddress = recipientState.addressTextField.value
|
||||
|
||||
val maybeUserWallet = userWallets.firstOrNull { it.address == destinationAddress } ?: return
|
||||
val receivingUserWallet = userWallets.firstOrNull { it.address == destinationAddress } ?: return
|
||||
|
||||
viewModelScope.launch(dispatchers.io) {
|
||||
addCryptoCurrenciesUseCase(userWalletId = maybeUserWallet.userWalletId, currency = cryptoCurrency)
|
||||
viewModelScope.launch {
|
||||
addCryptoCurrenciesUseCase(
|
||||
userWalletId = receivingUserWallet.userWalletId,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
network = receivingUserWallet.cryptoCurrency.network,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -949,7 +955,7 @@ internal class SendViewModel @Inject constructor(
|
|||
val noErrorNotifications = sendState.notifications.none { it is SendNotification.Error }
|
||||
|
||||
if (!isSuccess && noErrorNotifications) {
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
viewModelScope.launch {
|
||||
val feeUpdatedState = callFeeUseCase()?.fold(
|
||||
ifRight = {
|
||||
uiState = stateFactory.getSendingStateUpdate(isSending = false)
|
||||
|
|
@ -983,16 +989,23 @@ internal class SendViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun setNeverToShowTapHelp() {
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
viewModelScope.launch {
|
||||
neverShowTapHelpUseCase()
|
||||
}
|
||||
uiState = stateFactory.getHiddenTapHelpState()
|
||||
}
|
||||
|
||||
private fun showErrorAlert() {
|
||||
uiState = eventStateFactory.getGenericErrorState(
|
||||
onConsume = { uiState = eventStateFactory.onConsumeEventState() },
|
||||
)
|
||||
}
|
||||
// endregion
|
||||
|
||||
private companion object {
|
||||
const val CHECK_FEE_UPDATE_DELAY = 60_000L
|
||||
const val BALANCE_UPDATE_DELAY = 11_000L
|
||||
const val RECENT_LOAD_DELAY = 500L
|
||||
const val RECENT_TX_SIZE = 100
|
||||
|
||||
const val RU_LOCALE = "ru"
|
||||
|
|
|
|||
1
features/staking/api/.gitignore
vendored
Normal file
1
features/staking/api/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
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