Updated on 2026-08-14

This commit is contained in:
Tangem 2024-05-28 19:20:56 +04:00
parent f71fe3aa6b
commit cf7d5814d6
33 changed files with 476 additions and 2 deletions

View file

@ -77,6 +77,7 @@ dependencies {
implementation(projects.core.ui)
implementation(projects.core.datasource)
implementation(projects.core.utils)
implementation(projects.core.decompose)
implementation(projects.core.deepLinks)
implementation(projects.libs.crypto)
implementation(projects.libs.auth)
@ -123,6 +124,8 @@ dependencies {
implementation(projects.features.qrScanning.impl)
implementation(projects.features.staking.api)
implementation(projects.features.staking.impl)
implementation(projects.features.details.api)
implementation(projects.features.details.impl)
/** AndroidX libraries */
implementation(deps.androidx.core.ktx)

View file

@ -26,6 +26,8 @@ import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
import com.tangem.features.details.DetailsEntryPoint
import com.tangem.features.details.DetailsFeatureToggles
import com.tangem.features.managetokens.featuretoggles.ManageTokensFeatureToggles
import com.tangem.features.send.api.featuretoggles.SendFeatureToggles
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectSessionsRepository
@ -104,4 +106,8 @@ interface ApplicationEntryPoint {
fun getGetFeedbackEmailUseCase(): GetFeedbackEmailUseCase
fun getSaveBlockchainErrorUseCase(): SaveBlockchainErrorUseCase
fun getDetailsFeatureToggles(): DetailsFeatureToggles
fun getDetailsEntryPoint(): DetailsEntryPoint
}

View file

@ -27,6 +27,7 @@ import by.kirich1409.viewbindingdelegate.viewBinding
import com.google.android.material.snackbar.BaseTransientBottomBar
import com.google.android.material.snackbar.Snackbar
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.deeplink.DeepLinksRegistry
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
@ -155,6 +156,9 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
@Inject
lateinit var emailSender: EmailSender
@Inject
lateinit var rootComponentContext: AppComponentContext
internal val viewModel: MainViewModel by viewModels()
private lateinit var appThemeModeFlow: SharedFlow<AppThemeMode?>
@ -239,6 +243,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
sendRouter = sendRouter,
qrScanningRouter = qrScanningRouter,
emailSender = emailSender,
rootComponentContext = rootComponentContext,
),
)
}

View file

@ -43,6 +43,8 @@ import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
import com.tangem.features.details.DetailsEntryPoint
import com.tangem.features.details.DetailsFeatureToggles
import com.tangem.features.managetokens.featuretoggles.ManageTokensFeatureToggles
import com.tangem.features.send.api.featuretoggles.SendFeatureToggles
import com.tangem.tap.common.analytics.AnalyticsFactory
@ -181,6 +183,12 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
get() = entryPoint.getSaveBlockchainErrorUseCase()
// endregion
private val detailsFeatureToggles: DetailsFeatureToggles
get() = entryPoint.getDetailsFeatureToggles()
private val detailsEntryPoint: DetailsEntryPoint
get() = entryPoint.getDetailsEntryPoint()
override fun onCreate() {
super.onCreate()
@ -264,6 +272,8 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
settingsRepository = settingsRepository,
blockchainSDKFactory = blockchainSDKFactory,
saveBlockchainErrorUseCase = saveBlockchainErrorUseCase,
detailsFeatureToggles = detailsFeatureToggles,
detailsEntryPoint = detailsEntryPoint,
),
),
)

View file

@ -137,7 +137,17 @@ private fun fragmentFactory(screen: AppScreen): Fragment {
SendFragment()
}
}
AppScreen.Details -> DetailsFragment()
AppScreen.Details -> {
val featureToggles = store.inject(getDependency = DaggerGraphState::detailsFeatureToggles)
if (featureToggles.isRedesignEnabled) {
store.inject(DaggerGraphState::detailsEntryPoint).entryFragment(
parentContext = store.inject(DaggerGraphState::rootComponentContext),
)
} else {
DetailsFragment()
}
}
AppScreen.DetailsSecurity -> SecurityModeFragment()
AppScreen.CardSettings -> CardSettingsFragment()
AppScreen.AppSettings -> AppSettingsFragment()

