Updated on 2026-08-14

This commit is contained in:
Tangem 2023-10-18 18:47:15 +03:00
parent 38e3891680
commit c81ce487a2
30 changed files with 403 additions and 20 deletions

View file

@ -83,6 +83,7 @@ dependencies {
implementation(projects.features.send.api)
implementation(projects.features.manageTokens.api)
implementation(projects.features.manageTokens.impl)
implementation(projects.features.send.impl)
/** AndroidX libraries */
implementation(deps.androidx.core.ktx)

View file

@ -30,6 +30,7 @@ import com.tangem.domain.card.ScanCardUseCase
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.features.managetokens.navigation.ManageTokensRouter
import com.tangem.features.send.api.navigation.SendRouter
import com.tangem.features.tester.api.TesterRouter
import com.tangem.features.tokendetails.navigation.TokenDetailsRouter
import com.tangem.features.wallet.navigation.WalletRouter
@ -121,6 +122,9 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
@Inject
lateinit var walletConnectInteractor: WalletConnectInteractor
@Inject
lateinit var sendRouter: SendRouter
private lateinit var appThemeModeFlow: SharedFlow<AppThemeMode?>
// TODO: fixme: inject through DI
@ -169,6 +173,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
tokenDetailsRouter = tokenDetailsRouter,
manageTokensRouter = manageTokensRouter,
cardSdkConfigRepository = cardSdkConfigRepository,
sendRouter = sendRouter,
),
)
}

View file

@ -38,6 +38,7 @@ import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.legacy.WalletManagersRepository
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.features.managetokens.featuretoggles.ManageTokensFeatureToggles
import com.tangem.features.send.api.featuretoggles.SendFeatureToggles
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
import com.tangem.tap.common.analytics.AnalyticsFactory
import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
@ -216,6 +217,9 @@ internal class TapApplication : Application(), ImageLoaderFactory {
@Inject
lateinit var walletsRepository: WalletsRepository
@Inject
lateinit var sendFeatureToggles: SendFeatureToggles
// endregion Injected
override fun onCreate() {
@ -307,6 +311,7 @@ internal class TapApplication : Application(), ImageLoaderFactory {
balanceHidingRepository = balanceHidingRepository,
detailsFeatureToggles = detailsFeatureToggles,
walletsRepository = walletsRepository,
sendFeatureToggles = sendFeatureToggles,
),
),
)

View file

