Updated on 2026-08-14
This commit is contained in:
parent
8d3c2b1cd8
commit
f84d6a1261
15 changed files with 694 additions and 4 deletions
|
|
@ -9,4 +9,13 @@ android {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
/** Core */
|
||||
api(projects.core.decompose)
|
||||
api(projects.core.ui)
|
||||
|
||||
/** Domain */
|
||||
api(projects.domain.models)
|
||||
|
||||
/** Compose */
|
||||
implementation(deps.compose.runtime)
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.tangem.features.virtualaccount.details.component
|
||||
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
|
||||
interface VirtualAccountMainComponent : ComposableContentComponent {
|
||||
|
||||
data class Params(
|
||||
val userWalletId: UserWalletId,
|
||||
)
|
||||
|
||||
interface Factory : ComponentFactory<Params, VirtualAccountMainComponent>
|
||||
}
|
||||
|
|
@ -11,11 +11,25 @@ android {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
/** Core */
|
||||
implementation(projects.core.configToggles)
|
||||
implementation(projects.core.decompose)
|
||||
implementation(projects.core.res)
|
||||
implementation(projects.core.ui)
|
||||
implementation(projects.core.utils)
|
||||
|
||||
/** Domain */
|
||||
implementation(projects.domain.models)
|
||||
|
||||
/** Features */
|
||||
implementation(projects.features.virtualAccounts.details.api)
|
||||
|
||||
implementation(projects.core.configToggles)
|
||||
|
||||
implementation(deps.compose.runtime)
|
||||
/** Compose */
|
||||
implementation(deps.compose.foundation)
|
||||
implementation(deps.compose.material3)
|
||||
implementation(deps.compose.ui)
|
||||
implementation(deps.compose.ui.tooling)
|
||||
implementation(deps.decompose.ext.compose)
|
||||
|
||||
/** DI */
|
||||
implementation(deps.hilt.android)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,115 @@
|
|||
package com.tangem.features.virtualaccount.common.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.togetherWith
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.text.TextAutoSize
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.text.applyBladeBrush
|
||||
import com.tangem.core.ui.ds2.shimmers.TextShimmer
|
||||
import com.tangem.core.ui.ds2.shimmers.TextShimmerStyle
|
||||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
||||
import com.tangem.utils.StringsSigns.DASH_SIGN
|
||||
|
||||
@Composable
|
||||
fun TangemBalanceHeader(
|
||||
state: TangemBalanceHeaderState,
|
||||
label: TextReference,
|
||||
modifier: Modifier = Modifier,
|
||||
balanceModifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x2),
|
||||
) {
|
||||
AnimatedContent(
|
||||
targetState = state,
|
||||
label = "Updating the balance",
|
||||
transitionSpec = {
|
||||
fadeIn(animationSpec = tween(durationMillis = 220, delayMillis = 90)) togetherWith
|
||||
fadeOut(animationSpec = tween(durationMillis = 90))
|
||||
},
|
||||
) { animatedState ->
|
||||
when (animatedState) {
|
||||
is TangemBalanceHeaderState.Loading -> TextShimmer(
|
||||
modifier = Modifier.size(width = 160.dp, height = 56.dp),
|
||||
text = "1234.00",
|
||||
style = TextShimmerStyle.HEADING_MEDIUM,
|
||||
radius = TangemTheme.dimens2.x25,
|
||||
)
|
||||
is TangemBalanceHeaderState.Content -> Text(
|
||||
modifier = balanceModifier,
|
||||
text = animatedState.balance
|
||||
.orMaskWithStars(animatedState.isBalanceHidden)
|
||||
.resolveAnnotatedReference(),
|
||||
style = TangemTheme.typography3.display.medium.applyBladeBrush(
|
||||
isEnabled = animatedState.isFlickering,
|
||||
textColor = TangemTheme.colors3.text.primary,
|
||||
),
|
||||
color = TangemTheme.colors3.text.primary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
autoSize = TextAutoSize.StepBased(
|
||||
minFontSize = TangemTheme.typography3.heading.medium.fontSize,
|
||||
maxFontSize = TangemTheme.typography3.display.medium.fontSize,
|
||||
),
|
||||
)
|
||||
is TangemBalanceHeaderState.Error -> Text(
|
||||
modifier = balanceModifier,
|
||||
text = DASH_SIGN,
|
||||
style = TangemTheme.typography3.display.medium,
|
||||
color = TangemTheme.colors3.text.primary,
|
||||
)
|
||||
}
|
||||
}
|
||||
Text(
|
||||
modifier = Modifier.padding(vertical = TangemTheme.dimens2.x1),
|
||||
text = label.resolveReference(),
|
||||
color = TangemTheme.colors3.text.secondary,
|
||||
style = TangemTheme.typography3.caption.medium,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun TangemBalanceHeaderPreview() {
|
||||
TangemThemePreviewRedesign {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
) {
|
||||
TangemBalanceHeader(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
state = TangemBalanceHeaderState.Content(
|
||||
balance = stringReference("$0.00"),
|
||||
isBalanceHidden = false,
|
||||
),
|
||||
label = stringReference("Total balance"),
|
||||
)
|
||||
TangemBalanceHeader(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
state = TangemBalanceHeaderState.Loading,
|
||||
label = stringReference("Total balance"),
|
||||
)
|
||||
TangemBalanceHeader(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
state = TangemBalanceHeaderState.Error,
|
||||
label = stringReference("Total balance"),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.features.virtualaccount.common.ui
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
|
||||
@Immutable
|
||||
sealed interface TangemBalanceHeaderState {
|
||||
|
||||
data object Loading : TangemBalanceHeaderState
|
||||
|
||||
data class Content(
|
||||
val balance: TextReference,
|
||||
val isBalanceHidden: Boolean,
|
||||
val isFlickering: Boolean = false,
|
||||
) : TangemBalanceHeaderState
|
||||
|
||||
data object Error : TangemBalanceHeaderState
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
package com.tangem.features.virtualaccount.common.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.text.TextAutoSize
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.ds.image.TangemIconUM
|
||||
import com.tangem.core.ui.ds2.button.TangemButton
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveAnnotatedReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
||||
import com.tangem.core.ui.res.generated.icons.Icons
|
||||
import com.tangem.core.ui.res.generated.icons.ic_arrow_down_24
|
||||
|
||||
@Composable
|
||||
fun TangemCircleActionButton(
|
||||
title: TextReference,
|
||||
icon: TangemIconUM,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
isEnabled: Boolean = true,
|
||||
isLoading: Boolean = false,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x2),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
TangemButton(
|
||||
variant = TangemButton.Variant.Material,
|
||||
size = TangemButton.Size.X14,
|
||||
onClick = onClick,
|
||||
iconStart = icon,
|
||||
isLoading = isLoading,
|
||||
isEnabled = isEnabled,
|
||||
)
|
||||
Text(
|
||||
text = title.resolveAnnotatedReference(),
|
||||
style = TangemTheme.typography3.subheading.medium,
|
||||
color = TangemTheme.colors3.text.primary,
|
||||
autoSize = TextAutoSize.StepBased(
|
||||
minFontSize = TangemTheme.typography3.caption.medium.fontSize,
|
||||
maxFontSize = TangemTheme.typography3.subheading.medium.fontSize,
|
||||
),
|
||||
maxLines = 1,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun TangemCircleActionButtonPreview() {
|
||||
TangemThemePreviewRedesign {
|
||||
TangemCircleActionButton(
|
||||
title = stringReference("Action"),
|
||||
icon = TangemIconUM.Icon(
|
||||
imageVector = Icons.ic_arrow_down_24,
|
||||
tintReference = { TangemTheme.colors3.icon.primary },
|
||||
),
|
||||
onClick = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
package com.tangem.features.virtualaccount.common.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
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.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
||||
import com.tangem.core.ui.res.generated.icons.Icons
|
||||
import com.tangem.core.ui.res.generated.icons.ic_binoculars_20
|
||||
|
||||
@Composable
|
||||
fun TangemEmptyState(
|
||||
icon: ImageVector,
|
||||
text: TextReference,
|
||||
modifier: Modifier = Modifier,
|
||||
iconModifier: Modifier = Modifier,
|
||||
textModifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x3, Alignment.CenterVertically),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens2.x10)
|
||||
.background(
|
||||
color = TangemTheme.colors3.bg.opaque.primary,
|
||||
shape = CircleShape,
|
||||
)
|
||||
.padding(10.dp)
|
||||
.then(iconModifier),
|
||||
imageVector = icon,
|
||||
tint = TangemTheme.colors3.icon.secondary,
|
||||
contentDescription = null,
|
||||
)
|
||||
|
||||
Text(
|
||||
modifier = textModifier,
|
||||
textAlign = TextAlign.Center,
|
||||
text = text.resolveReference(),
|
||||
style = TangemTheme.typography3.caption.medium,
|
||||
color = TangemTheme.colors3.text.secondary,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun TangemEmptyStatePreview() {
|
||||
TangemThemePreviewRedesign {
|
||||
TangemEmptyState(
|
||||
icon = Icons.ic_binoculars_20,
|
||||
text = stringReference("No transactions yet\nStart spending and see history here"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -11,7 +11,7 @@ import javax.inject.Singleton
|
|||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object VirtualAccountDetailsModule {
|
||||
internal object VirtualAccountMainModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package com.tangem.features.virtualaccount.main
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.features.virtualaccount.details.component.VirtualAccountMainComponent
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
internal class DefaultVirtualAccountMainComponent @AssistedInject constructor(
|
||||
@Assisted appComponentContext: AppComponentContext,
|
||||
@Assisted params: VirtualAccountMainComponent.Params,
|
||||
) : VirtualAccountMainComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
private val model: VirtualAccountMainModel = getOrCreateModel(params = params)
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val state by model.uiState.collectAsStateWithLifecycle()
|
||||
VirtualAccountMainScreen(state = state, modifier = modifier)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : VirtualAccountMainComponent.Factory {
|
||||
override fun create(
|
||||
context: AppComponentContext,
|
||||
params: VirtualAccountMainComponent.Params,
|
||||
): DefaultVirtualAccountMainComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package com.tangem.features.virtualaccount.main
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.features.virtualaccount.details.component.VirtualAccountMainComponent
|
||||
import com.tangem.features.virtualaccount.details.impl.R
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import javax.inject.Inject
|
||||
|
||||
@Stable
|
||||
@ModelScoped
|
||||
internal class VirtualAccountMainModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val router: Router,
|
||||
) : Model() {
|
||||
|
||||
@Suppress("UnusedPrivateProperty")
|
||||
private val params = paramsContainer.require<VirtualAccountMainComponent.Params>()
|
||||
|
||||
val uiState: StateFlow<VirtualAccountMainUM>
|
||||
field = MutableStateFlow(
|
||||
createInitialState(),
|
||||
)
|
||||
|
||||
private fun createInitialState(): VirtualAccountMainUM = VirtualAccountMainUM(
|
||||
title = resourceReference(R.string.virtual_account_title),
|
||||
subtitle = resourceReference(R.string.tangempay_usdc_on_polygon_network),
|
||||
balance = VirtualAccountBalanceBlockState.Content(
|
||||
fiatBalance = stringReference("$0.00"),
|
||||
isBalanceFlickering = false,
|
||||
),
|
||||
isBalanceHidden = false,
|
||||
onBackClick = { router.pop() },
|
||||
onMenuClick = {},
|
||||
onAddFundsClick = {},
|
||||
onSendClick = {},
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,229 @@
|
|||
package com.tangem.features.virtualaccount.main
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.tooling.preview.Devices
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.SpacerH24
|
||||
import com.tangem.core.ui.components.topFade
|
||||
import com.tangem.core.ui.ds.image.TangemIconUM
|
||||
import com.tangem.core.ui.ds.topbar.TangemTopBar
|
||||
import com.tangem.core.ui.ds2.button.TangemButton
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
||||
import com.tangem.core.ui.res.generated.icons.*
|
||||
import com.tangem.features.virtualaccount.common.ui.TangemBalanceHeader
|
||||
import com.tangem.features.virtualaccount.common.ui.TangemBalanceHeaderState
|
||||
import com.tangem.features.virtualaccount.common.ui.TangemCircleActionButton
|
||||
import com.tangem.features.virtualaccount.common.ui.TangemEmptyState
|
||||
import com.tangem.features.virtualaccount.details.impl.R
|
||||
import com.tangem.core.ui.R as CoreUiR
|
||||
|
||||
private val InitialTopBarHeight: Dp = 64.dp
|
||||
private const val TOP_FADE_MID_STOP = 0.8f
|
||||
private const val TOP_FADE_MID_ALPHA = 0.8f
|
||||
|
||||
@Composable
|
||||
internal fun VirtualAccountMainScreen(state: VirtualAccountMainUM, modifier: Modifier = Modifier) {
|
||||
val listState = rememberLazyListState()
|
||||
val density = LocalDensity.current
|
||||
val statusBarHeight = with(density) { WindowInsets.systemBars.getTop(this).toDp() }
|
||||
val bottomBarHeight = with(density) { WindowInsets.systemBars.getBottom(this).toDp() }
|
||||
var topBarTotalHeight by remember { mutableStateOf(InitialTopBarHeight + statusBarHeight) }
|
||||
val rootBackground = TangemTheme.colors3.bg.primary
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.background(rootBackground),
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.topFade(
|
||||
height = topBarTotalHeight,
|
||||
0f to rootBackground,
|
||||
TOP_FADE_MID_STOP to rootBackground.copy(alpha = TOP_FADE_MID_ALPHA),
|
||||
1f to Color.Transparent,
|
||||
),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
state = listState,
|
||||
contentPadding = PaddingValues(
|
||||
top = topBarTotalHeight,
|
||||
bottom = TangemTheme.dimens2.x4 + bottomBarHeight,
|
||||
),
|
||||
) {
|
||||
body(
|
||||
state = state,
|
||||
listState = listState,
|
||||
)
|
||||
}
|
||||
TopBar(
|
||||
state = state,
|
||||
onHeightChange = { measuredHeight ->
|
||||
if (topBarTotalHeight != measuredHeight) topBarTotalHeight = measuredHeight
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun LazyListScope.body(state: VirtualAccountMainUM, listState: LazyListState) {
|
||||
item("balanceBlock") {
|
||||
BalanceBlock(
|
||||
state = state.balance,
|
||||
isBalanceHidden = state.isBalanceHidden,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = TangemTheme.dimens2.x4)
|
||||
.padding(top = TangemTheme.dimens2.x12),
|
||||
)
|
||||
}
|
||||
item("actionButtonsBlock") {
|
||||
SpacerH24()
|
||||
ActionBlock(state = state)
|
||||
}
|
||||
item("emptyTransactions") {
|
||||
SpacerH24()
|
||||
TangemEmptyState(
|
||||
icon = Icons.ic_binoculars_20,
|
||||
text = resourceReference(R.string.virtual_account_transactions_empty),
|
||||
modifier = Modifier
|
||||
.heightIn(min = rememberRemainingViewportHeight(listState, "emptyTransactions"))
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = TangemTheme.dimens2.x4, vertical = TangemTheme.dimens2.x3),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BalanceBlock(
|
||||
state: VirtualAccountBalanceBlockState,
|
||||
isBalanceHidden: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
TangemBalanceHeader(
|
||||
state = when (state) {
|
||||
is VirtualAccountBalanceBlockState.Loading -> TangemBalanceHeaderState.Loading
|
||||
is VirtualAccountBalanceBlockState.Content -> TangemBalanceHeaderState.Content(
|
||||
balance = state.fiatBalance,
|
||||
isFlickering = state.isBalanceFlickering,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
)
|
||||
is VirtualAccountBalanceBlockState.Error -> TangemBalanceHeaderState.Error
|
||||
},
|
||||
label = resourceReference(R.string.token_details_balance_total),
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LazyItemScope.ActionBlock(state: VirtualAccountMainUM, modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
) {
|
||||
TangemCircleActionButton(
|
||||
modifier = Modifier.padding(horizontal = TangemTheme.dimens2.x4),
|
||||
title = resourceReference(R.string.common_add_funds),
|
||||
icon = TangemIconUM.Icon(
|
||||
imageVector = Icons.ic_arrow_down_24,
|
||||
tintReference = { TangemTheme.colors3.icon.primary },
|
||||
),
|
||||
onClick = state.onAddFundsClick,
|
||||
)
|
||||
TangemCircleActionButton(
|
||||
modifier = Modifier.padding(horizontal = TangemTheme.dimens2.x4),
|
||||
title = resourceReference(R.string.common_send),
|
||||
icon = TangemIconUM.Icon(
|
||||
imageVector = Icons.ic_arrow_up_24,
|
||||
tintReference = { TangemTheme.colors3.icon.primary },
|
||||
),
|
||||
onClick = state.onSendClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TopBar(state: VirtualAccountMainUM, onHeightChange: (Dp) -> Unit, modifier: Modifier = Modifier) {
|
||||
val density = LocalDensity.current
|
||||
TangemTopBar(
|
||||
modifier = modifier
|
||||
.onSizeChanged { size -> onHeightChange(with(density) { size.height.toDp() }) }
|
||||
.statusBarsPadding(),
|
||||
title = state.title,
|
||||
subtitle = state.subtitle,
|
||||
startContent = {
|
||||
TangemButton(
|
||||
iconStart = TangemIconUM.Icon(iconRes = CoreUiR.drawable.ic_arrow_back_28),
|
||||
onClick = state.onBackClick,
|
||||
size = TangemButton.Size.X11,
|
||||
variant = TangemButton.Variant.Material,
|
||||
)
|
||||
},
|
||||
endContent = {
|
||||
TangemButton(
|
||||
iconStart = TangemIconUM.Icon(imageVector = Icons.ic_dots_vertical_24),
|
||||
onClick = state.onMenuClick,
|
||||
size = TangemButton.Size.X11,
|
||||
variant = TangemButton.Variant.Material,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the height left between the top of the item identified by [itemKey] and the bottom of the
|
||||
* list's viewport (excluding bottom content padding). Returns `0.dp` until the item has been laid out.
|
||||
*
|
||||
* The item's own height does not affect its offset (only the items above it do), so reading the offset
|
||||
* back to size the item is stable and does not loop.
|
||||
*/
|
||||
@Composable
|
||||
private fun rememberRemainingViewportHeight(listState: LazyListState, itemKey: Any): Dp {
|
||||
val density = LocalDensity.current
|
||||
val remainingPx by remember(listState, itemKey) {
|
||||
derivedStateOf {
|
||||
val info = listState.layoutInfo
|
||||
val item = info.visibleItemsInfo.firstOrNull { it.key == itemKey }
|
||||
?: return@derivedStateOf 0
|
||||
(info.viewportEndOffset - info.afterContentPadding - item.offset).coerceAtLeast(minimumValue = 0)
|
||||
}
|
||||
}
|
||||
return with(density) { remainingPx.toDp() }
|
||||
}
|
||||
|
||||
@Preview(device = Devices.PIXEL_7_PRO)
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES, device = Devices.PIXEL_7_PRO)
|
||||
@Composable
|
||||
private fun VirtualAccountMainScreenPreview() {
|
||||
TangemThemePreviewRedesign {
|
||||
VirtualAccountMainScreen(
|
||||
state = VirtualAccountMainUM(
|
||||
title = resourceReference(R.string.virtual_account_title),
|
||||
subtitle = resourceReference(R.string.tangempay_usdc_on_polygon_network),
|
||||
balance = VirtualAccountBalanceBlockState.Content(
|
||||
fiatBalance = stringReference("$0.00"),
|
||||
isBalanceFlickering = false,
|
||||
),
|
||||
isBalanceHidden = false,
|
||||
onBackClick = {},
|
||||
onMenuClick = {},
|
||||
onAddFundsClick = {},
|
||||
onSendClick = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package com.tangem.features.virtualaccount.main
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
|
||||
@Immutable
|
||||
internal data class VirtualAccountMainUM(
|
||||
val title: TextReference,
|
||||
val subtitle: TextReference,
|
||||
val balance: VirtualAccountBalanceBlockState,
|
||||
val isBalanceHidden: Boolean,
|
||||
val onBackClick: () -> Unit,
|
||||
val onMenuClick: () -> Unit,
|
||||
val onAddFundsClick: () -> Unit,
|
||||
val onSendClick: () -> Unit,
|
||||
)
|
||||
|
||||
@Immutable
|
||||
internal sealed class VirtualAccountBalanceBlockState {
|
||||
|
||||
data object Loading : VirtualAccountBalanceBlockState()
|
||||
|
||||
data class Content(
|
||||
val fiatBalance: TextReference,
|
||||
val isBalanceFlickering: Boolean,
|
||||
) : VirtualAccountBalanceBlockState()
|
||||
|
||||
data object Error : VirtualAccountBalanceBlockState()
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.features.virtualaccount.main.di
|
||||
|
||||
import com.tangem.features.virtualaccount.main.DefaultVirtualAccountMainComponent
|
||||
import com.tangem.features.virtualaccount.details.component.VirtualAccountMainComponent
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal interface VirtualAccountMainComponentModule {
|
||||
|
||||
@Binds
|
||||
fun bindVirtualAccountMainComponentFactory(
|
||||
factory: DefaultVirtualAccountMainComponent.Factory,
|
||||
): VirtualAccountMainComponent.Factory
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.features.virtualaccount.main.di
|
||||
|
||||
import com.tangem.core.decompose.di.ModelComponent
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.features.virtualaccount.main.VirtualAccountMainModel
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.multibindings.ClassKey
|
||||
import dagger.multibindings.IntoMap
|
||||
|
||||
@Module
|
||||
@InstallIn(ModelComponent::class)
|
||||
internal interface VirtualAccountMainModelModule {
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(VirtualAccountMainModel::class)
|
||||
fun bindVirtualAccountMainModel(model: VirtualAccountMainModel): Model
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@ internal fun BaseExtension.configureCompilerOptions() {
|
|||
internal fun BaseExtension.configureCompose(project: Project) {
|
||||
val useCompose = with(project.path) {
|
||||
contains(":ui") ||
|
||||
contains(Regex(pattern = ":common-ui\$")) || // shared Composable UI component modules
|
||||
contains(":common:ui-charts") ||
|
||||
contains(":features:onboarding") || // TODO: divide on api/impl after migrating all onboarding to module
|
||||
contains(Regex(pattern = ":presentation\$")) ||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue