Updated on 2026-08-14
|
|
@ -232,6 +232,8 @@ dependencies {
|
|||
implementation(projects.features.welcome.impl)
|
||||
implementation(projects.features.createWalletSelection.api)
|
||||
implementation(projects.features.createWalletSelection.impl)
|
||||
implementation(projects.features.home.api)
|
||||
implementation(projects.features.home.impl)
|
||||
|
||||
/** AndroidX libraries */
|
||||
implementation(deps.androidx.core.ktx)
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ package com.tangem.tests
|
|||
import android.content.Intent.ACTION_VIEW
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.screens.onDisclaimerScreen
|
||||
import com.tangem.screens.onStoriesScreen
|
||||
import com.tangem.screens.DisclaimerTestScreen
|
||||
import com.tangem.screens.StoriesTestScreen
|
||||
import com.tangem.tap.features.home.redux.HomeMiddleware.NEW_BUY_WALLET_URL
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import io.github.kakaocup.kakao.intent.KIntent
|
||||
|
|
|
|||
|
|
@ -176,13 +176,22 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
|
|||
@Inject
|
||||
internal lateinit var testerMenuLauncher: TesterMenuLauncher
|
||||
|
||||
@Inject
|
||||
internal lateinit var intentProcessor: IntentProcessor
|
||||
|
||||
@Inject
|
||||
internal lateinit var walletConnectLinkIntentHandler: WalletConnectLinkIntentHandler
|
||||
|
||||
@Inject
|
||||
internal lateinit var onPushClickedIntentHandler: OnPushClickedIntentHandler
|
||||
|
||||
@Inject
|
||||
internal lateinit var backgroundScanIntentHandler: BackgroundScanIntentHandler
|
||||
|
||||
internal val viewModel: MainViewModel by viewModels()
|
||||
|
||||
private lateinit var appThemeModeFlow: SharedFlow<AppThemeMode>
|
||||
|
||||
// TODO: fixme: inject through DI
|
||||
private val intentProcessor: IntentProcessor = IntentProcessor()
|
||||
|
||||
private val dialogManager = DialogManager()
|
||||
|
||||
private val onActivityResultCallbacks = mutableListOf<OnActivityResultCallback>()
|
||||
|
|
@ -344,12 +353,10 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
|
|||
}
|
||||
|
||||
private fun initIntentHandlers() {
|
||||
val hasSavedWalletsProvider = { userWalletsListManager.hasUserWallets }
|
||||
intentProcessor.addHandler(OnPushClickedIntentHandler(analyticsEventsHandler))
|
||||
intentProcessor.addHandler(BackgroundScanIntentHandler(hasSavedWalletsProvider, lifecycleScope))
|
||||
intentProcessor.addHandler(onPushClickedIntentHandler)
|
||||
|
||||
if (!walletConnectFeatureToggles.isRedesignedWalletConnectEnabled) {
|
||||
intentProcessor.addHandler(WalletConnectLinkIntentHandler())
|
||||
intentProcessor.addHandler(walletConnectLinkIntentHandler)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -435,9 +442,15 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
|
|||
}
|
||||
|
||||
private fun navigateToInitialScreen(intentWhichStartedActivity: Intent?) {
|
||||
val launchMode = backgroundScanIntentHandler.getInitScreenLaunchMode(intentWhichStartedActivity)
|
||||
if (userWalletsListManager.isLockable && userWalletsListManager.hasUserWallets) {
|
||||
store.dispatchNavigationAction {
|
||||
replaceAll(AppRoute.Welcome(intentWhichStartedActivity?.let(::SerializableIntent)))
|
||||
replaceAll(
|
||||
AppRoute.Welcome(
|
||||
launchMode = launchMode,
|
||||
intent = intentWhichStartedActivity?.let(::SerializableIntent),
|
||||
),
|
||||
)
|
||||
}
|
||||
intentProcessor.handleIntent(
|
||||
intent = intentWhichStartedActivity,
|
||||
|
|
@ -452,7 +465,7 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
|
|||
val route = when {
|
||||
shouldShowTos -> AppRoute.Disclaimer(isTosAccepted = false)
|
||||
shouldShowInitialPush -> AppRoute.PushNotification
|
||||
else -> AppRoute.Home
|
||||
else -> AppRoute.Home(launchMode = launchMode)
|
||||
}
|
||||
|
||||
store.dispatchNavigationAction { replaceAll(route) }
|
||||
|
|
|
|||
|
|
@ -1,18 +0,0 @@
|
|||
package com.tangem.tap.common.analytics.events
|
||||
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed class IntroductionProcess(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : AnalyticsEvent("Introduction Process", event, params) {
|
||||
|
||||
class ScreenOpened : IntroductionProcess("Introduction Process Screen Opened")
|
||||
class ButtonTokensList : IntroductionProcess("Button - Tokens List")
|
||||
class ButtonBuyCards : IntroductionProcess("Button - Buy Cards")
|
||||
class ButtonScanCard : IntroductionProcess("Button - Scan Card")
|
||||
class ButtonRequestSupport : IntroductionProcess("Button - Request Support")
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
package com.tangem.tap.common.analytics.events
|
||||
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
import com.tangem.tap.common.extensions.filterNotNull
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed class Shop(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : AnalyticsEvent("Shop", event, params) {
|
||||
|
||||
class ScreenOpened : Shop("Shop Screen Opened")
|
||||
|
||||
class Purchased(sku: String, count: String, amount: String, couponCode: String?) : Shop(
|
||||
event = "Purchased",
|
||||
params = mapOf(
|
||||
"SKU" to sku,
|
||||
"Count" to count,
|
||||
"Amount" to amount,
|
||||
"Coupon Code" to couponCode,
|
||||
).filterNotNull(),
|
||||
)
|
||||
|
||||
class Redirected(partnerName: String?) : Shop(
|
||||
event = "Redirected",
|
||||
params = partnerName?.let { mapOf("Partner" to partnerName) } ?: mapOf(),
|
||||
)
|
||||
}
|
||||
|
|
@ -6,9 +6,9 @@ import com.tangem.domain.card.common.util.cardTypesResolver
|
|||
import com.tangem.domain.models.scan.ProductType
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
||||
import com.tangem.features.home.impl.analytics.IntroductionProcess
|
||||
import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.IntroductionProcess
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
|
|
|
|||
|
|
@ -1,17 +0,0 @@
|
|||
package com.tangem.tap.common.compose.extensions
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
fun Dp.toPx(): Float {
|
||||
val currentDp = this
|
||||
return with(LocalDensity.current) { currentDp.toPx() }
|
||||
}
|
||||
|
||||
fun DpSize.halfHeight(): Dp = this.height / 2
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
package com.tangem.tap.common.compose.extensions
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.sdk.extensions.pxToDp
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
fun Painter.dpSize(): DpSize = DpSize(
|
||||
intrinsicSize.width.pxToDp().dp,
|
||||
intrinsicSize.height.pxToDp().dp,
|
||||
)
|
||||
|
||||
@Composable
|
||||
private fun Float.pxToDp(): Float = LocalContext.current.pxToDp(this)
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
package com.tangem.tap.common.extensions
|
||||
|
||||
fun Int.isEven() = this and 1 == 0
|
||||
|
|
@ -3,7 +3,6 @@ package com.tangem.tap.common.redux
|
|||
import com.tangem.tap.common.redux.global.globalReducer
|
||||
import com.tangem.tap.features.details.redux.DetailsReducer
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectReducer
|
||||
import com.tangem.tap.features.home.redux.HomeReducer
|
||||
import com.tangem.tap.features.welcome.redux.WelcomeReducer
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphReducer
|
||||
import org.rekotlin.Action
|
||||
|
|
@ -14,7 +13,6 @@ fun appReducer(action: Action, state: AppState?): AppState {
|
|||
|
||||
return AppState(
|
||||
globalState = globalReducer(action, state),
|
||||
homeState = HomeReducer.reduce(action, state),
|
||||
detailsState = DetailsReducer.reduce(action, state),
|
||||
walletConnectState = WalletConnectReducer.reduce(action, state.walletConnectState),
|
||||
welcomeState = WelcomeReducer.reduce(action, state),
|
||||
|
|
|
|||
|
|
@ -7,8 +7,6 @@ import com.tangem.tap.features.details.redux.DetailsMiddleware
|
|||
import com.tangem.tap.features.details.redux.DetailsState
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectMiddleware
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectState
|
||||
import com.tangem.tap.features.home.redux.HomeMiddleware
|
||||
import com.tangem.tap.features.home.redux.HomeState
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupMiddleware
|
||||
import com.tangem.tap.features.wallet.redux.middlewares.TradeCryptoMiddleware
|
||||
import com.tangem.tap.features.welcome.redux.WelcomeMiddleware
|
||||
|
|
@ -20,7 +18,6 @@ import org.rekotlin.StateType
|
|||
|
||||
data class AppState(
|
||||
val globalState: GlobalState = GlobalState(),
|
||||
val homeState: HomeState = HomeState(),
|
||||
val detailsState: DetailsState = DetailsState(),
|
||||
val walletConnectState: WalletConnectState = WalletConnectState(),
|
||||
val welcomeState: WelcomeState = WelcomeState(),
|
||||
|
|
@ -32,7 +29,6 @@ data class AppState(
|
|||
return listOf(
|
||||
logMiddleware,
|
||||
GlobalMiddleware.handler,
|
||||
HomeMiddleware.handler,
|
||||
DetailsMiddleware().detailsMiddleware,
|
||||
WalletConnectMiddleware().walletConnectMiddleware,
|
||||
BackupMiddleware().backupMiddleware,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import android.content.Context
|
|||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.compose.ui.text.intl.Locale
|
||||
import androidx.core.view.isVisible
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
|
|
@ -14,8 +15,6 @@ import com.tangem.tap.common.analytics.events.ScanFailsDialogAnalytics
|
|||
import com.tangem.tap.common.extensions.dispatchDialogHide
|
||||
import com.tangem.tap.common.extensions.dispatchOpenUrl
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.features.home.LocaleRegionProvider
|
||||
import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
|
|
@ -29,6 +28,7 @@ internal object ScanFailsDialog {
|
|||
|
||||
private const val HOW_TO_SCAN_RU_LINK = "https://tangem.com/ru/blog/post/scan-tangem-card/"
|
||||
private const val HOW_TO_SCAN_LINK = "https://tangem.com/en/blog/post/scan-tangem-card/"
|
||||
private const val RUSSIA_LOCALE = "ru"
|
||||
|
||||
fun create(context: Context, source: StateDialog.ScanFailsSource, onTryAgain: (() -> Unit)? = null): AlertDialog {
|
||||
return AlertDialog.Builder(context, R.style.CustomMaterialDialog).apply {
|
||||
|
|
@ -62,8 +62,8 @@ internal object ScanFailsDialog {
|
|||
source = sourceAnalytics,
|
||||
),
|
||||
)
|
||||
val locale = LocaleRegionProvider().getRegion()
|
||||
val link = if (locale.lowercase() == RUSSIA_COUNTRY_CODE) HOW_TO_SCAN_RU_LINK else HOW_TO_SCAN_LINK
|
||||
val locale = Locale.current.region
|
||||
val link = if (locale.lowercase() == RUSSIA_LOCALE) HOW_TO_SCAN_RU_LINK else HOW_TO_SCAN_LINK
|
||||
store.dispatchOpenUrl(link)
|
||||
}
|
||||
customView.findViewById<TextView>(R.id.request_support_button)?.setOnClickListener {
|
||||
|
|
|
|||
34
app/src/main/java/com/tangem/tap/di/IntentHandlingModule.kt
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
package com.tangem.tap.di
|
||||
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.tap.features.intentHandler.IntentProcessor
|
||||
import com.tangem.tap.features.intentHandler.handlers.BackgroundScanIntentHandler
|
||||
import com.tangem.tap.features.intentHandler.handlers.OnPushClickedIntentHandler
|
||||
import com.tangem.tap.features.intentHandler.handlers.WalletConnectLinkIntentHandler
|
||||
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 IntentHandlingModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideBackgroundScanIntentHandler(): BackgroundScanIntentHandler = BackgroundScanIntentHandler()
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideWalletConnectLinkIntentHandler(): WalletConnectLinkIntentHandler = WalletConnectLinkIntentHandler()
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideOnPushClickedIntentHandler(analyticsEventHandler: AnalyticsEventHandler): OnPushClickedIntentHandler =
|
||||
OnPushClickedIntentHandler(analyticsEventHandler)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideIntentProcessor(): IntentProcessor = IntentProcessor()
|
||||
}
|
||||
|
|
@ -233,7 +233,7 @@ class DetailsMiddleware {
|
|||
deleteSavedAccessCodes()
|
||||
store.inject(DaggerGraphState::walletsRepository).saveShouldSaveUserWallets(item = false)
|
||||
|
||||
store.dispatchNavigationAction { replaceAll(AppRoute.Home) }
|
||||
store.dispatchNavigationAction { replaceAll(AppRoute.Home()) }
|
||||
|
||||
return CompletionResult.Success(Unit)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -268,7 +268,7 @@ internal class ResetCardModel @Inject constructor(
|
|||
if (isLocked && userWalletsListManager.hasUserWallets) {
|
||||
store.dispatchNavigationAction { popTo<AppRoute.Welcome>() }
|
||||
} else {
|
||||
store.dispatchNavigationAction { replaceAll(AppRoute.Home) }
|
||||
store.dispatchNavigationAction { replaceAll(AppRoute.Home()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,90 +0,0 @@
|
|||
package com.tangem.tap.features.home
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import com.arkivanov.essenty.lifecycle.subscribe
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.ui.components.SystemBarsIconsDisposable
|
||||
import com.tangem.core.ui.utils.ChangeRootBackgroundColorEffect
|
||||
import com.tangem.core.ui.utils.findActivity
|
||||
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.features.home.api.HomeComponent
|
||||
import com.tangem.tap.features.home.compose.StoriesScreen
|
||||
import com.tangem.tap.features.home.compose.StoriesScreenV2
|
||||
import com.tangem.tap.features.home.redux.HomeAction
|
||||
import com.tangem.tap.features.home.redux.HomeState
|
||||
import com.tangem.tap.store
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import org.rekotlin.StoreSubscriber
|
||||
|
||||
@Suppress("UnusedPrivateMember")
|
||||
internal class DefaultHomeComponent @AssistedInject constructor(
|
||||
@Assisted appComponentContext: AppComponentContext,
|
||||
@Assisted params: Unit,
|
||||
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
) : HomeComponent, AppComponentContext by appComponentContext, StoreSubscriber<HomeState> {
|
||||
|
||||
private val model: HomeModel = getOrCreateModel()
|
||||
|
||||
private var homeState: MutableState<HomeState> = mutableStateOf(store.state.homeState)
|
||||
|
||||
init {
|
||||
lifecycle.subscribe(
|
||||
onCreate = {
|
||||
store.dispatch(HomeAction.OnCreate)
|
||||
},
|
||||
onStart = {
|
||||
store.subscribe(subscriber = this) { state ->
|
||||
state
|
||||
.skipRepeats { oldState, newState -> oldState.homeState == newState.homeState }
|
||||
.select(AppState::homeState)
|
||||
}
|
||||
},
|
||||
onStop = {
|
||||
store.unsubscribe(this)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val activity = LocalContext.current.findActivity()
|
||||
BackHandler(onBack = activity::finish)
|
||||
SystemBarsIconsDisposable(darkIcons = false)
|
||||
if (hotWalletFeatureToggles.isHotWalletEnabled) {
|
||||
StoriesScreenV2(
|
||||
homeState = homeState,
|
||||
onCreateNewWalletButtonClick = model::onCreateNewWalletScreen,
|
||||
onAddExistingWalletButtonClick = model::onAddExistingWalletScreen,
|
||||
onScanButtonClick = model::onScanClick,
|
||||
)
|
||||
} else {
|
||||
StoriesScreen(
|
||||
homeState = homeState,
|
||||
onScanButtonClick = model::onScanClick,
|
||||
onShopButtonClick = model::onShopClick,
|
||||
onSearchTokensClick = model::onSearchClick,
|
||||
)
|
||||
}
|
||||
|
||||
ChangeRootBackgroundColorEffect(Color(color = 0xFF010101))
|
||||
}
|
||||
|
||||
override fun newState(state: HomeState) {
|
||||
homeState.value = state
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : HomeComponent.Factory {
|
||||
override fun create(context: AppComponentContext, params: Unit): DefaultHomeComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -1,166 +0,0 @@
|
|||
package com.tangem.tap.features.home
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.google.firebase.analytics.ktx.analytics
|
||||
import com.google.firebase.ktx.Firebase
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.AppRoute.ManageTokens.Source
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.Basic
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.core.navigation.url.UrlOpener
|
||||
import com.tangem.domain.card.ScanCardProcessor
|
||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
|
||||
import com.tangem.domain.settings.usercountry.models.UserCountry
|
||||
import com.tangem.domain.tokens.TokensAction
|
||||
import com.tangem.domain.wallets.builder.ColdUserWalletBuilder
|
||||
import com.tangem.domain.wallets.usecase.SaveWalletUseCase
|
||||
import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter
|
||||
import com.tangem.tap.common.analytics.events.IntroductionProcess
|
||||
import com.tangem.tap.common.analytics.events.Shop
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.dispatchWithMain
|
||||
import com.tangem.tap.common.extensions.onUserWalletSelected
|
||||
import com.tangem.tap.features.home.redux.HIDE_PROGRESS_DELAY
|
||||
import com.tangem.tap.features.home.redux.HomeAction
|
||||
import com.tangem.tap.features.home.redux.HomeMiddleware.NEW_BUY_WALLET_URL
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Stable
|
||||
@ModelScoped
|
||||
internal class HomeModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val scanCardProcessor: ScanCardProcessor,
|
||||
private val saveWalletUseCase: SaveWalletUseCase,
|
||||
private val cardSdkConfigRepository: CardSdkConfigRepository,
|
||||
private val settingsRepository: SettingsRepository,
|
||||
private val urlOpener: UrlOpener,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val coldUserWalletBuilderFactory: ColdUserWalletBuilder.Factory,
|
||||
private val router: Router,
|
||||
getUserCountryUseCase: GetUserCountryUseCase,
|
||||
) : Model() {
|
||||
|
||||
private val tangemErrorHandler = TangemTangemErrorsHandler(store)
|
||||
|
||||
init {
|
||||
getUserCountryUseCase.invoke()
|
||||
.distinctUntilChanged()
|
||||
.filterNotNull()
|
||||
.onEach {
|
||||
val userCountry = it.getOrNull() ?: UserCountry.Other(Locale.getDefault().country)
|
||||
store.dispatchOnMain(HomeAction.UserCountryLoaded(userCountry))
|
||||
}
|
||||
.flowOn(dispatchers.io)
|
||||
.launchIn(modelScope)
|
||||
}
|
||||
|
||||
fun onCreateNewWalletScreen() {
|
||||
router.push(AppRoute.CreateWalletSelection)
|
||||
}
|
||||
|
||||
fun onAddExistingWalletScreen() {
|
||||
router.push(AppRoute.AddExistingWallet)
|
||||
}
|
||||
|
||||
fun onScanClick() {
|
||||
analyticsEventHandler.send(IntroductionProcess.ButtonScanCard())
|
||||
scanCard()
|
||||
}
|
||||
|
||||
fun onShopClick() {
|
||||
analyticsEventHandler.send(IntroductionProcess.ButtonBuyCards())
|
||||
analyticsEventHandler.send(Shop.ScreenOpened())
|
||||
|
||||
Firebase.analytics.appInstanceId
|
||||
.addOnSuccessListener { urlOpener.openUrl(url = "$NEW_BUY_WALLET_URL&app_instance_id=$it") }
|
||||
.addOnFailureListener { urlOpener.openUrl(url = NEW_BUY_WALLET_URL) }
|
||||
}
|
||||
|
||||
fun onSearchClick() {
|
||||
analyticsEventHandler.send(IntroductionProcess.ButtonTokensList())
|
||||
|
||||
store.dispatch(TokensAction.SetArgs.ReadAccess)
|
||||
store.dispatchNavigationAction { push(AppRoute.ManageTokens(Source.STORIES)) }
|
||||
}
|
||||
|
||||
private fun scanCard() {
|
||||
modelScope.launch {
|
||||
cardSdkConfigRepository.isBiometricsRequestPolicy = settingsRepository.shouldSaveAccessCodes()
|
||||
|
||||
scanCardProcessor.scan(
|
||||
analyticsSource = AnalyticsParam.ScreensSources.Intro,
|
||||
onProgressStateChange = { showProgress ->
|
||||
if (showProgress) {
|
||||
store.dispatch(HomeAction.ScanInProgress(scanInProgress = true))
|
||||
} else {
|
||||
delay(HIDE_PROGRESS_DELAY)
|
||||
store.dispatch(HomeAction.ScanInProgress(scanInProgress = false))
|
||||
}
|
||||
},
|
||||
onFailure = {
|
||||
tangemErrorHandler.onErrorReceived(error = it)
|
||||
delay(HIDE_PROGRESS_DELAY)
|
||||
store.dispatch(HomeAction.ScanInProgress(scanInProgress = false))
|
||||
},
|
||||
onSuccess = ::proceedWithScanResponse,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun proceedWithScanResponse(scanResponse: ScanResponse) {
|
||||
val userWallet = coldUserWalletBuilderFactory.create(scanResponse = scanResponse).build()
|
||||
|
||||
if (userWallet == null) {
|
||||
Timber.e("User wallet not created")
|
||||
return
|
||||
}
|
||||
|
||||
saveWalletUseCase(userWallet).fold(
|
||||
ifLeft = { Timber.e(it.toString(), "Unable to save user wallet") },
|
||||
ifRight = {
|
||||
sendSignedInCardAnalyticsEvent(scanResponse)
|
||||
coroutineScope { store.onUserWalletSelected(userWallet = userWallet) }
|
||||
},
|
||||
)
|
||||
|
||||
store.dispatchWithMain(HomeAction.ScanInProgress(scanInProgress = false))
|
||||
delay(HIDE_PROGRESS_DELAY)
|
||||
|
||||
store.dispatchNavigationAction { replaceAll(AppRoute.Wallet) }
|
||||
}
|
||||
|
||||
private fun sendSignedInCardAnalyticsEvent(scanResponse: ScanResponse) {
|
||||
val currency = ParamCardCurrencyConverter().convert(value = scanResponse.cardTypesResolver)
|
||||
|
||||
if (currency != null) {
|
||||
Analytics.send(
|
||||
event = Basic.SignedIn(
|
||||
currency = currency,
|
||||
batch = scanResponse.card.batchId,
|
||||
signInType = Basic.SignedIn.SignInType.Card,
|
||||
walletsCount = "1",
|
||||
hasBackup = scanResponse.card.backupStatus?.isActive,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
package com.tangem.tap.features.home
|
||||
|
||||
import androidx.compose.ui.text.intl.Locale
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface RegionProvider {
|
||||
fun getRegion(): String?
|
||||
}
|
||||
|
||||
class LocaleRegionProvider : RegionProvider {
|
||||
override fun getRegion(): String = Locale.current.region
|
||||
}
|
||||
|
||||
const val RUSSIA_COUNTRY_CODE = "ru"
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
package com.tangem.tap.features.home
|
||||
|
||||
import com.tangem.blockchain.common.BlockchainError
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.domain.redux.StateDialog
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.home.errors.TangemSdkErrorHandler
|
||||
import org.rekotlin.Store
|
||||
import timber.log.Timber
|
||||
|
||||
class TangemTangemErrorsHandler(val store: Store<AppState>) : TangemSdkErrorHandler {
|
||||
|
||||
override fun onErrorReceived(error: TangemError) {
|
||||
when (error) {
|
||||
is TangemSdkError -> {
|
||||
handleCardSdkError(error)
|
||||
}
|
||||
is BlockchainError -> {
|
||||
handleBlockchainSdkError(error)
|
||||
}
|
||||
else -> {
|
||||
Timber.e("Error happened", error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleCardSdkError(error: TangemSdkError) {
|
||||
when (error) {
|
||||
is TangemSdkError.NfcFeatureIsUnavailable -> {
|
||||
store.dispatchOnMain(GlobalAction.ShowDialog(StateDialog.NfcFeatureIsUnavailable))
|
||||
}
|
||||
else -> {
|
||||
Timber.e(error, "Unable to scan card")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleBlockchainSdkError(error: TangemError) {
|
||||
Timber.e("Sdk error happened", error)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
package com.tangem.tap.features.home.api
|
||||
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
|
||||
interface HomeComponent : ComposableContentComponent {
|
||||
|
||||
interface Factory : ComponentFactory<Unit, HomeComponent>
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
package com.tangem.tap.features.home.di
|
||||
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.tap.features.home.DefaultHomeComponent
|
||||
import com.tangem.tap.features.home.HomeModel
|
||||
import com.tangem.tap.features.home.api.HomeComponent
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import dagger.multibindings.ClassKey
|
||||
import dagger.multibindings.IntoMap
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal interface HomeFeatureModule {
|
||||
|
||||
@Binds
|
||||
fun bindFactory(impl: DefaultHomeComponent.Factory): HomeComponent.Factory
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(HomeModel::class)
|
||||
fun bindModel(model: HomeModel): Model
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
package com.tangem.tap.features.home.errors
|
||||
|
||||
import com.tangem.common.core.TangemError
|
||||
|
||||
interface TangemSdkErrorHandler {
|
||||
|
||||
fun onErrorReceived(error: TangemError)
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
package com.tangem.tap.features.home.redux
|
||||
|
||||
import com.tangem.domain.settings.usercountry.models.UserCountry
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import org.rekotlin.Action
|
||||
|
||||
sealed class HomeAction : Action {
|
||||
|
||||
data object OnCreate : HomeAction()
|
||||
|
||||
/**
|
||||
* Action for scanning card
|
||||
*
|
||||
* @property scope lifecycle scope. It will be canceled when lifecycle-aware component is destroyed
|
||||
*/
|
||||
data class ReadCard(val scope: CoroutineScope) : HomeAction()
|
||||
|
||||
data class ScanInProgress(val scanInProgress: Boolean) : HomeAction()
|
||||
|
||||
data class UserCountryLoaded(val userCountry: UserCountry) : HomeAction()
|
||||
}
|
||||
|
|
@ -1,147 +0,0 @@
|
|||
package com.tangem.tap.features.home.redux
|
||||
|
||||
import android.content.res.Resources
|
||||
import com.tangem.common.doOnFailure
|
||||
import com.tangem.common.doOnResult
|
||||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.Basic
|
||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter
|
||||
import com.tangem.tap.common.analytics.events.IntroductionProcess
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
import com.tangem.tap.common.extensions.eraseContext
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.common.extensions.onUserWalletSelected
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Action
|
||||
import org.rekotlin.Middleware
|
||||
import timber.log.Timber
|
||||
import java.util.Locale
|
||||
|
||||
internal const val HIDE_PROGRESS_DELAY = 400L
|
||||
|
||||
object HomeMiddleware {
|
||||
val handler = homeMiddleware
|
||||
|
||||
private val SYSTEM_LANGUAGE =
|
||||
runCatching { Resources.getSystem().configuration.locales[0].language }.getOrElse { "" }
|
||||
private val APP_LANGUAGE = Locale.getDefault().language
|
||||
private val UTM_MARKS = "utm_source=tangem-app" +
|
||||
"&utm_medium=app" +
|
||||
"&utm_campaign=prospect-$SYSTEM_LANGUAGE" +
|
||||
"&utm_content=devicelang-$APP_LANGUAGE"
|
||||
|
||||
val NEW_BUY_WALLET_URL = "https://buy.tangem.com/?$UTM_MARKS"
|
||||
}
|
||||
|
||||
private val homeMiddleware: Middleware<AppState> = { _, _ ->
|
||||
{ next ->
|
||||
{ action ->
|
||||
handleHomeAction(action)
|
||||
next(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleHomeAction(action: Action) {
|
||||
when (action) {
|
||||
is HomeAction.OnCreate -> {
|
||||
Analytics.eraseContext()
|
||||
Analytics.send(IntroductionProcess.ScreenOpened())
|
||||
|
||||
store.dispatch(GlobalAction.RestoreAppCurrency)
|
||||
}
|
||||
is HomeAction.ReadCard -> {
|
||||
action.scope.launch {
|
||||
readCard()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun readCard() {
|
||||
val shouldSaveAccessCodes = store.inject(DaggerGraphState::settingsRepository).shouldSaveAccessCodes()
|
||||
|
||||
store.inject(DaggerGraphState::cardSdkConfigRepository).setAccessCodeRequestPolicy(
|
||||
isBiometricsRequestPolicy = shouldSaveAccessCodes,
|
||||
)
|
||||
|
||||
store.inject(DaggerGraphState::scanCardProcessor).scan(
|
||||
analyticsSource = AnalyticsParam.ScreensSources.Intro,
|
||||
onProgressStateChange = { showProgress ->
|
||||
if (showProgress) {
|
||||
store.dispatch(HomeAction.ScanInProgress(scanInProgress = true))
|
||||
} else {
|
||||
delay(HIDE_PROGRESS_DELAY)
|
||||
store.dispatch(HomeAction.ScanInProgress(scanInProgress = false))
|
||||
}
|
||||
},
|
||||
onFailure = {
|
||||
Timber.e(it, "Unable to scan card")
|
||||
delay(HIDE_PROGRESS_DELAY)
|
||||
store.dispatch(HomeAction.ScanInProgress(scanInProgress = false))
|
||||
},
|
||||
onSuccess = { scanResponse ->
|
||||
proceedWithScanResponse(scanResponse)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun proceedWithScanResponse(scanResponse: ScanResponse) = scope.launch {
|
||||
val userWalletBuilder = store.inject(DaggerGraphState::coldUserWalletBuilderFactory).create(scanResponse)
|
||||
|
||||
val userWallet = userWalletBuilder.build().guard {
|
||||
Timber.e("User wallet not created")
|
||||
return@launch
|
||||
}
|
||||
|
||||
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
|
||||
userWalletsListManager.save(userWallet)
|
||||
.doOnFailure { error ->
|
||||
Timber.e(error, "Unable to save user wallet")
|
||||
}
|
||||
.doOnSuccess {
|
||||
sendSignedInCardAnalyticsEvent(scanResponse)
|
||||
store.onUserWalletSelected(userWallet = userWallet)
|
||||
}
|
||||
.doOnResult {
|
||||
navigateTo(AppRoute.Wallet)
|
||||
}
|
||||
}
|
||||
|
||||
private fun sendSignedInCardAnalyticsEvent(scanResponse: ScanResponse) {
|
||||
val currency = ParamCardCurrencyConverter().convert(
|
||||
value = scanResponse.cardTypesResolver,
|
||||
)
|
||||
|
||||
if (currency != null) {
|
||||
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
|
||||
|
||||
Analytics.send(
|
||||
event = Basic.SignedIn(
|
||||
currency = currency,
|
||||
batch = scanResponse.card.batchId,
|
||||
signInType = Basic.SignedIn.SignInType.Card,
|
||||
walletsCount = userWalletsListManager.walletsCount.toString(),
|
||||
hasBackup = scanResponse.card.backupStatus?.isActive,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun navigateTo(route: AppRoute) {
|
||||
store.dispatchNavigationAction { push(route) }
|
||||
delay(HIDE_PROGRESS_DELAY)
|
||||
store.dispatch(HomeAction.ScanInProgress(scanInProgress = false))
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
package com.tangem.tap.features.home.redux
|
||||
|
||||
import com.tangem.domain.settings.usercountry.models.needApplyFCARestrictions
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import org.rekotlin.Action
|
||||
|
||||
object HomeReducer {
|
||||
fun reduce(action: Action, state: AppState): HomeState = internalReduce(action, state)
|
||||
}
|
||||
|
||||
private fun internalReduce(action: Action, appState: AppState): HomeState {
|
||||
if (action !is HomeAction) return appState.homeState
|
||||
|
||||
return when (action) {
|
||||
is HomeAction.ScanInProgress -> {
|
||||
appState.homeState.copy(scanInProgress = action.scanInProgress)
|
||||
}
|
||||
is HomeAction.UserCountryLoaded -> {
|
||||
val stories = if (action.userCountry.needApplyFCARestrictions()) {
|
||||
getRestrictedStories()
|
||||
} else {
|
||||
Stories.entries
|
||||
}
|
||||
appState.homeState.copy(
|
||||
stories = stories.toImmutableList(),
|
||||
)
|
||||
}
|
||||
else -> appState.homeState
|
||||
}
|
||||
}
|
||||
|
|
@ -4,21 +4,12 @@ import android.content.Intent
|
|||
import android.nfc.NfcAdapter
|
||||
import android.nfc.Tag
|
||||
import android.os.Build
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.features.home.redux.HomeAction
|
||||
import com.tangem.tap.features.intentHandler.IntentHandler
|
||||
import com.tangem.tap.features.intentHandler.AffectsNavigation
|
||||
import com.tangem.tap.features.welcome.redux.WelcomeAction
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import com.tangem.common.routing.entity.InitScreenLaunchMode
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class BackgroundScanIntentHandler(
|
||||
private val hasSavedUserWalletsProvider: () -> Boolean,
|
||||
private val scope: CoroutineScope,
|
||||
) : IntentHandler, AffectsNavigation {
|
||||
class BackgroundScanIntentHandler {
|
||||
|
||||
private val nfcActions = arrayOf(
|
||||
NfcAdapter.ACTION_NDEF_DISCOVERED,
|
||||
|
|
@ -26,8 +17,15 @@ class BackgroundScanIntentHandler(
|
|||
NfcAdapter.ACTION_TAG_DISCOVERED,
|
||||
)
|
||||
|
||||
override fun handleIntent(intent: Intent?, isFromForeground: Boolean): Boolean {
|
||||
if (isFromForeground) return true
|
||||
fun getInitScreenLaunchMode(intent: Intent?): InitScreenLaunchMode {
|
||||
return if (shouldOpenScanCard(intent)) {
|
||||
InitScreenLaunchMode.WithCardScan
|
||||
} else {
|
||||
InitScreenLaunchMode.Standard
|
||||
}
|
||||
}
|
||||
|
||||
private fun shouldOpenScanCard(intent: Intent?): Boolean {
|
||||
if (intent == null || intent.action !in nfcActions) return false
|
||||
|
||||
val tag: Tag? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
|
|
@ -36,15 +34,9 @@ class BackgroundScanIntentHandler(
|
|||
@Suppress("DEPRECATION")
|
||||
intent.getParcelableExtra(NfcAdapter.EXTRA_TAG)
|
||||
}
|
||||
if (tag == null) return false
|
||||
|
||||
intent.action = null
|
||||
if (hasSavedUserWalletsProvider.invoke()) {
|
||||
store.dispatchOnMain(WelcomeAction.ProceedWithCard)
|
||||
} else {
|
||||
store.dispatchOnMain(HomeAction.ReadCard(scope = scope))
|
||||
}
|
||||
|
||||
return true
|
||||
return tag != null
|
||||
}
|
||||
}
|
||||
|
|
@ -4,8 +4,8 @@ import android.content.Intent
|
|||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.removePrefixOrNull
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
|
||||
import com.tangem.tap.features.intentHandler.IntentHandler
|
||||
import com.tangem.tap.features.intentHandler.AffectsNavigation
|
||||
import com.tangem.tap.features.intentHandler.IntentHandler
|
||||
import com.tangem.tap.store
|
||||
import timber.log.Timber
|
||||
import java.net.URLDecoder
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.tap.features.welcome.component
|
||||
|
||||
import com.tangem.common.routing.entity.InitScreenLaunchMode
|
||||
import com.tangem.common.routing.entity.SerializableIntent
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
|
|
@ -7,6 +8,7 @@ import com.tangem.core.ui.decompose.ComposableContentComponent
|
|||
interface WelcomeComponent : ComposableContentComponent {
|
||||
|
||||
data class Params(
|
||||
val launchMode: InitScreenLaunchMode,
|
||||
val intent: SerializableIntent?,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.tap.features.welcome.model
|
||||
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.common.routing.entity.InitScreenLaunchMode
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
|
|
@ -44,10 +45,9 @@ internal class WelcomeModel @Inject constructor(
|
|||
subscribeToStoreChanges()
|
||||
initGlobalState()
|
||||
|
||||
val welcomeAction = if (params.intent != null) {
|
||||
WelcomeAction.ProceedWithIntent(params.intent.toIntent())
|
||||
} else {
|
||||
WelcomeAction.ProceedWithBiometrics()
|
||||
val welcomeAction = when (params.launchMode) {
|
||||
is InitScreenLaunchMode.WithCardScan -> WelcomeAction.ProceedWithCard
|
||||
is InitScreenLaunchMode.Standard -> WelcomeAction.ProceedWithBiometrics(params.intent?.toIntent())
|
||||
}
|
||||
|
||||
store.dispatch(welcomeAction)
|
||||
|
|
|
|||
|
|
@ -20,10 +20,8 @@ import com.tangem.tap.*
|
|||
import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.features.intentHandler.handlers.BackgroundScanIntentHandler
|
||||
import com.tangem.tap.features.intentHandler.handlers.WalletConnectLinkIntentHandler
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Middleware
|
||||
import timber.log.Timber
|
||||
|
|
@ -44,7 +42,7 @@ internal class WelcomeMiddleware {
|
|||
private fun handleAction(action: WelcomeAction, state: WelcomeState) {
|
||||
mainScope.launch {
|
||||
when (action) {
|
||||
is WelcomeAction.ProceedWithIntent -> proceedWithIntent(action.intent, scope = this)
|
||||
is WelcomeAction.ProceedWithIntent -> proceedWithIntent(action.intent)
|
||||
is WelcomeAction.ProceedWithBiometrics -> proceedWithBiometrics(
|
||||
afterUnlockIntent = action.afterUnlockIntent ?: state.intent,
|
||||
)
|
||||
|
|
@ -55,7 +53,7 @@ internal class WelcomeMiddleware {
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun proceedWithIntent(initialIntent: Intent, scope: CoroutineScope) {
|
||||
private suspend fun proceedWithIntent(initialIntent: Intent) {
|
||||
Timber.d(
|
||||
"""
|
||||
Proceeding with intent
|
||||
|
|
@ -63,15 +61,12 @@ internal class WelcomeMiddleware {
|
|||
""".trimIndent(),
|
||||
)
|
||||
|
||||
val handler = BackgroundScanIntentHandler(
|
||||
scope = scope,
|
||||
hasSavedUserWalletsProvider = { true },
|
||||
)
|
||||
val isBackgroundScanHandled = handler.handleIntent(initialIntent, isFromForeground = false)
|
||||
val hasUncompletedBackup = backupService.hasIncompletedBackup
|
||||
|
||||
if (!isBackgroundScanHandled && !hasUncompletedBackup) {
|
||||
if (!hasUncompletedBackup) {
|
||||
store.dispatchWithMain(WelcomeAction.ProceedWithBiometrics(initialIntent))
|
||||
} else {
|
||||
store.dispatchWithMain(WelcomeAction.ProceedWithCard)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ import com.tangem.tap.features.details.ui.cardsettings.coderecovery.api.AccessCo
|
|||
import com.tangem.tap.features.details.ui.resetcard.api.ResetCardComponent
|
||||
import com.tangem.tap.features.details.ui.securitymode.api.SecurityModeComponent
|
||||
import com.tangem.tap.features.details.ui.walletconnect.api.WalletConnectComponent
|
||||
import com.tangem.tap.features.home.api.HomeComponent
|
||||
import com.tangem.features.home.api.HomeComponent
|
||||
import com.tangem.tap.features.welcome.component.WelcomeComponent
|
||||
import com.tangem.tap.routing.component.RoutingComponent.Child
|
||||
import dagger.hilt.android.scopes.ActivityScoped
|
||||
|
|
@ -131,6 +131,7 @@ internal class ChildFactory @Inject constructor(
|
|||
createComponentChild(
|
||||
context = context,
|
||||
params = WelcomeComponent.Params(
|
||||
launchMode = route.launchMode,
|
||||
intent = route.intent,
|
||||
),
|
||||
componentFactory = welcomeComponentFactory,
|
||||
|
|
@ -288,7 +289,7 @@ internal class ChildFactory @Inject constructor(
|
|||
is AppRoute.Home -> {
|
||||
createComponentChild(
|
||||
context = context,
|
||||
params = Unit,
|
||||
params = HomeComponent.Params(route.launchMode),
|
||||
componentFactory = homeComponentFactory,
|
||||
)
|
||||
}
|
||||
|
|
@ -380,7 +381,7 @@ internal class ChildFactory @Inject constructor(
|
|||
is AppRoute.PushNotification -> {
|
||||
createComponentChild(
|
||||
context = context,
|
||||
params = PushNotificationsComponent.Params.Route(AppRoute.Home),
|
||||
params = PushNotificationsComponent.Params.Route(AppRoute.Home()),
|
||||
componentFactory = pushNotificationsComponentFactory,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.tangem.common.routing
|
|||
import android.os.Bundle
|
||||
import com.tangem.common.routing.bundle.RouteBundleParams
|
||||
import com.tangem.common.routing.bundle.bundle
|
||||
import com.tangem.common.routing.entity.InitScreenLaunchMode
|
||||
import com.tangem.common.routing.entity.SerializableIntent
|
||||
import com.tangem.core.decompose.navigation.Route
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
|
|
@ -22,10 +23,14 @@ sealed class AppRoute(val path: String) : Route {
|
|||
data object Initial : AppRoute(path = "/initial")
|
||||
|
||||
@Serializable
|
||||
data object Home : AppRoute(path = "/home")
|
||||
data class Home(
|
||||
val launchMode: InitScreenLaunchMode = InitScreenLaunchMode.Standard,
|
||||
) : AppRoute(path = "/home")
|
||||
|
||||
@Serializable
|
||||
data class Welcome(
|
||||
val launchMode: InitScreenLaunchMode = InitScreenLaunchMode.Standard,
|
||||
// we still have this param to be handled by WalletConnectLinkIntentHandler in WelcomeMiddleware
|
||||
val intent: SerializableIntent? = null,
|
||||
) : AppRoute(path = "/welcome"), RouteBundleParams {
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.common.routing.entity
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
sealed class InitScreenLaunchMode {
|
||||
|
||||
@Serializable
|
||||
data object Standard : InitScreenLaunchMode()
|
||||
|
||||
@Serializable
|
||||
data object WithCardScan : InitScreenLaunchMode()
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@ data class SerializableIntent(
|
|||
val packageValue: String?,
|
||||
val component: String?,
|
||||
val flags: Int,
|
||||
// CAUTION: works wrong with SerializableBundle constructor(bundle: Bundle), need to be removed
|
||||
val extras: SerializableBundle?,
|
||||
) {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.tap.common.compose.extensions
|
||||
package com.tangem.core.ui.utils
|
||||
|
||||
import androidx.compose.animation.core.Animatable
|
||||
import androidx.compose.animation.core.AnimationVector1D
|
||||
|
|
@ -1,9 +1,15 @@
|
|||
package com.tangem.core.ui.utils
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@Stable
|
||||
@Composable
|
||||
|
|
@ -15,4 +21,16 @@ fun convertPxToDp(px: Float): Dp = convertPxToDp(px, density = LocalDensity.curr
|
|||
|
||||
fun Dp.toPx(density: Float): Float = this.value * density
|
||||
|
||||
fun convertPxToDp(px: Float, density: Float): Dp = Dp(value = px / density)
|
||||
fun convertPxToDp(px: Float, density: Float): Dp = Dp(value = px / density)
|
||||
|
||||
fun Context.dpToPx(dp: Float): Float = dp * resources.displayMetrics.density
|
||||
fun Context.pxToDp(px: Float): Float = (px / resources.displayMetrics.density).roundToInt().toFloat()
|
||||
|
||||
@Composable
|
||||
fun Painter.dpSize(): DpSize = DpSize(
|
||||
intrinsicSize.width.pxToDp().dp,
|
||||
intrinsicSize.height.pxToDp().dp,
|
||||
)
|
||||
|
||||
@Composable
|
||||
private fun Float.pxToDp(): Float = LocalContext.current.pxToDp(this)
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.tap.common.compose.extensions
|
||||
package com.tangem.core.ui.utils
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
|
Before Width: | Height: | Size: 140 KiB After Width: | Height: | Size: 140 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 127 KiB After Width: | Height: | Size: 127 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 234 KiB After Width: | Height: | Size: 234 KiB |
|
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 222 KiB After Width: | Height: | Size: 222 KiB |
|
Before Width: | Height: | Size: 509 KiB After Width: | Height: | Size: 509 KiB |
|
Before Width: | Height: | Size: 156 KiB After Width: | Height: | Size: 156 KiB |
|
Before Width: | Height: | Size: 484 KiB After Width: | Height: | Size: 484 KiB |
|
Before Width: | Height: | Size: 901 KiB After Width: | Height: | Size: 901 KiB |
|
Before Width: | Height: | Size: 243 KiB After Width: | Height: | Size: 243 KiB |
|
Before Width: | Height: | Size: 818 KiB After Width: | Height: | Size: 818 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 137 KiB After Width: | Height: | Size: 137 KiB |
|
Before Width: | Height: | Size: 140 KiB After Width: | Height: | Size: 140 KiB |
|
Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 128 KiB |
|
Before Width: | Height: | Size: 138 KiB After Width: | Height: | Size: 138 KiB |
|
|
@ -1,6 +0,0 @@
|
|||
package com.tangem.utils.extensions
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
fun Int.isEven(): Boolean = this % 2 == 0
|
||||
|
|
@ -51,7 +51,7 @@ internal class DisclaimerModel @Inject constructor(
|
|||
} else {
|
||||
neverToInitiallyAskPermissionUseCase(PUSH_PERMISSION)
|
||||
neverRequestPermissionUseCase(PUSH_PERMISSION)
|
||||
router.replaceAll(AppRoute.Home)
|
||||
router.replaceAll(AppRoute.Home())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
1
features/home/api/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
17
features/home/api/build.gradle.kts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
plugins {
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.tangem.features.home.api"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.core.decompose)
|
||||
implementation(projects.core.ui)
|
||||
|
||||
/** Common */
|
||||
implementation(projects.common.routing)
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.features.home.api
|
||||
|
||||
import com.tangem.common.routing.entity.InitScreenLaunchMode
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
|
||||
interface HomeComponent : ComposableContentComponent {
|
||||
|
||||
data class Params(
|
||||
val launchMode: InitScreenLaunchMode = InitScreenLaunchMode.Standard,
|
||||
)
|
||||
|
||||
interface Factory : ComponentFactory<Params, HomeComponent> {
|
||||
override fun create(context: AppComponentContext, params: Params): HomeComponent
|
||||
}
|
||||
}
|
||||
1
features/home/impl/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
69
features/home/impl/build.gradle.kts
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
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.home.impl"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
/** Api */
|
||||
implementation(projects.features.home.api)
|
||||
implementation(projects.features.hotWallet.api)
|
||||
|
||||
/** Core modules */
|
||||
implementation(projects.core.decompose)
|
||||
implementation(projects.core.ui)
|
||||
implementation(projects.core.res)
|
||||
implementation(projects.core.analytics)
|
||||
implementation(projects.core.analytics.models)
|
||||
implementation(projects.core.navigation)
|
||||
|
||||
/** Common */
|
||||
implementation(projects.common.routing)
|
||||
|
||||
/** Domain */
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.core)
|
||||
implementation(projects.domain.card)
|
||||
implementation(projects.domain.settings)
|
||||
implementation(projects.domain.tokens)
|
||||
implementation(projects.domain.wallets)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.legacy)
|
||||
implementation(projects.domain.feedback)
|
||||
implementation(projects.domain.feedback.models)
|
||||
|
||||
/** AndroidX libraries */
|
||||
implementation(deps.androidx.activity.compose)
|
||||
implementation(deps.lifecycle.runtime.ktx)
|
||||
|
||||
/** Compose libraries */
|
||||
implementation(deps.compose.ui)
|
||||
implementation(deps.compose.ui.tooling)
|
||||
implementation(deps.compose.foundation)
|
||||
implementation(deps.compose.material3)
|
||||
implementation(deps.compose.animation)
|
||||
implementation(deps.compose.coil)
|
||||
implementation(deps.decompose.ext.compose)
|
||||
|
||||
/** Firebase */
|
||||
implementation(deps.firebase.analytics)
|
||||
|
||||
/** Tangem libraries */
|
||||
implementation(tangemDeps.card.android)
|
||||
implementation(tangemDeps.card.core)
|
||||
implementation(tangemDeps.blockchain)
|
||||
|
||||
/** Other libraries */
|
||||
implementation(deps.kotlin.immutable.collections)
|
||||
implementation(deps.timber)
|
||||
|
||||
/** DI */
|
||||
implementation(deps.hilt.android)
|
||||
kapt(deps.hilt.kapt)
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
package com.tangem.features.home.impl
|
||||
|
||||
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.home.api.HomeComponent
|
||||
import com.tangem.features.home.impl.model.HomeModel
|
||||
import com.tangem.features.home.impl.ui.Home
|
||||
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
internal class DefaultHomeComponent @AssistedInject constructor(
|
||||
@Assisted appComponentContext: AppComponentContext,
|
||||
@Assisted params: HomeComponent.Params,
|
||||
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
) : HomeComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
private val model: HomeModel = getOrCreateModel(params)
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val state by model.uiState.collectAsStateWithLifecycle()
|
||||
|
||||
Home(
|
||||
state = state,
|
||||
modifier = modifier,
|
||||
isV2StoriesEnabled = hotWalletFeatureToggles.isHotWalletEnabled,
|
||||
)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : HomeComponent.Factory {
|
||||
override fun create(context: AppComponentContext, params: HomeComponent.Params): DefaultHomeComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.features.home.impl.analytics
|
||||
|
||||
internal sealed class AnalyticsParam {
|
||||
|
||||
sealed class CurrencyType(val value: String) {
|
||||
class Blockchain(blockchain: com.tangem.blockchain.common.Blockchain) : CurrencyType(blockchain.currency)
|
||||
class Token(token: com.tangem.blockchain.common.Token) : CurrencyType(token.symbol)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.tangem.features.home.impl.analytics
|
||||
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
|
||||
sealed class IntroductionProcess(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : AnalyticsEvent("Introduction Process", event, params) {
|
||||
|
||||
object ScreenOpened : IntroductionProcess("Introduction Process Screen Opened")
|
||||
object ButtonTokensList : IntroductionProcess("Button - Tokens List")
|
||||
object ButtonBuyCards : IntroductionProcess("Button - Buy Cards")
|
||||
object ButtonScanCard : IntroductionProcess("Button - Scan Card")
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.features.home.impl.analytics
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.domain.card.CardTypesResolver
|
||||
import com.tangem.utils.converter.Converter
|
||||
import com.tangem.core.analytics.models.AnalyticsParam as CoreAnalyticsParam
|
||||
|
||||
internal class ParamCardCurrencyConverter : Converter<CardTypesResolver, CoreAnalyticsParam.WalletType?> {
|
||||
|
||||
override fun convert(value: CardTypesResolver): CoreAnalyticsParam.WalletType? {
|
||||
if (value.isMultiwalletAllowed()) return CoreAnalyticsParam.WalletType.MultiCurrency
|
||||
|
||||
val type = when {
|
||||
value.isTangemNote() -> AnalyticsParam.CurrencyType.Blockchain(value.getBlockchain())
|
||||
value.isTangemTwins() -> AnalyticsParam.CurrencyType.Blockchain(Blockchain.Bitcoin)
|
||||
value.getBlockchain() != Blockchain.Unknown -> AnalyticsParam.CurrencyType.Blockchain(value.getBlockchain())
|
||||
value.getPrimaryToken() != null -> AnalyticsParam.CurrencyType.Token(value.getPrimaryToken()!!)
|
||||
else -> null
|
||||
} ?: return null
|
||||
|
||||
return CoreAnalyticsParam.WalletType.SingleCurrency(type.value)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.features.home.impl.analytics
|
||||
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
|
||||
internal sealed class Shop(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : AnalyticsEvent("Shop", event, params) {
|
||||
|
||||
object ScreenOpened : Shop("Shop Screen Opened")
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package com.tangem.features.home.impl.di
|
||||
|
||||
import com.tangem.core.decompose.di.ModelComponent
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.features.home.api.HomeComponent
|
||||
import com.tangem.features.home.impl.DefaultHomeComponent
|
||||
import com.tangem.features.home.impl.model.HomeModel
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import dagger.multibindings.ClassKey
|
||||
import dagger.multibindings.IntoMap
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal interface ComponentModule {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindComponent(factory: DefaultHomeComponent.Factory): HomeComponent.Factory
|
||||
}
|
||||
|
||||
@Module
|
||||
@InstallIn(ModelComponent::class)
|
||||
internal interface ModelModule {
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(HomeModel::class)
|
||||
fun provideModel(model: HomeModel): Model
|
||||
}
|
||||
|
|
@ -0,0 +1,268 @@
|
|||
package com.tangem.features.home.impl.model
|
||||
|
||||
import com.google.firebase.analytics.ktx.analytics
|
||||
import com.google.firebase.ktx.Firebase
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.AppRoute.ManageTokens.Source
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.common.routing.entity.InitScreenLaunchMode
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.Basic.SignedIn
|
||||
import com.tangem.core.analytics.models.Basic.SignedIn.SignInType
|
||||
import com.tangem.core.decompose.di.GlobalUiMessageSender
|
||||
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.decompose.ui.UiMessageSender
|
||||
import com.tangem.core.navigation.url.UrlOpener
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.message.DialogMessage
|
||||
import com.tangem.domain.card.ScanCardProcessor
|
||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
|
||||
import com.tangem.domain.settings.usercountry.models.UserCountry
|
||||
import com.tangem.domain.settings.usercountry.models.needApplyFCARestrictions
|
||||
import com.tangem.domain.wallets.builder.ColdUserWalletBuilder
|
||||
import com.tangem.domain.wallets.usecase.SaveWalletUseCase
|
||||
import com.tangem.domain.wallets.usecase.SelectWalletUseCase
|
||||
import com.tangem.features.home.api.HomeComponent
|
||||
import com.tangem.features.home.impl.analytics.IntroductionProcess
|
||||
import com.tangem.features.home.impl.analytics.ParamCardCurrencyConverter
|
||||
import com.tangem.features.home.impl.analytics.Shop
|
||||
import com.tangem.features.home.impl.ui.state.HomeUM
|
||||
import com.tangem.features.home.impl.ui.state.Stories
|
||||
import com.tangem.features.home.impl.ui.state.getRestrictedStories
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val HIDE_PROGRESS_DELAY = 400L
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@ModelScoped
|
||||
internal class HomeModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val scanCardProcessor: ScanCardProcessor,
|
||||
private val saveWalletUseCase: SaveWalletUseCase,
|
||||
private val cardSdkConfigRepository: CardSdkConfigRepository,
|
||||
private val settingsRepository: SettingsRepository,
|
||||
private val urlOpener: UrlOpener,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val coldUserWalletBuilderFactory: ColdUserWalletBuilder.Factory,
|
||||
private val router: Router,
|
||||
private val selectWalletUseCase: SelectWalletUseCase,
|
||||
private val appRouter: AppRouter,
|
||||
private val getUserCountryUseCase: GetUserCountryUseCase,
|
||||
@GlobalUiMessageSender private val uiMessageSender: UiMessageSender,
|
||||
) : Model() {
|
||||
|
||||
val params = paramsContainer.require<HomeComponent.Params>()
|
||||
|
||||
private val _uiState = MutableStateFlow(
|
||||
HomeUM(
|
||||
scanInProgress = false,
|
||||
stories = getRestrictedStories().toImmutableList(),
|
||||
onScanClick = ::onScanClick,
|
||||
onShopClick = ::onShopClick,
|
||||
onSearchTokensClick = ::onSearchTokensClick,
|
||||
onCreateNewWalletClick = ::onCreateNewWalletClick,
|
||||
onAddExistingWalletClick = ::onAddExistingWalletClick,
|
||||
),
|
||||
)
|
||||
|
||||
val uiState = _uiState.asStateFlow()
|
||||
|
||||
init {
|
||||
analyticsEventHandler.send(IntroductionProcess.ScreenOpened)
|
||||
observeUserCountryChanges()
|
||||
|
||||
when (params.launchMode) {
|
||||
InitScreenLaunchMode.Standard -> Unit
|
||||
InitScreenLaunchMode.WithCardScan -> scanCard()
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeUserCountryChanges() {
|
||||
getUserCountryUseCase.invoke()
|
||||
.distinctUntilChanged()
|
||||
.filterNotNull()
|
||||
.onEach { result ->
|
||||
val userCountry = result.getOrNull() ?: UserCountry.Other(Locale.getDefault().country)
|
||||
updateStoriesForCountry(userCountry)
|
||||
}
|
||||
.flowOn(dispatchers.io)
|
||||
.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private fun updateStoriesForCountry(userCountry: UserCountry) {
|
||||
val stories = if (userCountry.needApplyFCARestrictions()) {
|
||||
getRestrictedStories()
|
||||
} else {
|
||||
Stories.entries
|
||||
}
|
||||
|
||||
_uiState.update {
|
||||
it.copy(stories = stories.toImmutableList())
|
||||
}
|
||||
}
|
||||
|
||||
private fun onScanClick() {
|
||||
analyticsEventHandler.send(IntroductionProcess.ButtonScanCard)
|
||||
scanCard()
|
||||
}
|
||||
|
||||
private fun onShopClick() {
|
||||
analyticsEventHandler.send(IntroductionProcess.ButtonBuyCards)
|
||||
analyticsEventHandler.send(Shop.ScreenOpened)
|
||||
|
||||
Firebase.analytics.appInstanceId
|
||||
.addOnSuccessListener { urlOpener.openUrl(url = "$NEW_BUY_WALLET_URL&app_instance_id=$it") }
|
||||
.addOnFailureListener { urlOpener.openUrl(url = NEW_BUY_WALLET_URL) }
|
||||
}
|
||||
|
||||
private fun onSearchTokensClick() {
|
||||
analyticsEventHandler.send(IntroductionProcess.ButtonTokensList)
|
||||
router.push(AppRoute.ManageTokens(Source.STORIES))
|
||||
}
|
||||
|
||||
private fun onCreateNewWalletClick() {
|
||||
router.push(AppRoute.CreateWalletSelection)
|
||||
}
|
||||
|
||||
private fun onAddExistingWalletClick() {
|
||||
router.push(AppRoute.AddExistingWallet)
|
||||
}
|
||||
|
||||
private fun scanCard() {
|
||||
modelScope.launch {
|
||||
setLoading(true)
|
||||
|
||||
val shouldSaveAccessCodes = settingsRepository.shouldSaveAccessCodes()
|
||||
cardSdkConfigRepository.setAccessCodeRequestPolicy(
|
||||
isBiometricsRequestPolicy = shouldSaveAccessCodes,
|
||||
)
|
||||
|
||||
val analyticsSource = AnalyticsParam.ScreensSources.Intro
|
||||
|
||||
scanCardProcessor.scan(
|
||||
analyticsSource = analyticsSource,
|
||||
onProgressStateChange = { showProgress ->
|
||||
if (!showProgress) {
|
||||
delay(HIDE_PROGRESS_DELAY)
|
||||
setLoading(false)
|
||||
} else {
|
||||
setLoading(true)
|
||||
}
|
||||
},
|
||||
onFailure = { error ->
|
||||
handleScanError(error)
|
||||
delay(HIDE_PROGRESS_DELAY)
|
||||
setLoading(false)
|
||||
},
|
||||
onSuccess = { scanResponse ->
|
||||
proceedWithScanResponse(scanResponse)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun proceedWithScanResponse(scanResponse: ScanResponse) {
|
||||
val userWallet = coldUserWalletBuilderFactory.create(scanResponse = scanResponse).build()
|
||||
|
||||
if (userWallet == null) {
|
||||
Timber.e("User wallet not created")
|
||||
setLoading(false)
|
||||
return
|
||||
}
|
||||
|
||||
saveWalletUseCase(userWallet).fold(
|
||||
ifLeft = {
|
||||
Timber.e(it.toString(), "Unable to save user wallet")
|
||||
setLoading(false)
|
||||
},
|
||||
ifRight = {
|
||||
sendSignedInCardAnalyticsEvent(scanResponse)
|
||||
|
||||
// Select the wallet using new mechanism
|
||||
selectWalletUseCase(userWallet.walletId).fold(
|
||||
ifLeft = {
|
||||
Timber.e("Unable to select user wallet: $it")
|
||||
setLoading(false)
|
||||
},
|
||||
ifRight = {
|
||||
delay(HIDE_PROGRESS_DELAY)
|
||||
setLoading(false)
|
||||
appRouter.replaceAll(AppRoute.Wallet)
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun sendSignedInCardAnalyticsEvent(scanResponse: ScanResponse) {
|
||||
val currency = ParamCardCurrencyConverter().convert(value = scanResponse.cardTypesResolver)
|
||||
if (currency != null) {
|
||||
analyticsEventHandler.send(
|
||||
SignedIn(
|
||||
currency = currency,
|
||||
batch = scanResponse.card.batchId,
|
||||
signInType = SignInType.Card,
|
||||
walletsCount = "1",
|
||||
hasBackup = scanResponse.card.backupStatus?.isActive,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setLoading(isLoading: Boolean) {
|
||||
_uiState.update { it.copy(scanInProgress = isLoading) }
|
||||
}
|
||||
|
||||
fun handleScanError(error: TangemError) {
|
||||
when (error) {
|
||||
is TangemSdkError.NfcFeatureIsUnavailable -> {
|
||||
handleNfcFeatureUnavailable()
|
||||
}
|
||||
is TangemSdkError -> {
|
||||
Timber.e(error, "Scan error occurred")
|
||||
}
|
||||
else -> {
|
||||
Timber.e(error, "Error happened")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleNfcFeatureUnavailable() {
|
||||
uiMessageSender.send(
|
||||
message = DialogMessage(
|
||||
message = resourceReference(R.string.nfc_error_unavailable),
|
||||
title = resourceReference(id = R.string.common_error),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val NEW_BUY_WALLET_URL = "https://buy.tangem.com/?utm_source=tangem-app&utm_medium=app"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package com.tangem.features.home.impl.ui
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.ui.components.SystemBarsIconsDisposable
|
||||
import com.tangem.core.ui.res.TangemColorPalette
|
||||
import com.tangem.core.ui.utils.ChangeRootBackgroundColorEffect
|
||||
import com.tangem.features.home.impl.ui.compose.StoriesScreen
|
||||
import com.tangem.features.home.impl.ui.compose.StoriesScreenV2
|
||||
import com.tangem.features.home.impl.ui.state.HomeUM
|
||||
|
||||
@Composable
|
||||
internal fun Home(state: HomeUM, isV2StoriesEnabled: Boolean, modifier: Modifier = Modifier) {
|
||||
SystemBarsIconsDisposable(darkIcons = false)
|
||||
|
||||
if (isV2StoriesEnabled) {
|
||||
StoriesScreenV2(
|
||||
modifier = modifier,
|
||||
state = state,
|
||||
onCreateNewWalletButtonClick = state.onCreateNewWalletClick,
|
||||
onAddExistingWalletButtonClick = state.onAddExistingWalletClick,
|
||||
onScanButtonClick = state.onScanClick,
|
||||
)
|
||||
} else {
|
||||
StoriesScreen(
|
||||
modifier = modifier,
|
||||
state = state,
|
||||
onScanButtonClick = state.onScanClick,
|
||||
onShopButtonClick = state.onShopClick,
|
||||
onSearchTokensClick = state.onSearchTokensClick,
|
||||
)
|
||||
}
|
||||
|
||||
ChangeRootBackgroundColorEffect(TangemColorPalette.Black)
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.tap.features.home.compose
|
||||
package com.tangem.features.home.impl.ui.compose
|
||||
|
||||
import androidx.compose.animation.core.*
|
||||
import androidx.compose.foundation.Image
|
||||
|
|
@ -19,8 +19,8 @@ import androidx.compose.ui.layout.ContentScale
|
|||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.tap.common.compose.extensions.AnimatedValue
|
||||
import com.tangem.tap.common.compose.extensions.toAnimatable
|
||||
import com.tangem.core.ui.utils.AnimatedValue
|
||||
import com.tangem.core.ui.utils.toAnimatable
|
||||
|
||||
private const val SCALE_SWITCH_BARRIER = 1.15f
|
||||
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
@file:Suppress("MagicNumber")
|
||||
|
||||
package com.tangem.tap.features.home.compose
|
||||
package com.tangem.features.home.impl.ui.compose
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.fadeIn
|
||||
|
|
@ -23,24 +23,23 @@ import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameter
|
|||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.test.StoriesScreenTestTags
|
||||
import com.tangem.tap.features.home.compose.content.*
|
||||
import com.tangem.tap.features.home.compose.views.HomeButtons
|
||||
import com.tangem.tap.features.home.compose.views.SearchCurrenciesButton
|
||||
import com.tangem.tap.features.home.compose.views.StoriesProgressBar
|
||||
import com.tangem.tap.features.home.redux.HomeState
|
||||
import com.tangem.tap.features.home.redux.Stories
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.features.home.impl.ui.compose.content.*
|
||||
import com.tangem.features.home.impl.ui.compose.views.HomeButtons
|
||||
import com.tangem.features.home.impl.ui.compose.views.SearchCurrenciesButton
|
||||
import com.tangem.features.home.impl.ui.compose.views.StoriesProgressBar
|
||||
import com.tangem.features.home.impl.ui.state.Stories
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.features.home.impl.ui.state.HomeUM
|
||||
import kotlin.math.max
|
||||
|
||||
@Composable
|
||||
internal fun StoriesScreen(
|
||||
homeState: MutableState<HomeState>,
|
||||
state: HomeUM,
|
||||
onScanButtonClick: () -> Unit,
|
||||
onShopButtonClick: () -> Unit,
|
||||
onSearchTokensClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val state = homeState.value
|
||||
|
||||
var currentStory by remember { mutableStateOf(state.firstStory) }
|
||||
val currentStoryIndex by rememberUpdatedState(newValue = state.stepOf(currentStory))
|
||||
|
||||
|
|
@ -59,14 +58,14 @@ internal fun StoriesScreen(
|
|||
|
||||
// todo refactor [REDACTED_TASK_KEY]
|
||||
StoriesScreenContent(
|
||||
modifier = Modifier
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.testTag(StoriesScreenTestTags.SCREEN_CONTAINER),
|
||||
config = StoriesScreenContentConfig(
|
||||
storiesSize = state.stories.lastIndex,
|
||||
currentStoryIndex = currentStoryIndex,
|
||||
currentStory = currentStory,
|
||||
isScanInProgress = homeState.value.scanInProgress,
|
||||
isScanInProgress = state.scanInProgress,
|
||||
onGoToPreviousStory = goToPreviousStory,
|
||||
onGoToNextStory = goToNextStory,
|
||||
onSearchTokensClick = onSearchTokensClick,
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
@file:Suppress("MagicNumber")
|
||||
|
||||
package com.tangem.tap.features.home.compose
|
||||
package com.tangem.features.home.impl.ui.compose
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
|
|
@ -20,23 +20,22 @@ import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameter
|
|||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.test.StoriesScreenTestTags
|
||||
import com.tangem.tap.features.home.compose.content.*
|
||||
import com.tangem.tap.features.home.compose.views.HomeButtonsV2
|
||||
import com.tangem.tap.features.home.compose.views.StoriesProgressBar
|
||||
import com.tangem.tap.features.home.redux.HomeState
|
||||
import com.tangem.tap.features.home.redux.Stories
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.features.home.impl.ui.compose.content.*
|
||||
import com.tangem.features.home.impl.ui.compose.views.HomeButtonsV2
|
||||
import com.tangem.features.home.impl.ui.compose.views.StoriesProgressBar
|
||||
import com.tangem.features.home.impl.ui.state.Stories
|
||||
import kotlin.math.max
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.features.home.impl.ui.state.HomeUM
|
||||
|
||||
@Composable
|
||||
internal fun StoriesScreenV2(
|
||||
homeState: MutableState<HomeState>,
|
||||
state: HomeUM,
|
||||
onCreateNewWalletButtonClick: () -> Unit,
|
||||
onAddExistingWalletButtonClick: () -> Unit,
|
||||
onScanButtonClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val state = homeState.value
|
||||
|
||||
var currentStory by remember { mutableStateOf(state.firstStory) }
|
||||
val currentStoryIndex by rememberUpdatedState(newValue = state.stepOf(currentStory))
|
||||
|
||||
|
|
@ -55,14 +54,14 @@ internal fun StoriesScreenV2(
|
|||
|
||||
// todo refactor [REDACTED_TASK_KEY]
|
||||
StoriesScreenContentV2(
|
||||
modifier = Modifier
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.testTag(StoriesScreenTestTags.SCREEN_CONTAINER),
|
||||
config = StoriesScreenContentV2Config(
|
||||
storiesSize = state.stories.lastIndex,
|
||||
currentStoryIndex = currentStoryIndex,
|
||||
currentStory = currentStory,
|
||||
isScanInProgress = homeState.value.scanInProgress,
|
||||
isScanInProgress = state.scanInProgress,
|
||||
onGoToPreviousStory = goToPreviousStory,
|
||||
onGoToNextStory = goToNextStory,
|
||||
onCreateNewWalletButtonClick = onCreateNewWalletButtonClick,
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.tap.features.home.compose.content
|
||||
package com.tangem.features.home.impl.ui.compose.content
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.foundation.Image
|
||||
|
|
@ -21,9 +21,9 @@ import com.tangem.core.ui.components.SpacerH32
|
|||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemColorPalette
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.tap.features.home.compose.StoriesBottomImageAnimation
|
||||
import com.tangem.tap.features.home.compose.StoriesTextAnimation
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.features.home.impl.ui.compose.StoriesBottomImageAnimation
|
||||
import com.tangem.features.home.impl.ui.compose.StoriesTextAnimation
|
||||
import com.tangem.core.ui.R
|
||||
|
||||
@Composable
|
||||
fun StoriesRevolutionaryWallet() {
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.tap.features.home.compose.content
|
||||
package com.tangem.features.home.impl.ui.compose.content
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
|
|
@ -17,12 +17,10 @@ import androidx.compose.ui.unit.dp
|
|||
import com.tangem.core.ui.components.SpacerH12
|
||||
import com.tangem.core.ui.res.TangemColorPalette
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.tap.common.compose.extensions.dpSize
|
||||
import com.tangem.tap.common.compose.extensions.halfHeight
|
||||
import com.tangem.tap.common.compose.extensions.toPx
|
||||
import com.tangem.tap.common.extensions.isEven
|
||||
import com.tangem.tap.features.home.compose.HorizontalSlidingImage
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.features.home.impl.ui.compose.HorizontalSlidingImage
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.utils.dpSize
|
||||
import com.tangem.core.ui.utils.toPx
|
||||
|
||||
@Composable
|
||||
fun StoriesCurrenciesContent(paused: Boolean, duration: Int) {
|
||||
|
|
@ -138,4 +136,8 @@ private val BottomGradient: Brush = Brush.verticalGradient(
|
|||
TangemColorPalette.Black.copy(alpha = 0.95f),
|
||||
TangemColorPalette.Black,
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
fun DpSize.halfHeight(): Dp = this.height / 2
|
||||
|
||||
fun Int.isEven() = this and 1 == 0
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.tap.features.home.compose.content
|
||||
package com.tangem.features.home.impl.ui.compose.content
|
||||
|
||||
import androidx.compose.animation.core.Animatable
|
||||
import androidx.compose.animation.core.LinearEasing
|
||||
|
|
@ -25,8 +25,8 @@ import com.tangem.core.ui.components.SpacerH
|
|||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemColorPalette
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.tap.features.home.compose.StoriesTextAnimation
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.features.home.impl.ui.compose.StoriesTextAnimation
|
||||
import com.tangem.core.ui.R
|
||||
|
||||
@Suppress("LongMethod", "ComplexMethod", "MagicNumber")
|
||||
@Composable
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.tap.features.home.compose.content
|
||||
package com.tangem.features.home.impl.ui.compose.content
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.*
|
||||
|
|
@ -6,10 +6,10 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.ImageBitmap
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import com.tangem.tap.common.compose.extensions.AnimatedValue
|
||||
import com.tangem.tap.common.compose.extensions.asImageBitmap
|
||||
import com.tangem.tap.common.compose.extensions.toAnimatable
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.utils.AnimatedValue
|
||||
import com.tangem.core.ui.utils.asImageBitmap
|
||||
import com.tangem.core.ui.utils.toAnimatable
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.tap.features.home.compose.views
|
||||
package com.tangem.features.home.impl.ui.compose.views
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
|
|
@ -18,7 +18,7 @@ import com.tangem.core.ui.extensions.stringResourceSafe
|
|||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.test.StoriesScreenTestTags
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.core.ui.R
|
||||
|
||||
@Composable
|
||||
internal fun HomeButtons(
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.tap.features.home.compose.views
|
||||
package com.tangem.features.home.impl.ui.compose.views
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
|
|
@ -19,7 +19,7 @@ import com.tangem.core.ui.extensions.stringResourceSafe
|
|||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.test.StoriesScreenTestTags
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.core.ui.R
|
||||
|
||||
@Composable
|
||||
internal fun HomeButtonsV2(
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.tap.features.home.compose.views
|
||||
package com.tangem.features.home.impl.ui.compose.views
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
|
|
@ -12,7 +12,7 @@ import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
|
|||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.core.ui.R
|
||||
|
||||
@Composable
|
||||
internal fun SearchCurrenciesButton(onClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.tap.features.home.compose.views
|
||||
package com.tangem.features.home.impl.ui.compose.views
|
||||
|
||||
import androidx.compose.material3.ButtonColors
|
||||
import androidx.compose.runtime.Composable
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.tap.features.home.compose.views
|
||||
package com.tangem.features.home.impl.ui.compose.views
|
||||
|
||||
import android.provider.Settings
|
||||
import androidx.compose.animation.core.Animatable
|
||||
|
|
@ -1,15 +1,16 @@
|
|||
package com.tangem.tap.features.home.redux
|
||||
package com.tangem.features.home.impl.ui.state
|
||||
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import org.rekotlin.StateType
|
||||
|
||||
// todo refactor [REDACTED_TASK_KEY]
|
||||
data class HomeState(
|
||||
val scanInProgress: Boolean = false,
|
||||
val stories: ImmutableList<Stories> = getRestrictedStories().toImmutableList(),
|
||||
) : StateType {
|
||||
|
||||
data class HomeUM(
|
||||
val scanInProgress: Boolean,
|
||||
val stories: ImmutableList<Stories>,
|
||||
val onScanClick: () -> Unit,
|
||||
val onShopClick: () -> Unit,
|
||||
val onSearchTokensClick: () -> Unit,
|
||||
val onCreateNewWalletClick: () -> Unit,
|
||||
val onAddExistingWalletClick: () -> Unit,
|
||||
) {
|
||||
val firstStory: Stories get() = stories[0]
|
||||
|
||||
fun stepOf(story: Stories): Int = stories.indexOf(story)
|
||||
|
|
@ -206,7 +206,7 @@ internal class OnboardingEntryModel @Inject constructor(
|
|||
router.replaceAll(AppRoute.Wallet)
|
||||
}
|
||||
} else {
|
||||
router.replaceAll(AppRoute.Home)
|
||||
router.replaceAll(AppRoute.Home())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -197,7 +197,7 @@ internal class WalletSettingsModel @Inject constructor(
|
|||
if (hasUserWallets) {
|
||||
router.pop()
|
||||
} else {
|
||||
router.replaceAll(AppRoute.Home)
|
||||
router.replaceAll(AppRoute.Home())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ internal class WalletCardClickIntentsImplementor @Inject constructor(
|
|||
} else {
|
||||
tokenListStore.clear()
|
||||
stateHolder.clear()
|
||||
appRouter.replaceAll(AppRoute.Home)
|
||||
appRouter.replaceAll(AppRoute.Home())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ internal class DefaultWalletRouter @Inject constructor(
|
|||
}
|
||||
|
||||
override fun openStoriesScreen() {
|
||||
router.push(AppRoute.Home)
|
||||
router.push(AppRoute.Home())
|
||||
}
|
||||
|
||||
override fun isWalletLastScreen(): Boolean {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.features.welcome
|
||||
|
||||
import com.tangem.common.routing.entity.InitScreenLaunchMode
|
||||
import com.tangem.common.routing.entity.SerializableIntent
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
|
|
@ -7,6 +8,7 @@ import com.tangem.core.ui.decompose.ComposableContentComponent
|
|||
interface WelcomeComponent : ComposableContentComponent {
|
||||
|
||||
data class Params(
|
||||
val launchMode: InitScreenLaunchMode,
|
||||
val intent: SerializableIntent?,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -183,6 +183,9 @@ include(":libs:tangem-sdk-api")
|
|||
include(":features:onboarding-v2:api")
|
||||
include(":features:onboarding-v2:impl")
|
||||
|
||||
include(":features:home:api")
|
||||
include(":features:home:impl")
|
||||
|
||||
include(":features:referral:api")
|
||||
include(":features:referral:data")
|
||||
include(":features:referral:domain")
|
||||
|
|
|
|||