View file

@ -0,0 +1,45 @@
package com.tangem.tap.di
import android.content.Context
import androidx.appcompat.app.AppCompatActivity
import com.arkivanov.decompose.defaultComponentContext
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.DefaultAppComponentContext
import com.tangem.core.decompose.di.DecomposeComponent
import com.tangem.core.decompose.ui.UiMessage
import com.tangem.core.decompose.ui.UiMessageHandler
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ActivityComponent
import dagger.hilt.android.qualifiers.ActivityContext
import dagger.hilt.android.scopes.ActivityScoped
import timber.log.Timber
@Module
@InstallIn(ActivityComponent::class)
internal object RootAppComponentContextModule {
@Provides
@ActivityScoped
fun provideRootAppComponentContext(
@ActivityContext context: Context,
dispatchers: CoroutineDispatcherProvider,
componentBuilder: DecomposeComponent.Builder,
): AppComponentContext {
// TODO: Implement message handler
val dummyMessageHandler = object : UiMessageHandler {
override fun handleMessage(message: UiMessage) {
Timber.w("Unable to handle message: $message")
}
}
return DefaultAppComponentContext(
componentContext = (context as AppCompatActivity).defaultComponentContext(),
messageHandler = dummyMessageHandler,
dispatchers = dispatchers,
hiltComponentBuilder = componentBuilder,
)
}
}

View file

@ -1,5 +1,6 @@
package com.tangem.tap.proxy.redux
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.navigation.email.EmailSender
import com.tangem.domain.card.ScanCardUseCase
import com.tangem.domain.card.repository.CardSdkConfigRepository
@ -25,5 +26,6 @@ sealed interface DaggerGraphAction : Action {
val sendRouter: SendRouter,
val qrScanningRouter: QrScanningRouter,
val emailSender: EmailSender,
val rootComponentContext: AppComponentContext,
) : DaggerGraphAction
}

View file

@ -23,6 +23,7 @@ object DaggerGraphReducer {
sendRouter = action.sendRouter,
qrScanningRouter = action.qrScanningRouter,
emailSender = action.emailSender,
rootComponentContext = action.rootComponentContext,
)
}
}

View file

@ -2,6 +2,7 @@ package com.tangem.tap.proxy.redux
import com.tangem.TangemSdkLogger
import com.tangem.blockchainsdk.BlockchainSDKFactory
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.navigation.email.EmailSender
import com.tangem.datasource.connection.NetworkConnectionManager
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
@ -23,6 +24,8 @@ import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
import com.tangem.feature.qrscanning.QrScanningRouter
import com.tangem.features.details.DetailsEntryPoint
import com.tangem.features.details.DetailsFeatureToggles
import com.tangem.features.managetokens.featuretoggles.ManageTokensFeatureToggles
import com.tangem.features.managetokens.navigation.ManageTokensUi
import com.tangem.features.send.api.featuretoggles.SendFeatureToggles
@ -38,6 +41,7 @@ import com.tangem.tap.proxy.AppStateHolder
import org.rekotlin.StateType
data class DaggerGraphState(
val rootComponentContext: AppComponentContext? = null,
val testerRouter: TesterRouter? = null,
val networkConnectionManager: NetworkConnectionManager? = null,
val customTokenFeatureToggles: CustomTokenFeatureToggles? = null,
@ -73,4 +77,6 @@ data class DaggerGraphState(
val blockchainSDKFactory: BlockchainSDKFactory? = null,
val emailSender: EmailSender? = null,
val saveBlockchainErrorUseCase: SaveBlockchainErrorUseCase? = null,
val detailsFeatureToggles: DetailsFeatureToggles? = null,
val detailsEntryPoint: DetailsEntryPoint? = null,
) : StateType

View file

@ -5,7 +5,7 @@ plugins {
}
dependencies {
implementation(projects.core.utils)
api(projects.core.utils)
api(deps.decompose)
implementation(deps.kotlin.coroutines)

View file

@ -38,5 +38,9 @@
{
"name": "FULL_RESET_ENABLED",
"version": "5.12.0"
},
{
"name": "DETAILS_REDESIGN_ENABLED",
"version": "undefined"
}
]

1
features/details/api/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/build

View 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)
}

