diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 2d3445cf24..b536124d5f 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -37,6 +37,8 @@ dependencies { implementation(project(":features:swap:data")) implementation(project(":features:tester:api")) implementation(project(":features:tester:impl")) + implementation(project(":features:wallet:api")) + implementation(project(":features:wallet:impl")) /** AndroidX libraries */ implementation(deps.androidx.core.ktx) diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index b224a7f81a..65d7a0c72c 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -21,6 +21,7 @@ import com.tangem.datasource.config.models.Config import com.tangem.datasource.connection.NetworkConnectionManager import com.tangem.domain.DomainLayer import com.tangem.domain.common.LogConfig +import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles import com.tangem.tap.common.IntentHandler import com.tangem.tap.common.analytics.AnalyticsFactory import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder @@ -133,6 +134,9 @@ class TapApplication : Application(), ImageLoaderFactory { @Inject lateinit var preferencesDataSource: PreferencesDataSource + @Inject + lateinit var walletFeatureToggles: WalletFeatureToggles + override fun onCreate() { super.onCreate() @@ -146,6 +150,7 @@ class TapApplication : Application(), ImageLoaderFactory { assetReader = assetReader, networkConnectionManager = networkConnectionManager, customTokenFeatureToggles = customTokenFeatureToggles, + walletFeatureToggles = walletFeatureToggles, ), ), ) diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt index 806a46dd1d..1d0af74a36 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt @@ -1,11 +1,7 @@ package com.tangem.tap.common.extensions import android.os.Bundle -import androidx.fragment.app.DialogFragment -import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentActivity -import androidx.fragment.app.FragmentManager -import androidx.fragment.app.FragmentTransaction +import androidx.fragment.app.* import com.tangem.feature.referral.ReferralFragment import com.tangem.feature.swap.presentation.SwapFragment import com.tangem.tap.common.redux.navigation.AppScreen @@ -37,6 +33,7 @@ import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.store import com.tangem.wallet.R import timber.log.Timber +import com.tangem.feature.wallet.presentation.WalletFragment as RedesignedWalletFragment import com.tangem.tap.features.customtoken.impl.presentation.AddCustomTokenFragment as RedesignedAddCustomTokenFragment fun FragmentActivity.openFragment( @@ -138,7 +135,16 @@ private fun fragmentFactory(screen: AppScreen): Fragment { AppScreen.OnboardingWallet -> OnboardingWalletFragment() AppScreen.OnboardingTwins -> TwinsCardsFragment() AppScreen.OnboardingOther -> OnboardingOtherCardsFragment() - AppScreen.Wallet -> WalletFragment() + AppScreen.Wallet -> { + val featureToggles = store.state.daggerGraphState.get( + getDependency = DaggerGraphState::walletFeatureToggles, + ) + if (featureToggles.isRedesignedScreenEnabled) { + RedesignedWalletFragment() + } else { + WalletFragment() + } + } AppScreen.Send -> SendFragment() AppScreen.Details -> DetailsFragment() AppScreen.DetailsSecurity -> SecurityModeFragment() diff --git a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt index 3ffa120662..5e823c02da 100644 --- a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt +++ b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt @@ -4,6 +4,7 @@ import com.tangem.datasource.asset.AssetReader import com.tangem.datasource.connection.NetworkConnectionManager import com.tangem.domain.card.ScanCardUseCase import com.tangem.features.tester.api.TesterRouter +import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles import com.tangem.tap.features.customtoken.api.featuretoggles.CustomTokenFeatureToggles import org.rekotlin.StateType @@ -13,6 +14,7 @@ data class DaggerGraphState( val networkConnectionManager: NetworkConnectionManager? = null, val customTokenFeatureToggles: CustomTokenFeatureToggles? = null, val scanCardUseCase: ScanCardUseCase? = null, + val walletFeatureToggles: WalletFeatureToggles? = null, ) : StateType { inline fun get(getDependency: DaggerGraphState.() -> T?): T { diff --git a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json index d2cc7402fd..174175e4e6 100644 --- a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json @@ -10,5 +10,9 @@ { "name": "NEW_CARD_SCANNING_ENABLED", "version": "4.7.0" + }, + { + "name": "REDESIGNED_WALLET_SCREEN_ENABLED", + "version": "undefined" } -] +] \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_tap_card_24.xml b/core/ui/src/main/res/drawable/ic_tap_card_24.xml similarity index 100% rename from app/src/main/res/drawable/ic_tap_card_24.xml rename to core/ui/src/main/res/drawable/ic_tap_card_24.xml diff --git a/features/wallet/api/src/main/kotlin/com/tangem/features/wallet/featuretoggles/WalletFeatureToggles.kt b/features/wallet/api/src/main/kotlin/com/tangem/features/wallet/featuretoggles/WalletFeatureToggles.kt new file mode 100644 index 0000000000..5af5379a9e --- /dev/null +++ b/features/wallet/api/src/main/kotlin/com/tangem/features/wallet/featuretoggles/WalletFeatureToggles.kt @@ -0,0 +1,12 @@ +package com.tangem.features.wallet.featuretoggles + +/** + * Wallet feature toggles + * +[REDACTED_AUTHOR] + */ +interface WalletFeatureToggles { + + /** Availability of redesigned screen */ + val isRedesignedScreenEnabled: Boolean +} \ No newline at end of file diff --git a/features/wallet/impl/build.gradle.kts b/features/wallet/impl/build.gradle.kts index 873d96fbe0..c0a25c65df 100644 --- a/features/wallet/impl/build.gradle.kts +++ b/features/wallet/impl/build.gradle.kts @@ -15,6 +15,7 @@ dependencies { implementation(deps.compose.constraintLayout) implementation(deps.compose.material) implementation(deps.compose.foundation) + implementation(deps.compose.material3) implementation(deps.compose.navigation) implementation(deps.compose.navigation.hilt) implementation(deps.compose.ui) @@ -32,5 +33,4 @@ dependencies { /** Feature Apis */ implementation(project(":features:wallet:api")) - } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/di/WalletFeatureTogglesModule.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/di/WalletFeatureTogglesModule.kt new file mode 100644 index 0000000000..ed23c5d4c3 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/di/WalletFeatureTogglesModule.kt @@ -0,0 +1,21 @@ +package com.tangem.feature.wallet.di + +import com.tangem.core.featuretoggle.manager.FeatureTogglesManager +import com.tangem.feature.wallet.featuretoggles.DefaultWalletFeatureToggles +import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles +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 WalletFeatureTogglesModule { + + @Provides + @Singleton + fun provideWalletFeatureToggles(featureTogglesManager: FeatureTogglesManager): WalletFeatureToggles { + return DefaultWalletFeatureToggles(featureTogglesManager = featureTogglesManager) + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/featuretoggles/DefaultWalletFeatureToggles.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/featuretoggles/DefaultWalletFeatureToggles.kt new file mode 100644 index 0000000000..71696ceba1 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/featuretoggles/DefaultWalletFeatureToggles.kt @@ -0,0 +1,19 @@ +package com.tangem.feature.wallet.featuretoggles + +import com.tangem.core.featuretoggle.manager.FeatureTogglesManager +import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles + +/** + * Default implementation of CustomToken feature toggles + * + * @property featureTogglesManager manager for getting information about the availability of feature toggles + * +[REDACTED_AUTHOR] + */ +internal class DefaultWalletFeatureToggles( + private val featureTogglesManager: FeatureTogglesManager, +) : WalletFeatureToggles { + + override val isRedesignedScreenEnabled: Boolean + get() = featureTogglesManager.isFeatureEnabled(name = "REDESIGNED_WALLET_SCREEN_ENABLED") +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/WalletFragment.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/WalletFragment.kt new file mode 100644 index 0000000000..3c6f1114ad --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/WalletFragment.kt @@ -0,0 +1,41 @@ +package com.tangem.feature.wallet.presentation + +import android.os.Bundle +import android.transition.TransitionInflater +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.compose.ui.platform.ComposeView +import androidx.core.view.WindowCompat +import androidx.fragment.app.Fragment +import com.tangem.core.ui.res.TangemTheme +import com.tangem.feature.wallet.impl.R +import dagger.hilt.android.AndroidEntryPoint + +/** + * Wallet fragment + * +[REDACTED_AUTHOR] + */ +@AndroidEntryPoint +class WalletFragment : Fragment() { + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + activity?.window?.let { WindowCompat.setDecorFitsSystemWindows(it, true) } + + with(TransitionInflater.from(requireContext())) { + enterTransition = inflateTransition(R.transition.slide_right) + exitTransition = inflateTransition(R.transition.fade) + } + + return ComposeView(inflater.context).apply { + setContent { + isTransitionGroup = true + + TangemTheme { + // TODO: [REDACTED_TASK_KEY] Call WalletScreen + } + } + } + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/state/TokenUIState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/state/TokenUIState.kt new file mode 100644 index 0000000000..ace321b30a --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/state/TokenUIState.kt @@ -0,0 +1,9 @@ +package com.tangem.feature.wallet.presentation.state + +internal enum class TokenUIState { + LOADED, LOADING +} + +internal enum class TokenOptionsUIState { + VISIBLE, HIDDEN, DRAG, UNREACHABLE +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/config/TokenV2Config.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/state/TokenV2Config.kt similarity index 80% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/config/TokenV2Config.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/state/TokenV2Config.kt index bfa34bae3f..dca7440c39 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/config/TokenV2Config.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/state/TokenV2Config.kt @@ -1,8 +1,6 @@ -package com.tangem.feature.wallet.presentation.ui.config +package com.tangem.feature.wallet.presentation.state import androidx.annotation.DrawableRes -import com.tangem.feature.wallet.presentation.ui.state.TokenOptionsUIState -import com.tangem.feature.wallet.presentation.ui.state.TokenUIState /** * Token config @@ -18,7 +16,7 @@ import com.tangem.feature.wallet.presentation.ui.state.TokenUIState * @property tokenOptionsUIState state for token options like 'unreachable', 'hidden' etc * @property hasPending pending tx in blockchain */ -data class TokenV2Config( +internal data class TokenV2Config( val name: String, val amount: String, val fiatAmount: String, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/state/WalletCardState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/state/WalletCardState.kt new file mode 100644 index 0000000000..0837a3ef7d --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/state/WalletCardState.kt @@ -0,0 +1,92 @@ +package com.tangem.feature.wallet.presentation.state + +import androidx.annotation.DrawableRes + +/** Wallet card state */ +internal sealed interface WalletCardState { + /** Id */ + val id: String + + /** Title */ + val title: String + + /** Additional wallet information */ + val additionalInfo: String + + /** Wallet image resource id */ + @get:DrawableRes + val imageResId: Int + + /** Lambda be invoked when card is clicked */ + val onClick: (() -> Unit)? + + /** + * Wallet card content state + * + * @property id wallet id + * @property title wallet name + * @property additionalInfo wallet additional info + * @property imageResId wallet image resource id + * @property onClick lambda be invoked when wallet card is clicked + * @property balance wallet balance + */ + data class Content( + override val id: String, + override val title: String, + override val additionalInfo: String, + override val imageResId: Int, + override val onClick: (() -> Unit)? = null, + val balance: String, + ) : WalletCardState + + /** + * Wallet card loading state + * + * @property id wallet id + * @property title wallet name + * @property additionalInfo wallet additional info + * @property imageResId wallet image resource id + * @property onClick lambda be invoked when wallet card is clicked + */ + data class Loading( + override val id: String, + override val title: String, + override val additionalInfo: String, + override val imageResId: Int, + override val onClick: (() -> Unit)?, + ) : WalletCardState + + /** + * Wallet card hidden content state + * + * @property id wallet id + * @property title wallet name + * @property additionalInfo wallet additional info + * @property imageResId wallet image resource id + * @property onClick lambda be invoked when wallet card is clicked + */ + data class HiddenContent( + override val id: String, + override val title: String, + override val additionalInfo: String, + override val imageResId: Int, + override val onClick: (() -> Unit)?, + ) : WalletCardState + + /** + * Wallet card error state + * + * @property id wallet id + * @property title wallet name + * @property additionalInfo wallet additional info + * @property imageResId wallet image resource id + * @property onClick lambda be invoked when wallet card is clicked + */ + data class Error( + override val id: String, + override val title: String, + override val additionalInfo: String, + override val imageResId: Int, + override val onClick: (() -> Unit)?, + ) : WalletCardState +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/state/WalletStateHolder.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/state/WalletStateHolder.kt new file mode 100644 index 0000000000..c69ab425ae --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/state/WalletStateHolder.kt @@ -0,0 +1,28 @@ +package com.tangem.feature.wallet.presentation.state + +/** + * Wallet state holder + * + * @property onBackClick lambda be invoked when back button is clicked + * @property headerConfig header config + * +[REDACTED_AUTHOR] + */ +internal data class WalletStateHolder( + val onBackClick: () -> Unit, + val headerConfig: HeaderConfig, +) { + + /** + * Header config + * + * @property wallets list of wallets states + * @property onScanCardClick lambda be invoked when scan card button is clicked + * @property onMoreClick lambda be invoked when more button is clicked + */ + data class HeaderConfig( + val wallets: List, + val onScanCardClick: () -> Unit, + val onMoreClick: () -> Unit, + ) +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/Tokens.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/Tokens.kt index 6d178bdab6..45b3eb843d 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/Tokens.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/Tokens.kt @@ -5,10 +5,9 @@ import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.Icon -import androidx.compose.material.Surface -import androidx.compose.material.Text +import androidx.compose.material3.Icon +import androidx.compose.material3.Surface +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -21,20 +20,16 @@ import androidx.constraintlayout.compose.ConstraintLayout import androidx.constraintlayout.compose.Dimension import coil.compose.SubcomposeAsyncImage import coil.request.ImageRequest -import com.tangem.core.ui.R import com.tangem.core.ui.components.* import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemTypography -import com.tangem.feature.wallet.presentation.ui.config.PriceChange -import com.tangem.feature.wallet.presentation.ui.config.PriceChangeType -import com.tangem.feature.wallet.presentation.ui.config.TokenV2Config -import com.tangem.feature.wallet.presentation.ui.state.TokenOptionsUIState -import com.tangem.feature.wallet.presentation.ui.state.TokenUIState +import com.tangem.feature.wallet.impl.R +import com.tangem.feature.wallet.presentation.state.* private const val DOTS = "•••" @Composable -fun TokenItemV2(config: TokenV2Config) { +internal fun TokenItemV2(config: TokenV2Config) { when (config.tokenUIState) { TokenUIState.LOADED -> LoadedTokenState(config) TokenUIState.LOADING -> LoadingTokenState() @@ -167,7 +162,6 @@ private fun LoadingTokenState() { } } -@OptIn(ExperimentalMaterialApi::class) @Composable private fun BaseSurface(onClick: (() -> Unit)? = null, content: @Composable () -> Unit) { Surface( @@ -224,12 +218,15 @@ private fun TokenFiatPercentageBlock(fiatAmount: String, priceChange: PriceChang ) { val iconChangeArrow: Int val changeTextColor: Color - if (priceChange.type == PriceChangeType.UP) { - iconChangeArrow = R.drawable.img_arrow_up_8 - changeTextColor = TangemTheme.colors.text.accent - } else { - iconChangeArrow = R.drawable.img_arrow_down_8 - changeTextColor = TangemTheme.colors.text.warning + when (priceChange.type) { + PriceChangeType.UP -> { + iconChangeArrow = R.drawable.img_arrow_up_8 + changeTextColor = TangemTheme.colors.text.accent + } + PriceChangeType.DOWN -> { + iconChangeArrow = R.drawable.img_arrow_down_8 + changeTextColor = TangemTheme.colors.text.warning + } } Image( modifier = Modifier.align(Alignment.CenterVertically), diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/WalletHeader.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/WalletHeader.kt deleted file mode 100644 index 13eb2f12f3..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/WalletHeader.kt +++ /dev/null @@ -1,193 +0,0 @@ -package com.tangem.feature.wallet.presentation.ui - -import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.* -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.Icon -import androidx.compose.material.Surface -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.sp -import androidx.constraintlayout.compose.ConstraintLayout -import androidx.constraintlayout.compose.Dimension -import com.tangem.core.ui.R -import com.tangem.core.ui.components.* -import com.tangem.core.ui.res.TangemTheme -import com.tangem.feature.wallet.presentation.ui.config.WalletHeaderConfig -import com.tangem.feature.wallet.presentation.ui.state.BalanceUIState - -private const val DOTS = "•••" - -@OptIn(ExperimentalMaterialApi::class) -@Composable -fun WalletHeaderCard(config: WalletHeaderConfig, modifier: Modifier = Modifier) { - Surface( - modifier = modifier.defaultMinSize(minHeight = TangemTheme.dimens.size108), - shape = TangemTheme.shapes.roundedCornersXMedium, - color = TangemTheme.colors.background.primary, - onClick = config.onClick ?: {}, - enabled = config.onClick != null, - ) { - ConstraintLayout( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = TangemTheme.dimens.spacing12), - ) { - val (balanceBlock, imageItem) = createRefs() - Column( - modifier = Modifier.constrainAs(balanceBlock) { - centerVerticallyTo(parent) - start.linkTo(parent.start) - end.linkTo(imageItem.start) - width = Dimension.fillToConstraints - }, - verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8), - ) { - HeaderWalletName( - walletName = config.walletName, - balanceState = config.balanceState, - ) - HeaderBalanceTitle( - balance = config.balance, - balanceState = config.balanceState, - ) - Text( - text = config.additionalInfo, - color = TangemTheme.colors.text.disabled, - style = TangemTheme.typography.caption, - ) - } - val imageWidth = TangemTheme.dimens.size120 - Image( - modifier = Modifier.constrainAs(imageItem) { - centerVerticallyTo(parent) - top.linkTo(parent.top) - end.linkTo(parent.end) - height = Dimension.fillToConstraints - width = Dimension.value(imageWidth) - }, - painter = config.cardImage, - contentDescription = null, - contentScale = ContentScale.FillWidth, - ) - } - } -} - -@Composable -private fun HeaderWalletName(walletName: String, balanceState: BalanceUIState) { - if (balanceState == BalanceUIState.HIDDEN) { - Row(horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4)) { - Text( - text = walletName, - color = TangemTheme.colors.text.tertiary, - style = TangemTheme.typography.body2, - maxLines = 1, - ) - Icon( - modifier = Modifier.size(size = TangemTheme.dimens.size20), - painter = painterResource(id = R.drawable.ic_eye_off_24), - contentDescription = null, - tint = TangemTheme.colors.icon.informative, - ) - } - } else { - Text( - text = walletName, - color = TangemTheme.colors.text.tertiary, - style = TangemTheme.typography.body2, - maxLines = 1, - ) - } -} - -@Composable -private fun HeaderBalanceTitle(balance: String, balanceState: BalanceUIState) { - when (balanceState) { - BalanceUIState.VISIBLE -> { - ResizableText( - text = balance, - color = TangemTheme.colors.text.primary1, - style = TangemTheme.typography.h2, - fontSizeRange = FontSizeRange(min = 16.sp, max = TangemTheme.typography.h2.fontSize), - modifier = Modifier.defaultMinSize(minHeight = TangemTheme.dimens.size32), - ) - } - BalanceUIState.LOADING -> { - ShimmerRectangle( - modifier = Modifier.size( - width = TangemTheme.dimens.size102, - height = TangemTheme.dimens.size24, - ), - ) - } - BalanceUIState.HIDDEN -> { - Text( - text = DOTS, - color = TangemTheme.colors.text.primary1, - style = TangemTheme.typography.h2, - ) - } - BalanceUIState.NONE -> { - Text( - text = "—", - color = TangemTheme.colors.text.primary1, - style = TangemTheme.typography.h2, - ) - } - } -} - -// region Preview - -@Composable -private fun WarningsPreview() { - val walletHeaderConfig = WalletHeaderConfig( - walletName = "Wallet 1", - balance = "8923,05 $", - additionalInfo = "3 cards • Seed enabled", - balanceState = BalanceUIState.VISIBLE, - cardImage = painterResource(id = R.drawable.ill_businessman_3d), - onClick = {}, - ) - Column(modifier = Modifier.fillMaxWidth()) { - WalletHeaderCard( - config = walletHeaderConfig, - ) - SpacerH32() - WalletHeaderCard( - config = walletHeaderConfig.copy(balanceState = BalanceUIState.HIDDEN), - ) - SpacerH32() - WalletHeaderCard( - config = walletHeaderConfig.copy(balanceState = BalanceUIState.LOADING), - ) - SpacerH32() - WalletHeaderCard( - config = walletHeaderConfig.copy(balanceState = BalanceUIState.NONE), - ) - SpacerH32() - } -} - -@Preview(showBackground = true) -@Composable -private fun Preview_Warning_InLightTheme() { - TangemTheme(isDark = false) { - WarningsPreview() - } -} - -@Preview(showBackground = true) -@Composable -private fun Preview_Warning_InDarkTheme() { - TangemTheme(isDark = true) { - WarningsPreview() - } -} - -// endregion Preview \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/WalletPreviewData.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/WalletPreviewData.kt new file mode 100644 index 0000000000..535b09f1b4 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/WalletPreviewData.kt @@ -0,0 +1,41 @@ +package com.tangem.feature.wallet.presentation.ui + +import com.tangem.core.ui.R +import com.tangem.feature.wallet.presentation.state.WalletCardState +import java.util.UUID + +internal object WalletPreviewData { + + val walletCardContent = WalletCardState.Content( + id = UUID.randomUUID().toString(), + title = "Wallet 1", + balance = "8923,05 $", + additionalInfo = "3 cards • Seed enabled", + imageResId = R.drawable.ill_businessman_3d, + onClick = {}, + ) + + val walletCardLoading = WalletCardState.Loading( + id = UUID.randomUUID().toString(), + title = "Wallet 1", + additionalInfo = "3 cards • Seed enabled", + imageResId = R.drawable.ill_businessman_3d, + onClick = {}, + ) + + val walletCardHiddenContent = WalletCardState.HiddenContent( + id = UUID.randomUUID().toString(), + title = "Wallet 1", + additionalInfo = "3 cards • Seed enabled", + imageResId = R.drawable.ill_businessman_3d, + onClick = {}, + ) + + val walletCardError = WalletCardState.Error( + id = UUID.randomUUID().toString(), + title = "Wallet 1", + additionalInfo = "3 cards • Seed enabled", + imageResId = R.drawable.ill_businessman_3d, + onClick = {}, + ) +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/WalletScreen.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/WalletScreen.kt new file mode 100644 index 0000000000..dc166a77a2 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/WalletScreen.kt @@ -0,0 +1,25 @@ +package com.tangem.feature.wallet.presentation.ui + +import androidx.activity.compose.BackHandler +import androidx.compose.material.Scaffold +import androidx.compose.runtime.Composable +import com.tangem.feature.wallet.presentation.state.WalletStateHolder +import com.tangem.feature.wallet.presentation.ui.components.WalletHeader + +/** + * Wallet screen + * + * @param state screen state + * +[REDACTED_AUTHOR] + */ +@Composable +internal fun WalletScreen(state: WalletStateHolder) { + BackHandler(onBack = state.onBackClick) + + Scaffold( + topBar = { WalletHeader(config = state.headerConfig) }, + ) { + // TODO: [REDACTED_TASK_KEY] Design a body with tokens and transactions + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/components/WalletCard.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/components/WalletCard.kt new file mode 100644 index 0000000000..25a427408a --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/components/WalletCard.kt @@ -0,0 +1,192 @@ +package com.tangem.feature.wallet.presentation.ui.components + +import androidx.annotation.DrawableRes +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.* +import androidx.compose.material3.Icon +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import androidx.compose.ui.unit.sp +import androidx.constraintlayout.compose.ConstraintLayout +import androidx.constraintlayout.compose.Dimension +import com.tangem.core.ui.components.FontSizeRange +import com.tangem.core.ui.components.ResizableText +import com.tangem.core.ui.components.ShimmerRectangle +import com.tangem.core.ui.res.TangemTheme +import com.tangem.feature.wallet.impl.R +import com.tangem.feature.wallet.presentation.state.WalletCardState +import com.tangem.feature.wallet.presentation.ui.WalletPreviewData + +private const val DOTS = "•••" + +/** + * Wallet card + * + * @param state state + * @param modifier modifier + */ +@Composable +internal fun WalletCard(state: WalletCardState, modifier: Modifier = Modifier) { + Surface( + modifier = modifier.defaultMinSize(minHeight = TangemTheme.dimens.size108), + shape = TangemTheme.shapes.roundedCornersXMedium, + color = TangemTheme.colors.background.primary, + onClick = state.onClick ?: {}, + enabled = state.onClick != null, + ) { + ConstraintLayout( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = TangemTheme.dimens.spacing12), + ) { + val (balanceBlock, imageItem) = createRefs() + Column( + modifier = Modifier.constrainAs(balanceBlock) { + centerVerticallyTo(parent) + start.linkTo(parent.start) + end.linkTo(imageItem.start) + width = Dimension.fillToConstraints + }, + verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8), + ) { + Title(state) + Balance(state) + AdditionalInfo(description = state.additionalInfo) + } + + val imageWidth = TangemTheme.dimens.size120 + WalletImage( + id = state.imageResId, + modifier = Modifier.constrainAs(imageItem) { + centerVerticallyTo(parent) + top.linkTo(parent.top) + end.linkTo(parent.end) + height = Dimension.fillToConstraints + width = Dimension.value(imageWidth) + }, + ) + } + } +} + +@Composable +private fun Title(state: WalletCardState) { + when (state) { + is WalletCardState.HiddenContent -> { + Row(horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4)) { + Text( + text = state.title, + color = TangemTheme.colors.text.tertiary, + style = TangemTheme.typography.body2, + maxLines = 1, + ) + Icon( + modifier = Modifier.size(size = TangemTheme.dimens.size20), + painter = painterResource(id = R.drawable.ic_eye_off_24), + contentDescription = null, + tint = TangemTheme.colors.icon.informative, + ) + } + } + else -> { + Text( + text = state.title, + color = TangemTheme.colors.text.tertiary, + style = TangemTheme.typography.body2, + maxLines = 1, + ) + } + } +} + +@Composable +private fun Balance(state: WalletCardState) { + when (state) { + is WalletCardState.Content -> { + ResizableText( + text = state.balance, + color = TangemTheme.colors.text.primary1, + style = TangemTheme.typography.h2, + fontSizeRange = FontSizeRange(min = 16.sp, max = TangemTheme.typography.h2.fontSize), + modifier = Modifier.defaultMinSize(minHeight = TangemTheme.dimens.size32), + ) + } + is WalletCardState.Loading -> { + ShimmerRectangle( + modifier = Modifier.size( + width = TangemTheme.dimens.size102, + height = TangemTheme.dimens.size24, + ), + ) + } + is WalletCardState.HiddenContent -> { + Text( + text = DOTS, + color = TangemTheme.colors.text.primary1, + style = TangemTheme.typography.h2, + ) + } + is WalletCardState.Error -> { + Text( + text = "—", + color = TangemTheme.colors.text.primary1, + style = TangemTheme.typography.h2, + ) + } + } +} + +@Composable +private fun AdditionalInfo(description: String) { + Text( + text = description, + color = TangemTheme.colors.text.disabled, + style = TangemTheme.typography.caption, + ) +} + +@Composable +private fun WalletImage(@DrawableRes id: Int, modifier: Modifier = Modifier) { + Image( + painter = painterResource(id), + contentDescription = null, + modifier = modifier, + contentScale = ContentScale.FillWidth, + ) +} + +// region Preview + +@Preview +@Composable +private fun Preview_WalletCard_LightTheme(@PreviewParameter(WalletCardStateProvider::class) state: WalletCardState) { + TangemTheme(isDark = false) { + WalletCard(state) + } +} + +@Preview +@Composable +private fun Preview_WalletCard_DarkTheme(@PreviewParameter(WalletCardStateProvider::class) state: WalletCardState) { + TangemTheme(isDark = true) { + WalletCard(state) + } +} + +private class WalletCardStateProvider : CollectionPreviewParameterProvider( + collection = listOf( + WalletPreviewData.walletCardContent, + WalletPreviewData.walletCardLoading, + WalletPreviewData.walletCardHiddenContent, + WalletPreviewData.walletCardError, + ), +) + +// endregion Preview \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/components/WalletHeader.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/components/WalletHeader.kt new file mode 100644 index 0000000000..0c2169bb16 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/components/WalletHeader.kt @@ -0,0 +1,107 @@ +package com.tangem.feature.wallet.presentation.ui.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +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.res.TangemTheme +import com.tangem.feature.wallet.impl.R +import com.tangem.feature.wallet.presentation.state.WalletCardState +import com.tangem.feature.wallet.presentation.state.WalletStateHolder +import com.tangem.feature.wallet.presentation.ui.WalletPreviewData + +/** + * Wallet screen header + * + * @param config config + * +[REDACTED_AUTHOR] + */ +@Composable +internal fun WalletHeader(config: WalletStateHolder.HeaderConfig) { + Column( + modifier = Modifier + .background(color = TangemTheme.colors.background.secondary) + .padding(bottom = TangemTheme.dimens.spacing14), + ) { + TopBar(onScanCardClick = config.onScanCardClick, onMoreClick = config.onMoreClick) + LazyRow( + contentPadding = PaddingValues(horizontal = TangemTheme.dimens.spacing16), + horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8), + ) { + items(items = config.wallets, key = WalletCardState::id) { state -> + WalletCard( + state = state, + modifier = Modifier.width(LocalConfiguration.current.screenWidthDp.dp - TangemTheme.dimens.size32), + ) + } + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun TopBar(onScanCardClick: () -> Unit, onMoreClick: () -> Unit) { + TopAppBar( + title = { + Icon(painter = painterResource(id = R.drawable.img_tangem_logo_90_24), contentDescription = null) + }, + actions = { + IconButton(onClick = onScanCardClick) { + Icon(painter = painterResource(id = R.drawable.ic_tap_card_24), contentDescription = "Scan card") + } + IconButton(onClick = onMoreClick) { + Icon(painter = painterResource(id = R.drawable.ic_more_vertical_24), contentDescription = "More") + } + }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = TangemTheme.colors.background.secondary, + titleContentColor = TangemTheme.colors.icon.primary1, + actionIconContentColor = TangemTheme.colors.icon.primary1, + ), + ) +} + +@Preview +@Composable +private fun Preview_WalletHeader_LightTheme( + @PreviewParameter(WalletHeaderProvider::class) state: WalletStateHolder.HeaderConfig, +) { + TangemTheme(isDark = false) { + WalletHeader(state) + } +} + +@Preview +@Composable +private fun Preview_WalletHeader_DarkTheme( + @PreviewParameter(WalletHeaderProvider::class) state: WalletStateHolder.HeaderConfig, +) { + TangemTheme(isDark = true) { + WalletHeader(state) + } +} + +private class WalletHeaderProvider : CollectionPreviewParameterProvider( + collection = listOf( + WalletStateHolder.HeaderConfig( + wallets = listOf( + WalletPreviewData.walletCardContent, + WalletPreviewData.walletCardLoading, + WalletPreviewData.walletCardHiddenContent, + WalletPreviewData.walletCardError, + ), + onScanCardClick = {}, + onMoreClick = {}, + ), + ), +) \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/config/WalletHeaderConfig.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/config/WalletHeaderConfig.kt deleted file mode 100644 index 4a621be4e5..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/config/WalletHeaderConfig.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.tangem.feature.wallet.presentation.ui.config - -import androidx.compose.ui.graphics.painter.Painter -import com.tangem.feature.wallet.presentation.ui.state.BalanceUIState - -data class WalletHeaderConfig( - val walletName: String, - val balance: String, - val additionalInfo: String, - val balanceState: BalanceUIState, - val cardImage: Painter, - val onClick: (() -> Unit)?, -) \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/state/BalanceUIState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/state/BalanceUIState.kt deleted file mode 100644 index c4a22f046c..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/state/BalanceUIState.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.tangem.feature.wallet.presentation.ui.state - -enum class BalanceUIState { - VISIBLE, LOADING, HIDDEN, NONE -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/state/TokenUIState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/state/TokenUIState.kt deleted file mode 100644 index 0ef9163e07..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/state/TokenUIState.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.tangem.feature.wallet.presentation.ui.state - -enum class TokenUIState { - LOADED, LOADING -} - -enum class TokenOptionsUIState { - VISIBLE, HIDDEN, DRAG, UNREACHABLE -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/res/drawable/img_tangem_logo_90_24.xml b/features/wallet/impl/src/main/res/drawable/img_tangem_logo_90_24.xml new file mode 100644 index 0000000000..2fa7574ea9 --- /dev/null +++ b/features/wallet/impl/src/main/res/drawable/img_tangem_logo_90_24.xml @@ -0,0 +1,28 @@ + + + + + + + + + diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 1ad8e1ccca..f7f6cb31df 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -25,6 +25,7 @@ compose-compiler = "1.4.3" compose-runtime = "1.3.3" compose-foundation = "1.3.1" compose-material = "1.4.2" +compose-material3 = "1.1.0" compose-constraint = "1.0.1" compose-navigation = "2.5.3" compose-accompanist = "0.28.0" @@ -132,6 +133,7 @@ compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = compose-animation = { module = "androidx.compose.animation:animation", version.ref = "compose-runtime" } compose-foundation = { module = "androidx.compose.foundation:foundation", version.ref = "compose-foundation" } compose-material = { module = "androidx.compose.material:material", version.ref = "compose-material" } +compose-material3 = { module = "androidx.compose.material3:material3", version.ref = "compose-material3" } compose-constraintLayout = { module = "androidx.constraintlayout:constraintlayout-compose", version.ref = "compose-constraint" } compose-shimmer = { module = "com.valentinilk.shimmer:compose-shimmer", version.ref = "compose-shimmer" } compose-coil = { module = "io.coil-kt:coil-compose", version.ref = "coil" } @@ -211,5 +213,5 @@ mviCore-watcher = { module = "com.github.badoo.mvicore:mvicore-diff", version.re kotlin-serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinSerialization" } arrow-core = { module = "io.arrow-kt:arrow-core", version.ref = "arrow" } arrow-fx = { module = "io.arrow-kt:arrow-fx-coroutines", version.ref = "arrow" } -reactive-network = { module = "com.github.pwittchen:reactivenetwork-rx2", version.ref = "reactiveNetwork" } +reactive-network = { module = "com.github.pwittchen:reactivenetwork-rx2", version.ref = "reactiveNetwork" } # endregion Other