@ -59,12 +59,8 @@ fun FragmentActivity.openFragment(
}
}
if (screen.isDialogFragment) {
val dialogFragment = requireNotNull(fragment as? DialogFragment) {
"If screen.isDialogFragment == true then fragment must be a DialogFragment"
}
dialogFragment.showAllowingStateLoss(
if (screen.isDialogFragment && fragment is DialogFragment) {
fragment.showAllowingStateLoss(
fragmentManager = supportFragmentManager,
baseTransaction = transaction,
tag = screen.name,
@ -125,7 +121,7 @@ fun FragmentActivity.getPreviousScreen(): AppScreen? {
return tag?.let { AppScreen.valueOf(tag) }
}
@Suppress("ComplexMethod")
@Suppress("ComplexMethod", "LongMethod")
private fun fragmentFactory(screen: AppScreen): Fragment {
return when (screen) {
AppScreen.Home -> HomeFragment()
@ -146,7 +142,18 @@ private fun fragmentFactory(screen: AppScreen): Fragment {
WalletFragment()
}
}
AppScreen.Send -> SendFragment()
AppScreen.Send -> {
val featureToggles = store.state.daggerGraphState.get(
getDependency = DaggerGraphState::sendFeatureToggles,
)
if (featureToggles.isRedesignedSendEnabled) {
store.state.daggerGraphState
.get(getDependency = DaggerGraphState::sendRouter)
.getEntryFragment()
} else {
SendFragment()
}
}
AppScreen.Details -> DetailsFragment()
AppScreen.DetailsSecurity -> SecurityModeFragment()
AppScreen.CardSettings -> CardSettingsFragment()

View file

@ -9,7 +9,7 @@ import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
import com.tangem.features.send.navigation.SendRouter
import com.tangem.features.send.api.navigation.SendRouter
import com.tangem.tap.di.DelayedWork
import com.tangem.tap.features.send.redux.AmountAction
import com.tangem.tap.proxy.AppStateHolder

View file

@ -15,7 +15,7 @@ import com.tangem.domain.tokens.legacy.TradeCryptoAction
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.feature.swap.presentation.SwapFragment
import com.tangem.features.send.navigation.SendRouter
import com.tangem.features.send.api.navigation.SendRouter
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.Token
import com.tangem.tap.common.apptheme.MutableAppThemeModeHolder

View file

@ -3,6 +3,7 @@ package com.tangem.tap.proxy.redux
import com.tangem.domain.card.ScanCardUseCase
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.features.managetokens.navigation.ManageTokensRouter
import com.tangem.features.send.api.navigation.SendRouter
import com.tangem.features.tester.api.TesterRouter
import com.tangem.features.tokendetails.navigation.TokenDetailsRouter
import com.tangem.features.wallet.navigation.WalletRouter
@ -19,5 +20,6 @@ sealed interface DaggerGraphAction : Action {
val tokenDetailsRouter: TokenDetailsRouter,
val manageTokensRouter: ManageTokensRouter,
val cardSdkConfigRepository: CardSdkConfigRepository,
val sendRouter: SendRouter,
) : DaggerGraphAction
}

View file

@ -20,6 +20,7 @@ object DaggerGraphReducer {
tokenDetailsRouter = action.tokenDetailsRouter,
manageTokensRouter = action.manageTokensRouter,
cardSdkConfigRepository = action.cardSdkConfigRepository,
sendRouter = action.sendRouter,
)
}
}

View file

@ -14,6 +14,8 @@ import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.features.managetokens.featuretoggles.ManageTokensFeatureToggles
import com.tangem.features.managetokens.navigation.ManageTokensRouter
import com.tangem.features.send.api.featuretoggles.SendFeatureToggles
import com.tangem.features.send.api.navigation.SendRouter
import com.tangem.features.tester.api.TesterRouter
import com.tangem.features.tokendetails.navigation.TokenDetailsRouter
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
@ -50,6 +52,8 @@ data class DaggerGraphState(
val detailsFeatureToggles: DetailsFeatureToggles? = null,
val walletsRepository: WalletsRepository? = null,
val networksRepository: NetworksRepository? = null,
val sendFeatureToggles: SendFeatureToggles? = null,
val sendRouter: SendRouter? = null,
// FIXME: It is used only for TokensList screen. Remove after refactoring of TokensList
val currenciesRepository: CurrenciesRepository? = null,

View file

@ -30,5 +30,9 @@
{
"name": "REDESIGNED_MANAGE_TOKENS_SCREEN_ENABLED",
"version": "undefined"
},
{
"name": "REDESIGNED_SEND_SCREEN_ENABLED",
"version": "undefined"
}
]

View file

@ -19,7 +19,7 @@ enum class AppScreen(val isDialogFragment: Boolean = false) {
OnboardingOther,
Wallet,
WalletDetails,
Send,
Send(isDialogFragment = true),
Details,
DetailsSecurity,
CardSettings,

View file

@ -114,6 +114,7 @@
<string name="common_submit">Submit</string>
<string name="common_success">Success</string>
<string name="common_swap">Swap</string>
<string name="common_next">Next</string>
<string name="common_terms_and_conditions">terms and conditions</string>
<string name="common_transaction_failed">Transaction failed</string>
<string name="common_transactions">Transactions</string>

View file

@ -8,14 +8,15 @@ import androidx.compose.foundation.layout.size
import androidx.compose.material.Surface
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import com.tangem.core.ui.res.TangemTheme
@Composable
fun TangemBottomSheetDraggableHeader() {
fun TangemBottomSheetDraggableHeader(color: Color = TangemTheme.colors.background.primary) {
Surface(
modifier = Modifier
.height(TangemTheme.dimens.size20),
color = TangemTheme.colors.background.primary,
color = color,
) {
Box(
modifier = Modifier

View file

@ -170,6 +170,7 @@ class TangemColors internal constructor(
plain = other.plain
action = other.action
fade = other.fade
tertiary = other.tertiary
}
}

View file

@ -32,6 +32,7 @@ data class TangemDimens internal constructor(
val radius16: Dp = 16.dp,
val radius18: Dp = 18.dp,
val radius24: Dp = 24.dp,
val radius26: Dp = 26.dp,
val radius28: Dp = 28.dp,
val radius36: Dp = 36.dp,
// endregion Radius
@ -88,6 +89,7 @@ data class TangemDimens internal constructor(
// region Spacing
val spacing0: Dp = 0.dp,
val spacing0_5: Dp = 0.5.dp,
val spacing1: Dp = 1.dp,
val spacing2: Dp = 2.dp,
val spacing3: Dp = 3.dp,
val spacing4: Dp = 4.dp,

View file

@ -3,12 +3,7 @@ package com.tangem.core.ui.res
import androidx.compose.material.Colors
import androidx.compose.material.MaterialTheme
import androidx.compose.material.ProvideTextStyle
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.remember
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.runtime.*
// TODO: use isSystemInDarkTheme() for automatic color detection
internal const val IS_SYSTEM_IN_DARK_THEME: Boolean = false

View file

@ -0,0 +1,10 @@
package com.tangem.features.send.api.featuretoggles
/**
* Send feature toggles
*/
interface SendFeatureToggles {
/** Availability of redesigned send screen */
val isRedesignedSendEnabled: Boolean
}

View file

@ -1,4 +1,4 @@
package com.tangem.features.send.navigation
package com.tangem.features.send.api.navigation
import androidx.fragment.app.Fragment

1
features/send/impl/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/build

View file

@ -0,0 +1,40 @@
plugins {
alias(deps.plugins.android.library)
alias(deps.plugins.kotlin.android)
alias(deps.plugins.kotlin.kapt)
alias(deps.plugins.hilt.android)
id("configuration")
}
android {
namespace = "com.tangem.features.send.impl"
}
dependencies {
/** AndroidX */
implementation(deps.androidx.fragment.ktx)
implementation(deps.androidx.appCompat)
implementation(deps.material)
/** Compose */
implementation(deps.compose.accompanist.systemUiController)
implementation(deps.compose.material3)
implementation(deps.compose.foundation)
implementation(deps.compose.ui)
implementation(deps.compose.ui.tooling)
implementation(deps.compose.navigation)
/** Core modules */
implementation(projects.core.featuretoggles)
implementation(projects.core.ui)
/** Domain modules */
implementation(projects.domain.tokens.models)
/** Feature modules */
implementation(projects.features.send.api)
/** DI */
implementation(deps.hilt.android)
kapt(deps.hilt.kapt)
}

View file

@ -0,0 +1,24 @@
package com.tangem.features.send.impl.di
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
import com.tangem.features.send.api.featuretoggles.SendFeatureToggles
import com.tangem.features.send.impl.featuretoggles.DefaultSendFeatureToggles
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
/**
* DI module provides implementation of [SendFeatureToggles]
*/
@Module
@InstallIn(SingletonComponent::class)
internal object SendFeatureTogglesModule {
@Provides
@Singleton
fun provideSendFeatureToggles(featureTogglesManager: FeatureTogglesManager): SendFeatureToggles {
return DefaultSendFeatureToggles(featureTogglesManager = featureTogglesManager)
}
}

View file

@ -0,0 +1,23 @@
package com.tangem.features.send.impl.di
import com.tangem.features.send.api.navigation.SendRouter
import com.tangem.features.send.impl.navigation.DefaultSendRouter
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ActivityComponent
import dagger.hilt.android.scopes.ActivityScoped
/**
* DI module provides implementation of [SendRouter]
*/
@Module
@InstallIn(ActivityComponent::class)
internal object SendRouterModule {
@Provides
@ActivityScoped
fun provideSendRouter(): SendRouter {
return DefaultSendRouter()
}
}

View file

@ -0,0 +1,16 @@
package com.tangem.features.send.impl.featuretoggles
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
import com.tangem.features.send.api.featuretoggles.SendFeatureToggles
/**
* Default implementation of Send feature toggles
*
* @property featureTogglesManager manager for getting information about the availability of feature toggles
*/
internal class DefaultSendFeatureToggles(
private val featureTogglesManager: FeatureTogglesManager,
) : SendFeatureToggles {
override val isRedesignedSendEnabled: Boolean
get() = featureTogglesManager.isFeatureEnabled(name = "REDESIGNED_SEND_SCREEN_ENABLED")
}

View file

@ -0,0 +1,9 @@
package com.tangem.features.send.impl.navigation
import androidx.fragment.app.Fragment
import com.tangem.features.send.api.navigation.SendRouter
import com.tangem.features.send.impl.presentation.SendFragment
internal class DefaultSendRouter : SendRouter {
override fun getEntryFragment(): Fragment = SendFragment.create()
}

View file

@ -0,0 +1,41 @@
package com.tangem.features.send.impl.presentation
import androidx.activity.compose.BackHandler
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import com.tangem.core.ui.components.SystemBarsEffect
import com.tangem.core.ui.screen.ComposeBottomSheetFragment
import com.tangem.core.ui.theme.AppThemeModeHolder
import com.tangem.features.send.impl.presentation.send.ui.SendScreen
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
/**
* Send fragment
*/
@AndroidEntryPoint
internal class SendFragment : ComposeBottomSheetFragment() {
@Inject
override lateinit var appThemeModeHolder: AppThemeModeHolder
override val expandedHeightFraction: Float = 1f
@Composable
override fun ScreenContent(modifier: Modifier) {
SystemBarsEffect {
setSystemBarsColor(color = Color.Transparent)
}
BackHandler {
dismiss()
}
SendScreen()
}
companion object {
/** Create send fragment instance */
fun create(): SendFragment = SendFragment()
}
}

View file

@ -0,0 +1,7 @@
package com.tangem.features.send.impl.presentation.send.ui
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
data class SendBottomSheetConfig(
val currentState: SendStates,
) : TangemBottomSheetConfigContent

View file

@ -0,0 +1,96 @@
package com.tangem.features.send.impl.presentation.send.ui
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import com.tangem.core.ui.R
import com.tangem.core.ui.components.PrimaryButton
import com.tangem.core.ui.components.PrimaryButtonIconEnd
import com.tangem.core.ui.res.TangemTheme
@Composable
internal fun SendNavigationButtons(currentState: SendStates) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = TangemTheme.dimens.spacing12),
) {
SendSecondaryNavigationButton(currentState)
SendPrimaryNavigationButton(
currentState,
modifier = Modifier
.weight(1f)
.padding(horizontal = TangemTheme.dimens.spacing16),
)
}
}
@Composable
private fun SendSecondaryNavigationButton(currentState: SendStates) {
AnimatedVisibility(
visible = currentState == SendStates.Recipient || currentState == SendStates.Fee,
) {
Icon(
modifier = Modifier
.padding(start = TangemTheme.dimens.spacing16)
.clip(RoundedCornerShape(TangemTheme.dimens.radius16))
.background(TangemTheme.colors.button.secondary)
.clickable {
// todo add prev click
}
.padding(TangemTheme.dimens.spacing12),
painter = painterResource(R.drawable.ic_back_24),
contentDescription = null,
)
}
}
@Composable
private fun SendPrimaryNavigationButton(currentState: SendStates, modifier: Modifier = Modifier) {
val buttonTextId = when (currentState) {
SendStates.Amount,
SendStates.Recipient,
SendStates.Fee,
-> R.string.common_next
SendStates.Filled -> R.string.common_send
SendStates.Done -> R.string.common_close
}
AnimatedContent(
targetState = buttonTextId,
label = "Update send screen state",
modifier = modifier,
) { textId ->
when (currentState) {
SendStates.Amount,
SendStates.Recipient,
SendStates.Fee,
SendStates.Done,
-> PrimaryButton(
text = stringResource(textId),
onClick = {
// todo add next click
},
)
SendStates.Filled -> {
PrimaryButtonIconEnd(
text = stringResource(textId),
iconResId = R.drawable.ic_tangem_24,
onClick = {
// todo add next click
},
)
}
}
}
}

View file

@ -0,0 +1,52 @@
package com.tangem.features.send.impl.presentation.send.ui
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.scrollable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetDraggableHeader
import com.tangem.core.ui.res.TangemTheme
@Composable
fun SendScreen() {
// todo will be removed
val config = remember { SendBottomSheetConfig(SendStates.Amount) }
Column(
modifier = Modifier
.imePadding()
.background(
color = TangemTheme.colors.background.tertiary,
shape = RoundedCornerShape(
topStart = TangemTheme.dimens.radius24,
topEnd = TangemTheme.dimens.radius24,
),
),
horizontalAlignment = Alignment.CenterHorizontally,
) {
TangemBottomSheetDraggableHeader(
color = TangemTheme.colors.background.tertiary,
)
Box(
modifier = Modifier
.weight(1f)
.scrollable(state = rememberScrollState(), orientation = Orientation.Vertical),
) {
SendScreenContent()
}
SendNavigationButtons(config.currentState)
}
}
@Composable
fun SendScreenContent() {
// todo work in progress
}

View file

@ -0,0 +1,34 @@
package com.tangem.features.send.impl.presentation.send.ui
/**
* Screen states of Send
*/
enum class SendStates {
Amount,
Recipient,
Fee,
Filled,
Done,
;
companion object {
/** Get next [SendStates] */
fun SendStates.next(): SendStates {
return if (this.ordinal < SendStates.values().last().ordinal) {
SendStates.values()[this.ordinal + 1]
} else {
this
}
}
/** Get previous [SendStates] */
fun SendStates.previous(): SendStates {
return if (this.ordinal > 0) {
SendStates.values()[this.ordinal - 1]
} else {
this
}
}
}
}

View file

@ -93,6 +93,7 @@ include(":features:learn2earn:api")
include(":features:learn2earn:impl")
include(":features:send:api")
include(":features:send:impl")
include(":features:manage-tokens:api")
include(":features:manage-tokens:impl")