View file

@ -0,0 +1,9 @@
package com.tangem.features.details
import androidx.fragment.app.Fragment
import com.tangem.core.decompose.context.AppComponentContext
interface DetailsEntryPoint {
fun entryFragment(parentContext: AppComponentContext): Fragment
}

View file

@ -0,0 +1,6 @@
package com.tangem.features.details
interface DetailsFeatureToggles {
val isRedesignEnabled: Boolean
}

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

@ -0,0 +1 @@
/build

View file

@ -0,0 +1,45 @@
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)
/* Project - Core */
implementation(projects.core.decompose)
implementation(projects.core.ui)
implementation(projects.core.featuretoggles)
/* Project - Domain */
implementation(projects.domain.wallets.models)
/* AndroidX */
implementation(deps.androidx.fragment.ktx)
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)
}

View file

@ -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")
}

View file

@ -0,0 +1,33 @@
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.ui.UiDependencies
import com.tangem.core.ui.screen.ComposeFragment
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
@Composable
override fun ScreenContent(modifier: Modifier) {
// TODO: Update to use the actual component in [REDACTED_TASK_KEY]
PreviewDetailsComponent().View(modifier = modifier)
}
companion object : DetailsEntryPoint {
override fun entryFragment(parentContext: AppComponentContext): Fragment {
// TODO: Update to use the actual component in [REDACTED_TASK_KEY]
return DetailsFragment()
}
}
}

View file

@ -0,0 +1,22 @@
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 DetailsComponent {
@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,
)
}

View file

@ -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
}
}

View file

@ -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,
)
}

View file

@ -0,0 +1,14 @@
package com.tangem.features.details.component.preview
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.tangem.features.details.component.DetailsComponent
internal class PreviewDetailsComponent : DetailsComponent {
@Composable
@Suppress("TopLevelComposableFunctions") // TODO: Remove this check
override fun View(modifier: Modifier) {
TODO("Will be implemented in [REDACTED_TASK_KEY]")
}
}

View file

@ -0,0 +1,14 @@
package com.tangem.features.details.component.preview
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.tangem.features.details.component.UserWalletListComponent
internal class PreviewUserWalletListComponent : UserWalletListComponent {
@Composable
@Suppress("TopLevelComposableFunctions") // TODO: Remove this check
override fun View(modifier: Modifier) {
TODO("Will be implemented in [REDACTED_TASK_KEY]")
}
}

View file

@ -0,0 +1,16 @@
package com.tangem.features.details.component.preview
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.tangem.features.details.component.WalletConnectComponent
internal class PreviewWalletConnectComponent : WalletConnectComponent {
override suspend fun checkIsAvailable(): Boolean = true
@Composable
@Suppress("TopLevelComposableFunctions") // TODO: Remove this check
override fun View(modifier: Modifier) {
TODO("Will be implemented in [REDACTED_TASK_KEY]")
}
}

View file

@ -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
}
}

View file

@ -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
}

View file

@ -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,
)
}

View file

@ -0,0 +1,40 @@
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 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)
}
}
}

View file

@ -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,
)

View file

@ -0,0 +1,22 @@
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 addNewWalletText: TextReference,
val onAddNewWalletClick: () -> Unit,
) {
data class UserWalletUM(
val id: UserWalletId,
val name: String,
val information: TextReference,
@DrawableRes
val imageResId: Int,
val onClick: () -> Unit,
)
}

View file

@ -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()

View file

@ -156,6 +156,9 @@ include(":features:qr-scanning:impl")
include(":features:staking:api")
include(":features:staking:impl")
include(":features:details:api")
include(":features:details:impl")
// endregion Feature modules
// region Domain modules