Updated on 2026-08-14
|
|
@ -232,6 +232,8 @@ dependencies {
|
||||||
implementation(projects.features.welcome.impl)
|
implementation(projects.features.welcome.impl)
|
||||||
implementation(projects.features.createWalletSelection.api)
|
implementation(projects.features.createWalletSelection.api)
|
||||||
implementation(projects.features.createWalletSelection.impl)
|
implementation(projects.features.createWalletSelection.impl)
|
||||||
|
implementation(projects.features.home.api)
|
||||||
|
implementation(projects.features.home.impl)
|
||||||
|
|
||||||
/** AndroidX libraries */
|
/** AndroidX libraries */
|
||||||
implementation(deps.androidx.core.ktx)
|
implementation(deps.androidx.core.ktx)
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ package com.tangem.tests
|
||||||
import android.content.Intent.ACTION_VIEW
|
import android.content.Intent.ACTION_VIEW
|
||||||
import com.tangem.common.BaseTestCase
|
import com.tangem.common.BaseTestCase
|
||||||
import com.tangem.common.extensions.clickWithAssertion
|
import com.tangem.common.extensions.clickWithAssertion
|
||||||
import com.tangem.screens.onDisclaimerScreen
|
import com.tangem.screens.DisclaimerTestScreen
|
||||||
import com.tangem.screens.onStoriesScreen
|
import com.tangem.screens.StoriesTestScreen
|
||||||
import com.tangem.tap.features.home.redux.HomeMiddleware.NEW_BUY_WALLET_URL
|
import com.tangem.tap.features.home.redux.HomeMiddleware.NEW_BUY_WALLET_URL
|
||||||
import dagger.hilt.android.testing.HiltAndroidTest
|
import dagger.hilt.android.testing.HiltAndroidTest
|
||||||
import io.github.kakaocup.kakao.intent.KIntent
|
import io.github.kakaocup.kakao.intent.KIntent
|
||||||
|
|
|
||||||
|
|
@ -176,13 +176,22 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
|
||||||
@Inject
|
@Inject
|
||||||
internal lateinit var testerMenuLauncher: TesterMenuLauncher
|
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()
|
internal val viewModel: MainViewModel by viewModels()
|
||||||
|
|
||||||
private lateinit var appThemeModeFlow: SharedFlow<AppThemeMode>
|
private lateinit var appThemeModeFlow: SharedFlow<AppThemeMode>
|
||||||
|
|
||||||
// TODO: fixme: inject through DI
|
|
||||||
private val intentProcessor: IntentProcessor = IntentProcessor()
|
|
||||||
|
|
||||||
private val dialogManager = DialogManager()
|
private val dialogManager = DialogManager()
|
||||||
|
|
||||||
private val onActivityResultCallbacks = mutableListOf<OnActivityResultCallback>()
|
private val onActivityResultCallbacks = mutableListOf<OnActivityResultCallback>()
|
||||||
|
|
@ -344,12 +353,10 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initIntentHandlers() {
|
private fun initIntentHandlers() {
|
||||||
val hasSavedWalletsProvider = { userWalletsListManager.hasUserWallets }
|
intentProcessor.addHandler(onPushClickedIntentHandler)
|
||||||
intentProcessor.addHandler(OnPushClickedIntentHandler(analyticsEventsHandler))
|
|
||||||
intentProcessor.addHandler(BackgroundScanIntentHandler(hasSavedWalletsProvider, lifecycleScope))
|
|
||||||
|
|
||||||
if (!walletConnectFeatureToggles.isRedesignedWalletConnectEnabled) {
|
if (!walletConnectFeatureToggles.isRedesignedWalletConnectEnabled) {
|
||||||
intentProcessor.addHandler(WalletConnectLinkIntentHandler())
|
intentProcessor.addHandler(walletConnectLinkIntentHandler)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -435,9 +442,15 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun navigateToInitialScreen(intentWhichStartedActivity: Intent?) {
|
private fun navigateToInitialScreen(intentWhichStartedActivity: Intent?) {
|
||||||
|
val launchMode = backgroundScanIntentHandler.getInitScreenLaunchMode(intentWhichStartedActivity)
|
||||||
if (userWalletsListManager.isLockable && userWalletsListManager.hasUserWallets) {
|
if (userWalletsListManager.isLockable && userWalletsListManager.hasUserWallets) {
|
||||||
store.dispatchNavigationAction {
|
store.dispatchNavigationAction {
|
||||||
replaceAll(AppRoute.Welcome(intentWhichStartedActivity?.let(::SerializableIntent)))
|
replaceAll(
|
||||||
|
AppRoute.Welcome(
|
||||||
|
launchMode = launchMode,
|
||||||
|
intent = intentWhichStartedActivity?.let(::SerializableIntent),
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
intentProcessor.handleIntent(
|
intentProcessor.handleIntent(
|
||||||
intent = intentWhichStartedActivity,
|
intent = intentWhichStartedActivity,
|
||||||
|
|
@ -452,7 +465,7 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
|
||||||
val route = when {
|
val route = when {
|
||||||
shouldShowTos -> AppRoute.Disclaimer(isTosAccepted = false)
|
shouldShowTos -> AppRoute.Disclaimer(isTosAccepted = false)
|
||||||
shouldShowInitialPush -> AppRoute.PushNotification
|
shouldShowInitialPush -> AppRoute.PushNotification
|
||||||
else -> AppRoute.Home
|
else -> AppRoute.Home(launchMode = launchMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
store.dispatchNavigationAction { replaceAll(route) }
|
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.ProductType
|
||||||
import com.tangem.domain.models.scan.ScanResponse
|
import com.tangem.domain.models.scan.ScanResponse
|
||||||
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
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.converters.ParamCardCurrencyConverter
|
||||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
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.common.extensions.inject
|
||||||
import com.tangem.tap.features.demo.DemoHelper
|
import com.tangem.tap.features.demo.DemoHelper
|
||||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
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.common.redux.global.globalReducer
|
||||||
import com.tangem.tap.features.details.redux.DetailsReducer
|
import com.tangem.tap.features.details.redux.DetailsReducer
|
||||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectReducer
|
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.features.welcome.redux.WelcomeReducer
|
||||||
import com.tangem.tap.proxy.redux.DaggerGraphReducer
|
import com.tangem.tap.proxy.redux.DaggerGraphReducer
|
||||||
import org.rekotlin.Action
|
import org.rekotlin.Action
|
||||||
|
|
@ -14,7 +13,6 @@ fun appReducer(action: Action, state: AppState?): AppState {
|
||||||
|
|
||||||
return AppState(
|
return AppState(
|
||||||
globalState = globalReducer(action, state),
|
globalState = globalReducer(action, state),
|
||||||
homeState = HomeReducer.reduce(action, state),
|
|
||||||
detailsState = DetailsReducer.reduce(action, state),
|
detailsState = DetailsReducer.reduce(action, state),
|
||||||
walletConnectState = WalletConnectReducer.reduce(action, state.walletConnectState),
|
walletConnectState = WalletConnectReducer.reduce(action, state.walletConnectState),
|
||||||
welcomeState = WelcomeReducer.reduce(action, state),
|
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.DetailsState
|
||||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectMiddleware
|
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectMiddleware
|
||||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectState
|
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.onboarding.products.wallet.redux.BackupMiddleware
|
||||||
import com.tangem.tap.features.wallet.redux.middlewares.TradeCryptoMiddleware
|
import com.tangem.tap.features.wallet.redux.middlewares.TradeCryptoMiddleware
|
||||||
import com.tangem.tap.features.welcome.redux.WelcomeMiddleware
|
import com.tangem.tap.features.welcome.redux.WelcomeMiddleware
|
||||||
|
|
@ -20,7 +18,6 @@ import org.rekotlin.StateType
|
||||||
|
|
||||||
data class AppState(
|
data class AppState(
|
||||||
val globalState: GlobalState = GlobalState(),
|
val globalState: GlobalState = GlobalState(),
|
||||||
val homeState: HomeState = HomeState(),
|
|
||||||
val detailsState: DetailsState = DetailsState(),
|
val detailsState: DetailsState = DetailsState(),
|
||||||
val walletConnectState: WalletConnectState = WalletConnectState(),
|
val walletConnectState: WalletConnectState = WalletConnectState(),
|
||||||
val welcomeState: WelcomeState = WelcomeState(),
|
val welcomeState: WelcomeState = WelcomeState(),
|
||||||
|
|
@ -32,7 +29,6 @@ data class AppState(
|
||||||
return listOf(
|
return listOf(
|
||||||
logMiddleware,
|
logMiddleware,
|
||||||
GlobalMiddleware.handler,
|
GlobalMiddleware.handler,
|
||||||
HomeMiddleware.handler,
|
|
||||||
DetailsMiddleware().detailsMiddleware,
|
DetailsMiddleware().detailsMiddleware,
|
||||||
WalletConnectMiddleware().walletConnectMiddleware,
|
WalletConnectMiddleware().walletConnectMiddleware,
|
||||||
BackupMiddleware().backupMiddleware,
|
BackupMiddleware().backupMiddleware,
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import android.content.Context
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.compose.ui.text.intl.Locale
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import com.tangem.core.analytics.Analytics
|
import com.tangem.core.analytics.Analytics
|
||||||
import com.tangem.core.analytics.models.AnalyticsParam
|
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.dispatchDialogHide
|
||||||
import com.tangem.tap.common.extensions.dispatchOpenUrl
|
import com.tangem.tap.common.extensions.dispatchOpenUrl
|
||||||
import com.tangem.tap.common.extensions.inject
|
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.proxy.redux.DaggerGraphState
|
||||||
import com.tangem.tap.scope
|
import com.tangem.tap.scope
|
||||||
import com.tangem.tap.store
|
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_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 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 {
|
fun create(context: Context, source: StateDialog.ScanFailsSource, onTryAgain: (() -> Unit)? = null): AlertDialog {
|
||||||
return AlertDialog.Builder(context, R.style.CustomMaterialDialog).apply {
|
return AlertDialog.Builder(context, R.style.CustomMaterialDialog).apply {
|
||||||
|
|
@ -62,8 +62,8 @@ internal object ScanFailsDialog {
|
||||||
source = sourceAnalytics,
|
source = sourceAnalytics,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
val locale = LocaleRegionProvider().getRegion()
|
val locale = Locale.current.region
|
||||||
val link = if (locale.lowercase() == RUSSIA_COUNTRY_CODE) HOW_TO_SCAN_RU_LINK else HOW_TO_SCAN_LINK
|
val link = if (locale.lowercase() == RUSSIA_LOCALE) HOW_TO_SCAN_RU_LINK else HOW_TO_SCAN_LINK
|
||||||
store.dispatchOpenUrl(link)
|
store.dispatchOpenUrl(link)
|
||||||
}
|
}
|
||||||
customView.findViewById<TextView>(R.id.request_support_button)?.setOnClickListener {
|
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()
|
deleteSavedAccessCodes()
|
||||||
store.inject(DaggerGraphState::walletsRepository).saveShouldSaveUserWallets(item = false)
|
store.inject(DaggerGraphState::walletsRepository).saveShouldSaveUserWallets(item = false)
|
||||||
|
|
||||||
store.dispatchNavigationAction { replaceAll(AppRoute.Home) }
|
store.dispatchNavigationAction { replaceAll(AppRoute.Home()) }
|
||||||
|
|
||||||
return CompletionResult.Success(Unit)
|
return CompletionResult.Success(Unit)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -268,7 +268,7 @@ internal class ResetCardModel @Inject constructor(
|
||||||
if (isLocked && userWalletsListManager.hasUserWallets) {
|
if (isLocked && userWalletsListManager.hasUserWallets) {
|
||||||
store.dispatchNavigationAction { popTo<AppRoute.Welcome>() }
|
store.dispatchNavigationAction { popTo<AppRoute.Welcome>() }
|
||||||
} else {
|
} 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.NfcAdapter
|
||||||
import android.nfc.Tag
|
import android.nfc.Tag
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
import com.tangem.common.routing.entity.InitScreenLaunchMode
|
||||||
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
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
[REDACTED_AUTHOR]
|
[REDACTED_AUTHOR]
|
||||||
*/
|
*/
|
||||||
class BackgroundScanIntentHandler(
|
class BackgroundScanIntentHandler {
|
||||||
private val hasSavedUserWalletsProvider: () -> Boolean,
|
|
||||||
private val scope: CoroutineScope,
|
|
||||||
) : IntentHandler, AffectsNavigation {
|
|
||||||
|
|
||||||
private val nfcActions = arrayOf(
|
private val nfcActions = arrayOf(
|
||||||
NfcAdapter.ACTION_NDEF_DISCOVERED,
|
NfcAdapter.ACTION_NDEF_DISCOVERED,
|
||||||
|
|
@ -26,8 +17,15 @@ class BackgroundScanIntentHandler(
|
||||||
NfcAdapter.ACTION_TAG_DISCOVERED,
|
NfcAdapter.ACTION_TAG_DISCOVERED,
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun handleIntent(intent: Intent?, isFromForeground: Boolean): Boolean {
|
fun getInitScreenLaunchMode(intent: Intent?): InitScreenLaunchMode {
|
||||||
if (isFromForeground) return true
|
return if (shouldOpenScanCard(intent)) {
|
||||||
|
InitScreenLaunchMode.WithCardScan
|
||||||
|
} else {
|
||||||
|
InitScreenLaunchMode.Standard
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun shouldOpenScanCard(intent: Intent?): Boolean {
|
||||||
if (intent == null || intent.action !in nfcActions) return false
|
if (intent == null || intent.action !in nfcActions) return false
|
||||||
|
|
||||||
val tag: Tag? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
val tag: Tag? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
|
@ -36,15 +34,9 @@ class BackgroundScanIntentHandler(
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
intent.getParcelableExtra(NfcAdapter.EXTRA_TAG)
|
intent.getParcelableExtra(NfcAdapter.EXTRA_TAG)
|
||||||
}
|
}
|
||||||
if (tag == null) return false
|
|
||||||
|
|
||||||
intent.action = null
|
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.dispatchOnMain
|
||||||
import com.tangem.tap.common.extensions.removePrefixOrNull
|
import com.tangem.tap.common.extensions.removePrefixOrNull
|
||||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
|
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.AffectsNavigation
|
||||||
|
import com.tangem.tap.features.intentHandler.IntentHandler
|
||||||
import com.tangem.tap.store
|
import com.tangem.tap.store
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.net.URLDecoder
|
import java.net.URLDecoder
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.tangem.tap.features.welcome.component
|
package com.tangem.tap.features.welcome.component
|
||||||
|
|
||||||
|
import com.tangem.common.routing.entity.InitScreenLaunchMode
|
||||||
import com.tangem.common.routing.entity.SerializableIntent
|
import com.tangem.common.routing.entity.SerializableIntent
|
||||||
import com.tangem.core.decompose.factory.ComponentFactory
|
import com.tangem.core.decompose.factory.ComponentFactory
|
||||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||||
|
|
@ -7,6 +8,7 @@ import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||||
interface WelcomeComponent : ComposableContentComponent {
|
interface WelcomeComponent : ComposableContentComponent {
|
||||||
|
|
||||||
data class Params(
|
data class Params(
|
||||||
|
val launchMode: InitScreenLaunchMode,
|
||||||
val intent: SerializableIntent?,
|
val intent: SerializableIntent?,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package com.tangem.tap.features.welcome.model
|
package com.tangem.tap.features.welcome.model
|
||||||
|
|
||||||
import com.tangem.common.core.TangemError
|
import com.tangem.common.core.TangemError
|
||||||
|
import com.tangem.common.routing.entity.InitScreenLaunchMode
|
||||||
import com.tangem.core.analytics.Analytics
|
import com.tangem.core.analytics.Analytics
|
||||||
import com.tangem.core.decompose.di.ModelScoped
|
import com.tangem.core.decompose.di.ModelScoped
|
||||||
import com.tangem.core.decompose.model.Model
|
import com.tangem.core.decompose.model.Model
|
||||||
|
|
@ -44,10 +45,9 @@ internal class WelcomeModel @Inject constructor(
|
||||||
subscribeToStoreChanges()
|
subscribeToStoreChanges()
|
||||||
initGlobalState()
|
initGlobalState()
|
||||||
|
|
||||||
val welcomeAction = if (params.intent != null) {
|
val welcomeAction = when (params.launchMode) {
|
||||||
WelcomeAction.ProceedWithIntent(params.intent.toIntent())
|
is InitScreenLaunchMode.WithCardScan -> WelcomeAction.ProceedWithCard
|
||||||
} else {
|
is InitScreenLaunchMode.Standard -> WelcomeAction.ProceedWithBiometrics(params.intent?.toIntent())
|
||||||
WelcomeAction.ProceedWithBiometrics()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
store.dispatch(welcomeAction)
|
store.dispatch(welcomeAction)
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,8 @@ import com.tangem.tap.*
|
||||||
import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter
|
import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter
|
||||||
import com.tangem.tap.common.extensions.*
|
import com.tangem.tap.common.extensions.*
|
||||||
import com.tangem.tap.common.redux.AppState
|
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.features.intentHandler.handlers.WalletConnectLinkIntentHandler
|
||||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.rekotlin.Middleware
|
import org.rekotlin.Middleware
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
@ -44,7 +42,7 @@ internal class WelcomeMiddleware {
|
||||||
private fun handleAction(action: WelcomeAction, state: WelcomeState) {
|
private fun handleAction(action: WelcomeAction, state: WelcomeState) {
|
||||||
mainScope.launch {
|
mainScope.launch {
|
||||||
when (action) {
|
when (action) {
|
||||||
is WelcomeAction.ProceedWithIntent -> proceedWithIntent(action.intent, scope = this)
|
is WelcomeAction.ProceedWithIntent -> proceedWithIntent(action.intent)
|
||||||
is WelcomeAction.ProceedWithBiometrics -> proceedWithBiometrics(
|
is WelcomeAction.ProceedWithBiometrics -> proceedWithBiometrics(
|
||||||
afterUnlockIntent = action.afterUnlockIntent ?: state.intent,
|
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(
|
Timber.d(
|
||||||
"""
|
"""
|
||||||
Proceeding with intent
|
Proceeding with intent
|
||||||
|
|
@ -63,15 +61,12 @@ internal class WelcomeMiddleware {
|
||||||
""".trimIndent(),
|
""".trimIndent(),
|
||||||
)
|
)
|
||||||
|
|
||||||
val handler = BackgroundScanIntentHandler(
|
|
||||||
scope = scope,
|
|
||||||
hasSavedUserWalletsProvider = { true },
|
|
||||||
)
|
|
||||||
val isBackgroundScanHandled = handler.handleIntent(initialIntent, isFromForeground = false)
|
|
||||||
val hasUncompletedBackup = backupService.hasIncompletedBackup
|
val hasUncompletedBackup = backupService.hasIncompletedBackup
|
||||||
|
|
||||||
if (!isBackgroundScanHandled && !hasUncompletedBackup) {
|
if (!hasUncompletedBackup) {
|
||||||
store.dispatchWithMain(WelcomeAction.ProceedWithBiometrics(initialIntent))
|
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.resetcard.api.ResetCardComponent
|
||||||
import com.tangem.tap.features.details.ui.securitymode.api.SecurityModeComponent
|
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.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.features.welcome.component.WelcomeComponent
|
||||||
import com.tangem.tap.routing.component.RoutingComponent.Child
|
import com.tangem.tap.routing.component.RoutingComponent.Child
|
||||||
import dagger.hilt.android.scopes.ActivityScoped
|
import dagger.hilt.android.scopes.ActivityScoped
|
||||||
|
|
@ -131,6 +131,7 @@ internal class ChildFactory @Inject constructor(
|
||||||
createComponentChild(
|
createComponentChild(
|
||||||
context = context,
|
context = context,
|
||||||
params = WelcomeComponent.Params(
|
params = WelcomeComponent.Params(
|
||||||
|
launchMode = route.launchMode,
|
||||||
intent = route.intent,
|
intent = route.intent,
|
||||||
),
|
),
|
||||||
componentFactory = welcomeComponentFactory,
|
componentFactory = welcomeComponentFactory,
|
||||||
|
|
@ -288,7 +289,7 @@ internal class ChildFactory @Inject constructor(
|
||||||
is AppRoute.Home -> {
|
is AppRoute.Home -> {
|
||||||
createComponentChild(
|
createComponentChild(
|
||||||
context = context,
|
context = context,
|
||||||
params = Unit,
|
params = HomeComponent.Params(route.launchMode),
|
||||||
componentFactory = homeComponentFactory,
|
componentFactory = homeComponentFactory,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -380,7 +381,7 @@ internal class ChildFactory @Inject constructor(
|
||||||
is AppRoute.PushNotification -> {
|
is AppRoute.PushNotification -> {
|
||||||
createComponentChild(
|
createComponentChild(
|
||||||
context = context,
|
context = context,
|
||||||
params = PushNotificationsComponent.Params.Route(AppRoute.Home),
|
params = PushNotificationsComponent.Params.Route(AppRoute.Home()),
|
||||||
componentFactory = pushNotificationsComponentFactory,
|
componentFactory = pushNotificationsComponentFactory,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package com.tangem.common.routing
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import com.tangem.common.routing.bundle.RouteBundleParams
|
import com.tangem.common.routing.bundle.RouteBundleParams
|
||||||
import com.tangem.common.routing.bundle.bundle
|
import com.tangem.common.routing.bundle.bundle
|
||||||
|
import com.tangem.common.routing.entity.InitScreenLaunchMode
|
||||||
import com.tangem.common.routing.entity.SerializableIntent
|
import com.tangem.common.routing.entity.SerializableIntent
|
||||||
import com.tangem.core.decompose.navigation.Route
|
import com.tangem.core.decompose.navigation.Route
|
||||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||||
|
|
@ -22,10 +23,14 @@ sealed class AppRoute(val path: String) : Route {
|
||||||
data object Initial : AppRoute(path = "/initial")
|
data object Initial : AppRoute(path = "/initial")
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data object Home : AppRoute(path = "/home")
|
data class Home(
|
||||||
|
val launchMode: InitScreenLaunchMode = InitScreenLaunchMode.Standard,
|
||||||
|
) : AppRoute(path = "/home")
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Welcome(
|
data class Welcome(
|
||||||
|
val launchMode: InitScreenLaunchMode = InitScreenLaunchMode.Standard,
|
||||||
|
// we still have this param to be handled by WalletConnectLinkIntentHandler in WelcomeMiddleware
|
||||||
val intent: SerializableIntent? = null,
|
val intent: SerializableIntent? = null,
|
||||||
) : AppRoute(path = "/welcome"), RouteBundleParams {
|
) : 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 packageValue: String?,
|
||||||
val component: String?,
|
val component: String?,
|
||||||
val flags: Int,
|
val flags: Int,
|
||||||
|
// CAUTION: works wrong with SerializableBundle constructor(bundle: Bundle), need to be removed
|
||||||
val extras: SerializableBundle?,
|
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.Animatable
|
||||||
import androidx.compose.animation.core.AnimationVector1D
|
import androidx.compose.animation.core.AnimationVector1D
|
||||||
|
|
@ -1,9 +1,15 @@
|
||||||
package com.tangem.core.ui.utils
|
package com.tangem.core.ui.utils
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.Stable
|
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.platform.LocalDensity
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.DpSize
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
@Composable
|
@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 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.annotation.DrawableRes
|
||||||
import androidx.appcompat.content.res.AppCompatResources
|
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 {
|
} else {
|
||||||
neverToInitiallyAskPermissionUseCase(PUSH_PERMISSION)
|
neverToInitiallyAskPermissionUseCase(PUSH_PERMISSION)
|
||||||
neverRequestPermissionUseCase(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.animation.core.*
|
||||||
import androidx.compose.foundation.Image
|
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.Dp
|
||||||
import androidx.compose.ui.unit.DpSize
|
import androidx.compose.ui.unit.DpSize
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.tangem.tap.common.compose.extensions.AnimatedValue
|
import com.tangem.core.ui.utils.AnimatedValue
|
||||||
import com.tangem.tap.common.compose.extensions.toAnimatable
|
import com.tangem.core.ui.utils.toAnimatable
|
||||||
|
|
||||||
private const val SCALE_SWITCH_BARRIER = 1.15f
|
private const val SCALE_SWITCH_BARRIER = 1.15f
|
||||||
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
@file:Suppress("MagicNumber")
|
@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.AnimatedVisibility
|
||||||
import androidx.compose.animation.fadeIn
|
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.TangemTheme
|
||||||
import com.tangem.core.ui.res.TangemThemePreview
|
import com.tangem.core.ui.res.TangemThemePreview
|
||||||
import com.tangem.core.ui.test.StoriesScreenTestTags
|
import com.tangem.core.ui.test.StoriesScreenTestTags
|
||||||
import com.tangem.tap.features.home.compose.content.*
|
import com.tangem.features.home.impl.ui.compose.content.*
|
||||||
import com.tangem.tap.features.home.compose.views.HomeButtons
|
import com.tangem.features.home.impl.ui.compose.views.HomeButtons
|
||||||
import com.tangem.tap.features.home.compose.views.SearchCurrenciesButton
|
import com.tangem.features.home.impl.ui.compose.views.SearchCurrenciesButton
|
||||||
import com.tangem.tap.features.home.compose.views.StoriesProgressBar
|
import com.tangem.features.home.impl.ui.compose.views.StoriesProgressBar
|
||||||
import com.tangem.tap.features.home.redux.HomeState
|
import com.tangem.features.home.impl.ui.state.Stories
|
||||||
import com.tangem.tap.features.home.redux.Stories
|
import com.tangem.core.ui.R
|
||||||
import com.tangem.wallet.R
|
import com.tangem.features.home.impl.ui.state.HomeUM
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun StoriesScreen(
|
internal fun StoriesScreen(
|
||||||
homeState: MutableState<HomeState>,
|
state: HomeUM,
|
||||||
onScanButtonClick: () -> Unit,
|
onScanButtonClick: () -> Unit,
|
||||||
onShopButtonClick: () -> Unit,
|
onShopButtonClick: () -> Unit,
|
||||||
onSearchTokensClick: () -> Unit,
|
onSearchTokensClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val state = homeState.value
|
|
||||||
|
|
||||||
var currentStory by remember { mutableStateOf(state.firstStory) }
|
var currentStory by remember { mutableStateOf(state.firstStory) }
|
||||||
val currentStoryIndex by rememberUpdatedState(newValue = state.stepOf(currentStory))
|
val currentStoryIndex by rememberUpdatedState(newValue = state.stepOf(currentStory))
|
||||||
|
|
||||||
|
|
@ -59,14 +58,14 @@ internal fun StoriesScreen(
|
||||||
|
|
||||||
// todo refactor [REDACTED_TASK_KEY]
|
// todo refactor [REDACTED_TASK_KEY]
|
||||||
StoriesScreenContent(
|
StoriesScreenContent(
|
||||||
modifier = Modifier
|
modifier = modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.testTag(StoriesScreenTestTags.SCREEN_CONTAINER),
|
.testTag(StoriesScreenTestTags.SCREEN_CONTAINER),
|
||||||
config = StoriesScreenContentConfig(
|
config = StoriesScreenContentConfig(
|
||||||
storiesSize = state.stories.lastIndex,
|
storiesSize = state.stories.lastIndex,
|
||||||
currentStoryIndex = currentStoryIndex,
|
currentStoryIndex = currentStoryIndex,
|
||||||
currentStory = currentStory,
|
currentStory = currentStory,
|
||||||
isScanInProgress = homeState.value.scanInProgress,
|
isScanInProgress = state.scanInProgress,
|
||||||
onGoToPreviousStory = goToPreviousStory,
|
onGoToPreviousStory = goToPreviousStory,
|
||||||
onGoToNextStory = goToNextStory,
|
onGoToNextStory = goToNextStory,
|
||||||
onSearchTokensClick = onSearchTokensClick,
|
onSearchTokensClick = onSearchTokensClick,
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
@file:Suppress("MagicNumber")
|
@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.Image
|
||||||
import androidx.compose.foundation.background
|
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.TangemTheme
|
||||||
import com.tangem.core.ui.res.TangemThemePreview
|
import com.tangem.core.ui.res.TangemThemePreview
|
||||||
import com.tangem.core.ui.test.StoriesScreenTestTags
|
import com.tangem.core.ui.test.StoriesScreenTestTags
|
||||||
import com.tangem.tap.features.home.compose.content.*
|
import com.tangem.features.home.impl.ui.compose.content.*
|
||||||
import com.tangem.tap.features.home.compose.views.HomeButtonsV2
|
import com.tangem.features.home.impl.ui.compose.views.HomeButtonsV2
|
||||||
import com.tangem.tap.features.home.compose.views.StoriesProgressBar
|
import com.tangem.features.home.impl.ui.compose.views.StoriesProgressBar
|
||||||
import com.tangem.tap.features.home.redux.HomeState
|
import com.tangem.features.home.impl.ui.state.Stories
|
||||||
import com.tangem.tap.features.home.redux.Stories
|
|
||||||
import com.tangem.wallet.R
|
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
|
import com.tangem.core.ui.R
|
||||||
|
import com.tangem.features.home.impl.ui.state.HomeUM
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun StoriesScreenV2(
|
internal fun StoriesScreenV2(
|
||||||
homeState: MutableState<HomeState>,
|
state: HomeUM,
|
||||||
onCreateNewWalletButtonClick: () -> Unit,
|
onCreateNewWalletButtonClick: () -> Unit,
|
||||||
onAddExistingWalletButtonClick: () -> Unit,
|
onAddExistingWalletButtonClick: () -> Unit,
|
||||||
onScanButtonClick: () -> Unit,
|
onScanButtonClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val state = homeState.value
|
|
||||||
|
|
||||||
var currentStory by remember { mutableStateOf(state.firstStory) }
|
var currentStory by remember { mutableStateOf(state.firstStory) }
|
||||||
val currentStoryIndex by rememberUpdatedState(newValue = state.stepOf(currentStory))
|
val currentStoryIndex by rememberUpdatedState(newValue = state.stepOf(currentStory))
|
||||||
|
|
||||||
|
|
@ -55,14 +54,14 @@ internal fun StoriesScreenV2(
|
||||||
|
|
||||||
// todo refactor [REDACTED_TASK_KEY]
|
// todo refactor [REDACTED_TASK_KEY]
|
||||||
StoriesScreenContentV2(
|
StoriesScreenContentV2(
|
||||||
modifier = Modifier
|
modifier = modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.testTag(StoriesScreenTestTags.SCREEN_CONTAINER),
|
.testTag(StoriesScreenTestTags.SCREEN_CONTAINER),
|
||||||
config = StoriesScreenContentV2Config(
|
config = StoriesScreenContentV2Config(
|
||||||
storiesSize = state.stories.lastIndex,
|
storiesSize = state.stories.lastIndex,
|
||||||
currentStoryIndex = currentStoryIndex,
|
currentStoryIndex = currentStoryIndex,
|
||||||
currentStory = currentStory,
|
currentStory = currentStory,
|
||||||
isScanInProgress = homeState.value.scanInProgress,
|
isScanInProgress = state.scanInProgress,
|
||||||
onGoToPreviousStory = goToPreviousStory,
|
onGoToPreviousStory = goToPreviousStory,
|
||||||
onGoToNextStory = goToNextStory,
|
onGoToNextStory = goToNextStory,
|
||||||
onCreateNewWalletButtonClick = onCreateNewWalletButtonClick,
|
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.annotation.DrawableRes
|
||||||
import androidx.compose.foundation.Image
|
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.extensions.stringResourceSafe
|
||||||
import com.tangem.core.ui.res.TangemColorPalette
|
import com.tangem.core.ui.res.TangemColorPalette
|
||||||
import com.tangem.core.ui.res.TangemTheme
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
import com.tangem.tap.features.home.compose.StoriesBottomImageAnimation
|
import com.tangem.features.home.impl.ui.compose.StoriesBottomImageAnimation
|
||||||
import com.tangem.tap.features.home.compose.StoriesTextAnimation
|
import com.tangem.features.home.impl.ui.compose.StoriesTextAnimation
|
||||||
import com.tangem.wallet.R
|
import com.tangem.core.ui.R
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun StoriesRevolutionaryWallet() {
|
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.background
|
||||||
import androidx.compose.foundation.layout.*
|
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.components.SpacerH12
|
||||||
import com.tangem.core.ui.res.TangemColorPalette
|
import com.tangem.core.ui.res.TangemColorPalette
|
||||||
import com.tangem.core.ui.res.TangemTheme
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
import com.tangem.tap.common.compose.extensions.dpSize
|
import com.tangem.features.home.impl.ui.compose.HorizontalSlidingImage
|
||||||
import com.tangem.tap.common.compose.extensions.halfHeight
|
import com.tangem.core.ui.R
|
||||||
import com.tangem.tap.common.compose.extensions.toPx
|
import com.tangem.core.ui.utils.dpSize
|
||||||
import com.tangem.tap.common.extensions.isEven
|
import com.tangem.core.ui.utils.toPx
|
||||||
import com.tangem.tap.features.home.compose.HorizontalSlidingImage
|
|
||||||
import com.tangem.wallet.R
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun StoriesCurrenciesContent(paused: Boolean, duration: Int) {
|
fun StoriesCurrenciesContent(paused: Boolean, duration: Int) {
|
||||||
|
|
@ -138,4 +136,8 @@ private val BottomGradient: Brush = Brush.verticalGradient(
|
||||||
TangemColorPalette.Black.copy(alpha = 0.95f),
|
TangemColorPalette.Black.copy(alpha = 0.95f),
|
||||||
TangemColorPalette.Black,
|
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.Animatable
|
||||||
import androidx.compose.animation.core.LinearEasing
|
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.extensions.stringResourceSafe
|
||||||
import com.tangem.core.ui.res.TangemColorPalette
|
import com.tangem.core.ui.res.TangemColorPalette
|
||||||
import com.tangem.core.ui.res.TangemTheme
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
import com.tangem.tap.features.home.compose.StoriesTextAnimation
|
import com.tangem.features.home.impl.ui.compose.StoriesTextAnimation
|
||||||
import com.tangem.wallet.R
|
import com.tangem.core.ui.R
|
||||||
|
|
||||||
@Suppress("LongMethod", "ComplexMethod", "MagicNumber")
|
@Suppress("LongMethod", "ComplexMethod", "MagicNumber")
|
||||||
@Composable
|
@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.Image
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
|
|
@ -6,10 +6,10 @@ import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.ImageBitmap
|
import androidx.compose.ui.graphics.ImageBitmap
|
||||||
import androidx.compose.ui.graphics.graphicsLayer
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
import com.tangem.tap.common.compose.extensions.AnimatedValue
|
import com.tangem.core.ui.R
|
||||||
import com.tangem.tap.common.compose.extensions.asImageBitmap
|
import com.tangem.core.ui.utils.AnimatedValue
|
||||||
import com.tangem.tap.common.compose.extensions.toAnimatable
|
import com.tangem.core.ui.utils.asImageBitmap
|
||||||
import com.tangem.wallet.R
|
import com.tangem.core.ui.utils.toAnimatable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
[REDACTED_AUTHOR]
|
[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.background
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
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.TangemTheme
|
||||||
import com.tangem.core.ui.res.TangemThemePreview
|
import com.tangem.core.ui.res.TangemThemePreview
|
||||||
import com.tangem.core.ui.test.StoriesScreenTestTags
|
import com.tangem.core.ui.test.StoriesScreenTestTags
|
||||||
import com.tangem.wallet.R
|
import com.tangem.core.ui.R
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun HomeButtons(
|
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.background
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
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.TangemTheme
|
||||||
import com.tangem.core.ui.res.TangemThemePreview
|
import com.tangem.core.ui.res.TangemThemePreview
|
||||||
import com.tangem.core.ui.test.StoriesScreenTestTags
|
import com.tangem.core.ui.test.StoriesScreenTestTags
|
||||||
import com.tangem.wallet.R
|
import com.tangem.core.ui.R
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun HomeButtonsV2(
|
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.background
|
||||||
import androidx.compose.foundation.layout.Box
|
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.extensions.stringResourceSafe
|
||||||
import com.tangem.core.ui.res.TangemTheme
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
import com.tangem.core.ui.res.TangemThemePreview
|
import com.tangem.core.ui.res.TangemThemePreview
|
||||||
import com.tangem.wallet.R
|
import com.tangem.core.ui.R
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun SearchCurrenciesButton(onClick: () -> Unit, modifier: Modifier = Modifier) {
|
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.material3.ButtonColors
|
||||||
import androidx.compose.runtime.Composable
|
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 android.provider.Settings
|
||||||
import androidx.compose.animation.core.Animatable
|
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.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]
|
val firstStory: Stories get() = stories[0]
|
||||||
|
|
||||||
fun stepOf(story: Stories): Int = stories.indexOf(story)
|
fun stepOf(story: Stories): Int = stories.indexOf(story)
|
||||||
|
|
@ -206,7 +206,7 @@ internal class OnboardingEntryModel @Inject constructor(
|
||||||
router.replaceAll(AppRoute.Wallet)
|
router.replaceAll(AppRoute.Wallet)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
router.replaceAll(AppRoute.Home)
|
router.replaceAll(AppRoute.Home())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -197,7 +197,7 @@ internal class WalletSettingsModel @Inject constructor(
|
||||||
if (hasUserWallets) {
|
if (hasUserWallets) {
|
||||||
router.pop()
|
router.pop()
|
||||||
} else {
|
} else {
|
||||||
router.replaceAll(AppRoute.Home)
|
router.replaceAll(AppRoute.Home())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,7 @@ internal class WalletCardClickIntentsImplementor @Inject constructor(
|
||||||
} else {
|
} else {
|
||||||
tokenListStore.clear()
|
tokenListStore.clear()
|
||||||
stateHolder.clear()
|
stateHolder.clear()
|
||||||
appRouter.replaceAll(AppRoute.Home)
|
appRouter.replaceAll(AppRoute.Home())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ internal class DefaultWalletRouter @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun openStoriesScreen() {
|
override fun openStoriesScreen() {
|
||||||
router.push(AppRoute.Home)
|
router.push(AppRoute.Home())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun isWalletLastScreen(): Boolean {
|
override fun isWalletLastScreen(): Boolean {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.tangem.features.welcome
|
package com.tangem.features.welcome
|
||||||
|
|
||||||
|
import com.tangem.common.routing.entity.InitScreenLaunchMode
|
||||||
import com.tangem.common.routing.entity.SerializableIntent
|
import com.tangem.common.routing.entity.SerializableIntent
|
||||||
import com.tangem.core.decompose.factory.ComponentFactory
|
import com.tangem.core.decompose.factory.ComponentFactory
|
||||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||||
|
|
@ -7,6 +8,7 @@ import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||||
interface WelcomeComponent : ComposableContentComponent {
|
interface WelcomeComponent : ComposableContentComponent {
|
||||||
|
|
||||||
data class Params(
|
data class Params(
|
||||||
|
val launchMode: InitScreenLaunchMode,
|
||||||
val intent: SerializableIntent?,
|
val intent: SerializableIntent?,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -183,6 +183,9 @@ include(":libs:tangem-sdk-api")
|
||||||
include(":features:onboarding-v2:api")
|
include(":features:onboarding-v2:api")
|
||||||
include(":features:onboarding-v2:impl")
|
include(":features:onboarding-v2:impl")
|
||||||
|
|
||||||
|
include(":features:home:api")
|
||||||
|
include(":features:home:impl")
|
||||||
|
|
||||||
include(":features:referral:api")
|
include(":features:referral:api")
|
||||||
include(":features:referral:data")
|
include(":features:referral:data")
|
||||||
include(":features:referral:domain")
|
include(":features:referral:domain")
|
||||||
|
|
|
||||||