Updated on 2026-08-14
This commit is contained in:
parent
78720c5cab
commit
fb54de419b
98 changed files with 750 additions and 787 deletions
|
|
@ -176,13 +176,22 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
|
|||
@Inject
|
||||
internal lateinit var testerMenuLauncher: TesterMenuLauncher
|
||||
|
||||
@Inject
|
||||
internal lateinit var intentProcessor: IntentProcessor
|
||||
|
||||
@Inject
|
||||
internal lateinit var walletConnectLinkIntentHandler: WalletConnectLinkIntentHandler
|
||||
|
||||
@Inject
|
||||
internal lateinit var onPushClickedIntentHandler: OnPushClickedIntentHandler
|
||||
|
||||
@Inject
|
||||
internal lateinit var backgroundScanIntentHandler: BackgroundScanIntentHandler
|
||||
|
||||
internal val viewModel: MainViewModel by viewModels()
|
||||
|
||||
private lateinit var appThemeModeFlow: SharedFlow<AppThemeMode>
|
||||
|
||||
// TODO: fixme: inject through DI
|
||||
private val intentProcessor: IntentProcessor = IntentProcessor()
|
||||
|
||||
private val dialogManager = DialogManager()
|
||||
|
||||
private val onActivityResultCallbacks = mutableListOf<OnActivityResultCallback>()
|
||||
|
|
@ -344,12 +353,10 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
|
|||
}
|
||||
|
||||
private fun initIntentHandlers() {
|
||||
val hasSavedWalletsProvider = { userWalletsListManager.hasUserWallets }
|
||||
intentProcessor.addHandler(OnPushClickedIntentHandler(analyticsEventsHandler))
|
||||
intentProcessor.addHandler(BackgroundScanIntentHandler(hasSavedWalletsProvider, lifecycleScope))
|
||||
intentProcessor.addHandler(onPushClickedIntentHandler)
|
||||
|
||||
if (!walletConnectFeatureToggles.isRedesignedWalletConnectEnabled) {
|
||||
intentProcessor.addHandler(WalletConnectLinkIntentHandler())
|
||||
intentProcessor.addHandler(walletConnectLinkIntentHandler)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -435,9 +442,15 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
|
|||
}
|
||||
|
||||
private fun navigateToInitialScreen(intentWhichStartedActivity: Intent?) {
|
||||
val launchMode = backgroundScanIntentHandler.getInitScreenLaunchMode(intentWhichStartedActivity)
|
||||
if (userWalletsListManager.isLockable && userWalletsListManager.hasUserWallets) {
|
||||
store.dispatchNavigationAction {
|
||||
replaceAll(AppRoute.Welcome(intentWhichStartedActivity?.let(::SerializableIntent)))
|
||||
replaceAll(
|
||||
AppRoute.Welcome(
|
||||
launchMode = launchMode,
|
||||
intent = intentWhichStartedActivity?.let(::SerializableIntent),
|
||||
),
|
||||
)
|
||||
}
|
||||
intentProcessor.handleIntent(
|
||||
intent = intentWhichStartedActivity,
|
||||
|
|
@ -452,7 +465,7 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
|
|||
val route = when {
|
||||
shouldShowTos -> AppRoute.Disclaimer(isTosAccepted = false)
|
||||
shouldShowInitialPush -> AppRoute.PushNotification
|
||||
else -> AppRoute.Home
|
||||
else -> AppRoute.Home(launchMode = launchMode)
|
||||
}
|
||||
|
||||
store.dispatchNavigationAction { replaceAll(route) }
|
||||
|
|
|
|||
|
|
@ -1,18 +0,0 @@
|
|||
package com.tangem.tap.common.analytics.events
|
||||
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed class IntroductionProcess(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : AnalyticsEvent("Introduction Process", event, params) {
|
||||
|
||||
class ScreenOpened : IntroductionProcess("Introduction Process Screen Opened")
|
||||
class ButtonTokensList : IntroductionProcess("Button - Tokens List")
|
||||
class ButtonBuyCards : IntroductionProcess("Button - Buy Cards")
|
||||
class ButtonScanCard : IntroductionProcess("Button - Scan Card")
|
||||
class ButtonRequestSupport : IntroductionProcess("Button - Request Support")
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
package com.tangem.tap.common.analytics.events
|
||||
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
import com.tangem.tap.common.extensions.filterNotNull
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed class Shop(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : AnalyticsEvent("Shop", event, params) {
|
||||
|
||||
class ScreenOpened : Shop("Shop Screen Opened")
|
||||
|
||||
class Purchased(sku: String, count: String, amount: String, couponCode: String?) : Shop(
|
||||
event = "Purchased",
|
||||
params = mapOf(
|
||||
"SKU" to sku,
|
||||
"Count" to count,
|
||||
"Amount" to amount,
|
||||
"Coupon Code" to couponCode,
|
||||
).filterNotNull(),
|
||||
)
|
||||
|
||||
class Redirected(partnerName: String?) : Shop(
|
||||
event = "Redirected",
|
||||
params = partnerName?.let { mapOf("Partner" to partnerName) } ?: mapOf(),
|
||||
)
|
||||
}
|
||||
|
|
@ -6,9 +6,9 @@ import com.tangem.domain.card.common.util.cardTypesResolver
|
|||
import com.tangem.domain.models.scan.ProductType
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
||||
import com.tangem.features.home.impl.analytics.IntroductionProcess
|
||||
import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.IntroductionProcess
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
|
|
|
|||
|
|
@ -1,54 +0,0 @@
|
|||
package com.tangem.tap.common.compose.extensions
|
||||
|
||||
import androidx.compose.animation.core.Animatable
|
||||
import androidx.compose.animation.core.AnimationVector1D
|
||||
import androidx.compose.animation.core.Easing
|
||||
import androidx.compose.animation.core.LinearEasing
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.remember
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
typealias AnimatedValue = Pair<Float, Float>
|
||||
|
||||
@Composable
|
||||
fun AnimatedValue.toAnimatable(
|
||||
isPaused: Boolean,
|
||||
duration: Int,
|
||||
easing: Easing = LinearEasing,
|
||||
): Animatable<Float, AnimationVector1D> {
|
||||
return animatable(
|
||||
values = this,
|
||||
isPaused = isPaused,
|
||||
duration = duration,
|
||||
easing = easing,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun animatable(
|
||||
values: AnimatedValue,
|
||||
duration: Int,
|
||||
isPaused: Boolean = false,
|
||||
easing: Easing = LinearEasing,
|
||||
): Animatable<Float, AnimationVector1D> {
|
||||
val animatable = remember { Animatable(values.first) }
|
||||
|
||||
LaunchedEffect(isPaused) {
|
||||
if (isPaused) {
|
||||
animatable.stop()
|
||||
} else {
|
||||
animatable.animateTo(
|
||||
targetValue = values.second,
|
||||
animationSpec = tween(
|
||||
durationMillis = duration,
|
||||
easing = easing,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
return animatable
|
||||
}
|
||||
|
|
@ -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.annotation.DrawableRes
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.ImageBitmap
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
fun asImageBitmap(@DrawableRes drawableId: Int): ImageBitmap {
|
||||
val drawable = requireNotNull(AppCompatResources.getDrawable(LocalContext.current, drawableId)) {
|
||||
"drawable is null"
|
||||
}
|
||||
return drawable.toBitmap().asImageBitmap()
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
package com.tangem.tap.common.compose.extensions
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.sdk.extensions.pxToDp
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
fun Painter.dpSize(): DpSize = DpSize(
|
||||
intrinsicSize.width.pxToDp().dp,
|
||||
intrinsicSize.height.pxToDp().dp,
|
||||
)
|
||||
|
||||
@Composable
|
||||
private fun Float.pxToDp(): Float = LocalContext.current.pxToDp(this)
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
package com.tangem.tap.common.extensions
|
||||
|
||||
fun Int.isEven() = this and 1 == 0
|
||||
|
|
@ -3,7 +3,6 @@ package com.tangem.tap.common.redux
|
|||
import com.tangem.tap.common.redux.global.globalReducer
|
||||
import com.tangem.tap.features.details.redux.DetailsReducer
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectReducer
|
||||
import com.tangem.tap.features.home.redux.HomeReducer
|
||||
import com.tangem.tap.features.welcome.redux.WelcomeReducer
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphReducer
|
||||
import org.rekotlin.Action
|
||||
|
|
@ -14,7 +13,6 @@ fun appReducer(action: Action, state: AppState?): AppState {
|
|||
|
||||
return AppState(
|
||||
globalState = globalReducer(action, state),
|
||||
homeState = HomeReducer.reduce(action, state),
|
||||
detailsState = DetailsReducer.reduce(action, state),
|
||||
walletConnectState = WalletConnectReducer.reduce(action, state.walletConnectState),
|
||||
welcomeState = WelcomeReducer.reduce(action, state),
|
||||
|
|
|
|||
|
|
@ -7,8 +7,6 @@ import com.tangem.tap.features.details.redux.DetailsMiddleware
|
|||
import com.tangem.tap.features.details.redux.DetailsState
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectMiddleware
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectState
|
||||
import com.tangem.tap.features.home.redux.HomeMiddleware
|
||||
import com.tangem.tap.features.home.redux.HomeState
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupMiddleware
|
||||
import com.tangem.tap.features.wallet.redux.middlewares.TradeCryptoMiddleware
|
||||
import com.tangem.tap.features.welcome.redux.WelcomeMiddleware
|
||||
|
|
@ -20,7 +18,6 @@ import org.rekotlin.StateType
|
|||
|
||||
data class AppState(
|
||||
val globalState: GlobalState = GlobalState(),
|
||||
val homeState: HomeState = HomeState(),
|
||||
val detailsState: DetailsState = DetailsState(),
|
||||
val walletConnectState: WalletConnectState = WalletConnectState(),
|
||||
val welcomeState: WelcomeState = WelcomeState(),
|
||||
|
|
@ -32,7 +29,6 @@ data class AppState(
|
|||
return listOf(
|
||||
logMiddleware,
|
||||
GlobalMiddleware.handler,
|
||||
HomeMiddleware.handler,
|
||||
DetailsMiddleware().detailsMiddleware,
|
||||
WalletConnectMiddleware().walletConnectMiddleware,
|
||||
BackupMiddleware().backupMiddleware,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import android.content.Context
|
|||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.compose.ui.text.intl.Locale
|
||||
import androidx.core.view.isVisible
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
|
|
@ -14,8 +15,6 @@ import com.tangem.tap.common.analytics.events.ScanFailsDialogAnalytics
|
|||
import com.tangem.tap.common.extensions.dispatchDialogHide
|
||||
import com.tangem.tap.common.extensions.dispatchOpenUrl
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.features.home.LocaleRegionProvider
|
||||
import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
|
|
@ -29,6 +28,7 @@ internal object ScanFailsDialog {
|
|||
|
||||
private const val HOW_TO_SCAN_RU_LINK = "https://tangem.com/ru/blog/post/scan-tangem-card/"
|
||||
private const val HOW_TO_SCAN_LINK = "https://tangem.com/en/blog/post/scan-tangem-card/"
|
||||
private const val RUSSIA_LOCALE = "ru"
|
||||
|
||||
fun create(context: Context, source: StateDialog.ScanFailsSource, onTryAgain: (() -> Unit)? = null): AlertDialog {
|
||||
return AlertDialog.Builder(context, R.style.CustomMaterialDialog).apply {
|
||||
|
|
@ -62,8 +62,8 @@ internal object ScanFailsDialog {
|
|||
source = sourceAnalytics,
|
||||
),
|
||||
)
|
||||
val locale = LocaleRegionProvider().getRegion()
|
||||
val link = if (locale.lowercase() == RUSSIA_COUNTRY_CODE) HOW_TO_SCAN_RU_LINK else HOW_TO_SCAN_LINK
|
||||
val locale = Locale.current.region
|
||||
val link = if (locale.lowercase() == RUSSIA_LOCALE) HOW_TO_SCAN_RU_LINK else HOW_TO_SCAN_LINK
|
||||
store.dispatchOpenUrl(link)
|
||||
}
|
||||
customView.findViewById<TextView>(R.id.request_support_button)?.setOnClickListener {
|
||||
|
|
|
|||
34
app/src/main/java/com/tangem/tap/di/IntentHandlingModule.kt
Normal file
34
app/src/main/java/com/tangem/tap/di/IntentHandlingModule.kt
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
package com.tangem.tap.di
|
||||
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.tap.features.intentHandler.IntentProcessor
|
||||
import com.tangem.tap.features.intentHandler.handlers.BackgroundScanIntentHandler
|
||||
import com.tangem.tap.features.intentHandler.handlers.OnPushClickedIntentHandler
|
||||
import com.tangem.tap.features.intentHandler.handlers.WalletConnectLinkIntentHandler
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object IntentHandlingModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideBackgroundScanIntentHandler(): BackgroundScanIntentHandler = BackgroundScanIntentHandler()
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideWalletConnectLinkIntentHandler(): WalletConnectLinkIntentHandler = WalletConnectLinkIntentHandler()
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideOnPushClickedIntentHandler(analyticsEventHandler: AnalyticsEventHandler): OnPushClickedIntentHandler =
|
||||
OnPushClickedIntentHandler(analyticsEventHandler)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideIntentProcessor(): IntentProcessor = IntentProcessor()
|
||||
}
|
||||
|
|
@ -233,7 +233,7 @@ class DetailsMiddleware {
|
|||
deleteSavedAccessCodes()
|
||||
store.inject(DaggerGraphState::walletsRepository).saveShouldSaveUserWallets(item = false)
|
||||
|
||||
store.dispatchNavigationAction { replaceAll(AppRoute.Home) }
|
||||
store.dispatchNavigationAction { replaceAll(AppRoute.Home()) }
|
||||
|
||||
return CompletionResult.Success(Unit)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -268,7 +268,7 @@ internal class ResetCardModel @Inject constructor(
|
|||
if (isLocked && userWalletsListManager.hasUserWallets) {
|
||||
store.dispatchNavigationAction { popTo<AppRoute.Welcome>() }
|
||||
} else {
|
||||
store.dispatchNavigationAction { replaceAll(AppRoute.Home) }
|
||||
store.dispatchNavigationAction { replaceAll(AppRoute.Home()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,90 +0,0 @@
|
|||
package com.tangem.tap.features.home
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import com.arkivanov.essenty.lifecycle.subscribe
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.ui.components.SystemBarsIconsDisposable
|
||||
import com.tangem.core.ui.utils.ChangeRootBackgroundColorEffect
|
||||
import com.tangem.core.ui.utils.findActivity
|
||||
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.features.home.api.HomeComponent
|
||||
import com.tangem.tap.features.home.compose.StoriesScreen
|
||||
import com.tangem.tap.features.home.compose.StoriesScreenV2
|
||||
import com.tangem.tap.features.home.redux.HomeAction
|
||||
import com.tangem.tap.features.home.redux.HomeState
|
||||
import com.tangem.tap.store
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import org.rekotlin.StoreSubscriber
|
||||
|
||||
@Suppress("UnusedPrivateMember")
|
||||
internal class DefaultHomeComponent @AssistedInject constructor(
|
||||
@Assisted appComponentContext: AppComponentContext,
|
||||
@Assisted params: Unit,
|
||||
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
) : HomeComponent, AppComponentContext by appComponentContext, StoreSubscriber<HomeState> {
|
||||
|
||||
private val model: HomeModel = getOrCreateModel()
|
||||
|
||||
private var homeState: MutableState<HomeState> = mutableStateOf(store.state.homeState)
|
||||
|
||||
init {
|
||||
lifecycle.subscribe(
|
||||
onCreate = {
|
||||
store.dispatch(HomeAction.OnCreate)
|
||||
},
|
||||
onStart = {
|
||||
store.subscribe(subscriber = this) { state ->
|
||||
state
|
||||
.skipRepeats { oldState, newState -> oldState.homeState == newState.homeState }
|
||||
.select(AppState::homeState)
|
||||
}
|
||||
},
|
||||
onStop = {
|
||||
store.unsubscribe(this)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val activity = LocalContext.current.findActivity()
|
||||
BackHandler(onBack = activity::finish)
|
||||
SystemBarsIconsDisposable(darkIcons = false)
|
||||
if (hotWalletFeatureToggles.isHotWalletEnabled) {
|
||||
StoriesScreenV2(
|
||||
homeState = homeState,
|
||||
onCreateNewWalletButtonClick = model::onCreateNewWalletScreen,
|
||||
onAddExistingWalletButtonClick = model::onAddExistingWalletScreen,
|
||||
onScanButtonClick = model::onScanClick,
|
||||
)
|
||||
} else {
|
||||
StoriesScreen(
|
||||
homeState = homeState,
|
||||
onScanButtonClick = model::onScanClick,
|
||||
onShopButtonClick = model::onShopClick,
|
||||
onSearchTokensClick = model::onSearchClick,
|
||||
)
|
||||
}
|
||||
|
||||
ChangeRootBackgroundColorEffect(Color(color = 0xFF010101))
|
||||
}
|
||||
|
||||
override fun newState(state: HomeState) {
|
||||
homeState.value = state
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : HomeComponent.Factory {
|
||||
override fun create(context: AppComponentContext, params: Unit): DefaultHomeComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -1,166 +0,0 @@
|
|||
package com.tangem.tap.features.home
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.google.firebase.analytics.ktx.analytics
|
||||
import com.google.firebase.ktx.Firebase
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.AppRoute.ManageTokens.Source
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.Basic
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.core.navigation.url.UrlOpener
|
||||
import com.tangem.domain.card.ScanCardProcessor
|
||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
|
||||
import com.tangem.domain.settings.usercountry.models.UserCountry
|
||||
import com.tangem.domain.tokens.TokensAction
|
||||
import com.tangem.domain.wallets.builder.ColdUserWalletBuilder
|
||||
import com.tangem.domain.wallets.usecase.SaveWalletUseCase
|
||||
import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter
|
||||
import com.tangem.tap.common.analytics.events.IntroductionProcess
|
||||
import com.tangem.tap.common.analytics.events.Shop
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.dispatchWithMain
|
||||
import com.tangem.tap.common.extensions.onUserWalletSelected
|
||||
import com.tangem.tap.features.home.redux.HIDE_PROGRESS_DELAY
|
||||
import com.tangem.tap.features.home.redux.HomeAction
|
||||
import com.tangem.tap.features.home.redux.HomeMiddleware.NEW_BUY_WALLET_URL
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Stable
|
||||
@ModelScoped
|
||||
internal class HomeModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val scanCardProcessor: ScanCardProcessor,
|
||||
private val saveWalletUseCase: SaveWalletUseCase,
|
||||
private val cardSdkConfigRepository: CardSdkConfigRepository,
|
||||
private val settingsRepository: SettingsRepository,
|
||||
private val urlOpener: UrlOpener,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val coldUserWalletBuilderFactory: ColdUserWalletBuilder.Factory,
|
||||
private val router: Router,
|
||||
getUserCountryUseCase: GetUserCountryUseCase,
|
||||
) : Model() {
|
||||
|
||||
private val tangemErrorHandler = TangemTangemErrorsHandler(store)
|
||||
|
||||
init {
|
||||
getUserCountryUseCase.invoke()
|
||||
.distinctUntilChanged()
|
||||
.filterNotNull()
|
||||
.onEach {
|
||||
val userCountry = it.getOrNull() ?: UserCountry.Other(Locale.getDefault().country)
|
||||
store.dispatchOnMain(HomeAction.UserCountryLoaded(userCountry))
|
||||
}
|
||||
.flowOn(dispatchers.io)
|
||||
.launchIn(modelScope)
|
||||
}
|
||||
|
||||
fun onCreateNewWalletScreen() {
|
||||
router.push(AppRoute.CreateWalletSelection)
|
||||
}
|
||||
|
||||
fun onAddExistingWalletScreen() {
|
||||
router.push(AppRoute.AddExistingWallet)
|
||||
}
|
||||
|
||||
fun onScanClick() {
|
||||
analyticsEventHandler.send(IntroductionProcess.ButtonScanCard())
|
||||
scanCard()
|
||||
}
|
||||
|
||||
fun onShopClick() {
|
||||
analyticsEventHandler.send(IntroductionProcess.ButtonBuyCards())
|
||||
analyticsEventHandler.send(Shop.ScreenOpened())
|
||||
|
||||
Firebase.analytics.appInstanceId
|
||||
.addOnSuccessListener { urlOpener.openUrl(url = "$NEW_BUY_WALLET_URL&app_instance_id=$it") }
|
||||
.addOnFailureListener { urlOpener.openUrl(url = NEW_BUY_WALLET_URL) }
|
||||
}
|
||||
|
||||
fun onSearchClick() {
|
||||
analyticsEventHandler.send(IntroductionProcess.ButtonTokensList())
|
||||
|
||||
store.dispatch(TokensAction.SetArgs.ReadAccess)
|
||||
store.dispatchNavigationAction { push(AppRoute.ManageTokens(Source.STORIES)) }
|
||||
}
|
||||
|
||||
private fun scanCard() {
|
||||
modelScope.launch {
|
||||
cardSdkConfigRepository.isBiometricsRequestPolicy = settingsRepository.shouldSaveAccessCodes()
|
||||
|
||||
scanCardProcessor.scan(
|
||||
analyticsSource = AnalyticsParam.ScreensSources.Intro,
|
||||
onProgressStateChange = { showProgress ->
|
||||
if (showProgress) {
|
||||
store.dispatch(HomeAction.ScanInProgress(scanInProgress = true))
|
||||
} else {
|
||||
delay(HIDE_PROGRESS_DELAY)
|
||||
store.dispatch(HomeAction.ScanInProgress(scanInProgress = false))
|
||||
}
|
||||
},
|
||||
onFailure = {
|
||||
tangemErrorHandler.onErrorReceived(error = it)
|
||||
delay(HIDE_PROGRESS_DELAY)
|
||||
store.dispatch(HomeAction.ScanInProgress(scanInProgress = false))
|
||||
},
|
||||
onSuccess = ::proceedWithScanResponse,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun proceedWithScanResponse(scanResponse: ScanResponse) {
|
||||
val userWallet = coldUserWalletBuilderFactory.create(scanResponse = scanResponse).build()
|
||||
|
||||
if (userWallet == null) {
|
||||
Timber.e("User wallet not created")
|
||||
return
|
||||
}
|
||||
|
||||
saveWalletUseCase(userWallet).fold(
|
||||
ifLeft = { Timber.e(it.toString(), "Unable to save user wallet") },
|
||||
ifRight = {
|
||||
sendSignedInCardAnalyticsEvent(scanResponse)
|
||||
coroutineScope { store.onUserWalletSelected(userWallet = userWallet) }
|
||||
},
|
||||
)
|
||||
|
||||
store.dispatchWithMain(HomeAction.ScanInProgress(scanInProgress = false))
|
||||
delay(HIDE_PROGRESS_DELAY)
|
||||
|
||||
store.dispatchNavigationAction { replaceAll(AppRoute.Wallet) }
|
||||
}
|
||||
|
||||
private fun sendSignedInCardAnalyticsEvent(scanResponse: ScanResponse) {
|
||||
val currency = ParamCardCurrencyConverter().convert(value = scanResponse.cardTypesResolver)
|
||||
|
||||
if (currency != null) {
|
||||
Analytics.send(
|
||||
event = Basic.SignedIn(
|
||||
currency = currency,
|
||||
batch = scanResponse.card.batchId,
|
||||
signInType = Basic.SignedIn.SignInType.Card,
|
||||
walletsCount = "1",
|
||||
hasBackup = scanResponse.card.backupStatus?.isActive,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
package com.tangem.tap.features.home
|
||||
|
||||
import androidx.compose.ui.text.intl.Locale
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface RegionProvider {
|
||||
fun getRegion(): String?
|
||||
}
|
||||
|
||||
class LocaleRegionProvider : RegionProvider {
|
||||
override fun getRegion(): String = Locale.current.region
|
||||
}
|
||||
|
||||
const val RUSSIA_COUNTRY_CODE = "ru"
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
package com.tangem.tap.features.home
|
||||
|
||||
import com.tangem.blockchain.common.BlockchainError
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.domain.redux.StateDialog
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.home.errors.TangemSdkErrorHandler
|
||||
import org.rekotlin.Store
|
||||
import timber.log.Timber
|
||||
|
||||
class TangemTangemErrorsHandler(val store: Store<AppState>) : TangemSdkErrorHandler {
|
||||
|
||||
override fun onErrorReceived(error: TangemError) {
|
||||
when (error) {
|
||||
is TangemSdkError -> {
|
||||
handleCardSdkError(error)
|
||||
}
|
||||
is BlockchainError -> {
|
||||
handleBlockchainSdkError(error)
|
||||
}
|
||||
else -> {
|
||||
Timber.e("Error happened", error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleCardSdkError(error: TangemSdkError) {
|
||||
when (error) {
|
||||
is TangemSdkError.NfcFeatureIsUnavailable -> {
|
||||
store.dispatchOnMain(GlobalAction.ShowDialog(StateDialog.NfcFeatureIsUnavailable))
|
||||
}
|
||||
else -> {
|
||||
Timber.e(error, "Unable to scan card")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleBlockchainSdkError(error: TangemError) {
|
||||
Timber.e("Sdk error happened", error)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
package com.tangem.tap.features.home.api
|
||||
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
|
||||
interface HomeComponent : ComposableContentComponent {
|
||||
|
||||
interface Factory : ComponentFactory<Unit, HomeComponent>
|
||||
}
|
||||
|
|
@ -1,156 +0,0 @@
|
|||
package com.tangem.tap.features.home.compose
|
||||
|
||||
import androidx.compose.animation.core.*
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.absoluteOffset
|
||||
import androidx.compose.foundation.layout.requiredHeight
|
||||
import androidx.compose.foundation.layout.requiredWidth
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.scale
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.tap.common.compose.extensions.AnimatedValue
|
||||
import com.tangem.tap.common.compose.extensions.toAnimatable
|
||||
|
||||
private const val SCALE_SWITCH_BARRIER = 1.15f
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
fun HorizontalSlidingImage(
|
||||
painter: Painter,
|
||||
paused: Boolean,
|
||||
duration: Int,
|
||||
itemSize: DpSize,
|
||||
startOffset: Float,
|
||||
targetOffset: Float,
|
||||
contentDescription: String,
|
||||
) {
|
||||
val translateX = AnimatedValue(startOffset * -1f, (startOffset + targetOffset) * -1f)
|
||||
|
||||
Image(
|
||||
modifier = Modifier
|
||||
.requiredWidth(itemSize.width)
|
||||
.requiredHeight(itemSize.height)
|
||||
.graphicsLayer(
|
||||
translationX = translateX.toAnimatable(isPaused = paused, duration = duration).value,
|
||||
),
|
||||
alignment = Alignment.TopStart,
|
||||
contentScale = ContentScale.FillBounds,
|
||||
painter = painter,
|
||||
contentDescription = contentDescription,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun StoriesTextAnimation(
|
||||
slideInDuration: Int = 500,
|
||||
slideInDelay: Int = 200,
|
||||
slideDistance: Dp = 60.dp,
|
||||
label: String = "",
|
||||
content: @Composable (Modifier) -> Unit,
|
||||
) {
|
||||
val isLaunched = remember { mutableStateOf(false) }
|
||||
val transition = updateTransition(targetState = isLaunched.value, label = label)
|
||||
|
||||
val offsetY = transition.animateDp(
|
||||
transitionSpec = {
|
||||
tween(
|
||||
durationMillis = slideInDuration,
|
||||
delayMillis = slideInDelay,
|
||||
easing = FastOutSlowInEasing,
|
||||
)
|
||||
},
|
||||
label = "Slide in",
|
||||
) { value -> if (value) 0.dp else slideDistance }
|
||||
|
||||
val alpha = transition.animateFloat(
|
||||
transitionSpec = {
|
||||
tween(
|
||||
durationMillis = slideInDuration * 2,
|
||||
delayMillis = slideInDelay,
|
||||
easing = FastOutSlowInEasing,
|
||||
)
|
||||
},
|
||||
label = "Visibility",
|
||||
) { value -> if (value) 1f else 0f }
|
||||
|
||||
content(
|
||||
Modifier
|
||||
.absoluteOffset(y = offsetY.value)
|
||||
.alpha(alpha.value),
|
||||
)
|
||||
|
||||
LaunchedEffect(Unit) { isLaunched.value = true }
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun StoriesBottomImageAnimation(
|
||||
initialScale: Float = 2.5f,
|
||||
secondStageScale: Float = SCALE_SWITCH_BARRIER,
|
||||
targetScale: Float = 1.0f,
|
||||
firstStepDuration: Int,
|
||||
totalDuration: Int,
|
||||
content: @Composable (Modifier) -> Unit,
|
||||
) {
|
||||
val secondStepDuration = totalDuration - firstStepDuration
|
||||
|
||||
val isFirstStepLaunched = remember { mutableStateOf(false) }
|
||||
val isSecondStepLaunched = remember { mutableStateOf(false) }
|
||||
|
||||
val firstTransition = updateTransition(
|
||||
targetState = isFirstStepLaunched.value,
|
||||
label = "Image appearing",
|
||||
)
|
||||
val firstScaleStep = firstTransition.animateFloat(
|
||||
transitionSpec = {
|
||||
tween(
|
||||
durationMillis = firstStepDuration,
|
||||
easing = FastOutLinearInEasing,
|
||||
)
|
||||
},
|
||||
label = "Appearing scale",
|
||||
) { value -> if (value) secondStageScale else initialScale }
|
||||
|
||||
val secondTransition = updateTransition(
|
||||
targetState = isSecondStepLaunched.value,
|
||||
label = "Image slow outgoing",
|
||||
)
|
||||
val secondScaleStep = secondTransition.animateFloat(
|
||||
transitionSpec = {
|
||||
tween(
|
||||
durationMillis = secondStepDuration,
|
||||
easing = LinearEasing,
|
||||
)
|
||||
},
|
||||
label = "Outgoing scale",
|
||||
) { value -> if (value) targetScale else secondStageScale }
|
||||
|
||||
val fadeIn = firstTransition.animateFloat(
|
||||
transitionSpec = { tween(durationMillis = 400) },
|
||||
label = "Fade in on start",
|
||||
) { value -> if (value) 1f else 0f }
|
||||
|
||||
if (firstScaleStep.value == secondStageScale) {
|
||||
isSecondStepLaunched.value = true
|
||||
}
|
||||
|
||||
val modifier = if (!isSecondStepLaunched.value) {
|
||||
Modifier.scale(firstScaleStep.value)
|
||||
} else {
|
||||
Modifier.scale(secondScaleStep.value)
|
||||
}.alpha(fadeIn.value)
|
||||
|
||||
content(modifier)
|
||||
|
||||
LaunchedEffect(Unit) { isFirstStepLaunched.value = true }
|
||||
}
|
||||
|
|
@ -1,266 +0,0 @@
|
|||
@file:Suppress("MagicNumber")
|
||||
|
||||
package com.tangem.tap.features.home.compose
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.test.StoriesScreenTestTags
|
||||
import com.tangem.tap.features.home.compose.content.*
|
||||
import com.tangem.tap.features.home.compose.views.HomeButtons
|
||||
import com.tangem.tap.features.home.compose.views.SearchCurrenciesButton
|
||||
import com.tangem.tap.features.home.compose.views.StoriesProgressBar
|
||||
import com.tangem.tap.features.home.redux.HomeState
|
||||
import com.tangem.tap.features.home.redux.Stories
|
||||
import com.tangem.wallet.R
|
||||
import kotlin.math.max
|
||||
|
||||
@Composable
|
||||
internal fun StoriesScreen(
|
||||
homeState: MutableState<HomeState>,
|
||||
onScanButtonClick: () -> Unit,
|
||||
onShopButtonClick: () -> Unit,
|
||||
onSearchTokensClick: () -> Unit,
|
||||
) {
|
||||
val state = homeState.value
|
||||
|
||||
var currentStory by remember { mutableStateOf(state.firstStory) }
|
||||
val currentStoryIndex by rememberUpdatedState(newValue = state.stepOf(currentStory))
|
||||
|
||||
val goToPreviousStory = remember(currentStory, currentStoryIndex) {
|
||||
{ currentStory = state.stories[max(0, currentStoryIndex - 1)] }
|
||||
}
|
||||
val goToNextStory = remember(currentStory, currentStoryIndex) {
|
||||
{
|
||||
currentStory = if (currentStoryIndex < state.stories.lastIndex) {
|
||||
state.stories[currentStoryIndex + 1]
|
||||
} else {
|
||||
state.firstStory
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// todo refactor [REDACTED_TASK_KEY]
|
||||
StoriesScreenContent(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.testTag(StoriesScreenTestTags.SCREEN_CONTAINER),
|
||||
config = StoriesScreenContentConfig(
|
||||
storiesSize = state.stories.lastIndex,
|
||||
currentStoryIndex = currentStoryIndex,
|
||||
currentStory = currentStory,
|
||||
isScanInProgress = homeState.value.scanInProgress,
|
||||
onGoToPreviousStory = goToPreviousStory,
|
||||
onGoToNextStory = goToNextStory,
|
||||
onSearchTokensClick = onSearchTokensClick,
|
||||
onScanButtonClick = onScanButtonClick,
|
||||
onShopButtonClick = onShopButtonClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Deprecated("Use StoriesContainer from core/ui")
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
private fun StoriesScreenContent(config: StoriesScreenContentConfig, modifier: Modifier = Modifier) {
|
||||
var isPressed by remember { mutableStateOf(value = false) }
|
||||
|
||||
val isPaused = isPressed || config.isScanInProgress
|
||||
val currentStoryDuration = config.currentStory.duration
|
||||
|
||||
Box(
|
||||
modifier = modifier.background(Color(0xFF010101)),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
) {
|
||||
Box(
|
||||
Modifier
|
||||
.weight(1f)
|
||||
.fillMaxHeight()
|
||||
.pointerInput(Unit) {
|
||||
detectTapGestures(
|
||||
onPress = {
|
||||
val pressStartTime = System.currentTimeMillis()
|
||||
isPressed = true
|
||||
this.tryAwaitRelease()
|
||||
val pressEndTime = System.currentTimeMillis()
|
||||
val totalPressTime = pressEndTime - pressStartTime
|
||||
if (totalPressTime < 200) config.onGoToPreviousStory()
|
||||
isPressed = false
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
Box(
|
||||
Modifier
|
||||
.weight(1f)
|
||||
.fillMaxHeight()
|
||||
.pointerInput(Unit) {
|
||||
detectTapGestures(
|
||||
onPress = {
|
||||
val pressStartTime = System.currentTimeMillis()
|
||||
isPressed = true
|
||||
this.tryAwaitRelease()
|
||||
val pressEndTime = System.currentTimeMillis()
|
||||
val totalPressTime = pressEndTime - pressStartTime
|
||||
if (totalPressTime < 200) config.onGoToNextStory()
|
||||
isPressed = false
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.statusBarsPadding()
|
||||
.fillMaxSize(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center,
|
||||
) {
|
||||
StoriesProgressBar(
|
||||
steps = config.storiesSize,
|
||||
currentStep = config.currentStoryIndex,
|
||||
stepDuration = currentStoryDuration,
|
||||
paused = isPaused,
|
||||
onStepFinish = config.onGoToNextStory,
|
||||
)
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.ic_tangem_logo),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.FillHeight,
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
start = TangemTheme.dimens.spacing16,
|
||||
top = TangemTheme.dimens.spacing16,
|
||||
)
|
||||
.height(TangemTheme.dimens.size18)
|
||||
.align(Alignment.Start),
|
||||
)
|
||||
when (config.currentStory) {
|
||||
Stories.TangemIntro -> FirstStoriesContent(
|
||||
isPaused = isPaused,
|
||||
duration = currentStoryDuration,
|
||||
)
|
||||
Stories.RevolutionaryWallet -> StoriesRevolutionaryWallet()
|
||||
Stories.UltraSecureBackup -> StoriesUltraSecureBackup(
|
||||
isPaused = isPaused,
|
||||
stepDuration = currentStoryDuration,
|
||||
)
|
||||
Stories.Currencies -> StoriesCurrencies(isPaused, currentStoryDuration)
|
||||
Stories.Web3 -> StoriesWeb3(isPaused, currentStoryDuration)
|
||||
Stories.WalletForEveryone -> StoriesWalletForEveryone(currentStoryDuration)
|
||||
}
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.navigationBarsPadding()
|
||||
.padding(bottom = TangemTheme.dimens.spacing16)
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||
.align(Alignment.BottomCenter)
|
||||
.fillMaxWidth(),
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
visible = config.currentStory == Stories.Currencies,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
) {
|
||||
SearchCurrenciesButton(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
onClick = config.onSearchTokensClick,
|
||||
)
|
||||
}
|
||||
|
||||
HomeButtons(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
btnScanStateInProgress = config.isScanInProgress,
|
||||
onScanButtonClick = config.onScanButtonClick,
|
||||
onShopButtonClick = config.onShopButtonClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private data class StoriesScreenContentConfig(
|
||||
val storiesSize: Int,
|
||||
val currentStoryIndex: Int,
|
||||
val currentStory: Stories,
|
||||
val isScanInProgress: Boolean,
|
||||
val onGoToPreviousStory: () -> Unit = {},
|
||||
val onGoToNextStory: () -> Unit = {},
|
||||
val onSearchTokensClick: () -> Unit = {},
|
||||
val onScanButtonClick: () -> Unit = {},
|
||||
val onShopButtonClick: () -> Unit = {},
|
||||
)
|
||||
|
||||
// region Preview
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun StoriesScreenContentPreview(
|
||||
@PreviewParameter(StoriesScreenContentConfigProvider::class) config: StoriesScreenContentConfig,
|
||||
) {
|
||||
TangemThemePreview {
|
||||
StoriesScreenContent(config = config)
|
||||
}
|
||||
}
|
||||
|
||||
private class StoriesScreenContentConfigProvider : CollectionPreviewParameterProvider<StoriesScreenContentConfig>(
|
||||
collection = listOf(
|
||||
StoriesScreenContentConfig(
|
||||
storiesSize = 6,
|
||||
currentStoryIndex = 0,
|
||||
currentStory = Stories.TangemIntro,
|
||||
isScanInProgress = true,
|
||||
),
|
||||
StoriesScreenContentConfig(
|
||||
storiesSize = 6,
|
||||
currentStoryIndex = 1,
|
||||
currentStory = Stories.RevolutionaryWallet,
|
||||
isScanInProgress = false,
|
||||
),
|
||||
StoriesScreenContentConfig(
|
||||
storiesSize = 6,
|
||||
currentStoryIndex = 2,
|
||||
currentStory = Stories.UltraSecureBackup,
|
||||
isScanInProgress = false,
|
||||
),
|
||||
StoriesScreenContentConfig(
|
||||
storiesSize = 6,
|
||||
currentStoryIndex = 3,
|
||||
currentStory = Stories.Currencies,
|
||||
isScanInProgress = false,
|
||||
),
|
||||
StoriesScreenContentConfig(
|
||||
storiesSize = 6,
|
||||
currentStoryIndex = 4,
|
||||
currentStory = Stories.Web3,
|
||||
isScanInProgress = false,
|
||||
),
|
||||
StoriesScreenContentConfig(
|
||||
storiesSize = 6,
|
||||
currentStoryIndex = 5,
|
||||
currentStory = Stories.WalletForEveryone,
|
||||
isScanInProgress = false,
|
||||
),
|
||||
),
|
||||
)
|
||||
// endregion Preview
|
||||
|
|
@ -1,252 +0,0 @@
|
|||
@file:Suppress("MagicNumber")
|
||||
|
||||
package com.tangem.tap.features.home.compose
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.test.StoriesScreenTestTags
|
||||
import com.tangem.tap.features.home.compose.content.*
|
||||
import com.tangem.tap.features.home.compose.views.HomeButtonsV2
|
||||
import com.tangem.tap.features.home.compose.views.StoriesProgressBar
|
||||
import com.tangem.tap.features.home.redux.HomeState
|
||||
import com.tangem.tap.features.home.redux.Stories
|
||||
import com.tangem.wallet.R
|
||||
import kotlin.math.max
|
||||
|
||||
@Composable
|
||||
internal fun StoriesScreenV2(
|
||||
homeState: MutableState<HomeState>,
|
||||
onCreateNewWalletButtonClick: () -> Unit,
|
||||
onAddExistingWalletButtonClick: () -> Unit,
|
||||
onScanButtonClick: () -> Unit,
|
||||
) {
|
||||
val state = homeState.value
|
||||
|
||||
var currentStory by remember { mutableStateOf(state.firstStory) }
|
||||
val currentStoryIndex by rememberUpdatedState(newValue = state.stepOf(currentStory))
|
||||
|
||||
val goToPreviousStory = remember(currentStory, currentStoryIndex) {
|
||||
{ currentStory = state.stories[max(0, currentStoryIndex - 1)] }
|
||||
}
|
||||
val goToNextStory = remember(currentStory, currentStoryIndex) {
|
||||
{
|
||||
currentStory = if (currentStoryIndex < state.stories.lastIndex) {
|
||||
state.stories[currentStoryIndex + 1]
|
||||
} else {
|
||||
state.firstStory
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// todo refactor [REDACTED_TASK_KEY]
|
||||
StoriesScreenContentV2(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.testTag(StoriesScreenTestTags.SCREEN_CONTAINER),
|
||||
config = StoriesScreenContentV2Config(
|
||||
storiesSize = state.stories.lastIndex,
|
||||
currentStoryIndex = currentStoryIndex,
|
||||
currentStory = currentStory,
|
||||
isScanInProgress = homeState.value.scanInProgress,
|
||||
onGoToPreviousStory = goToPreviousStory,
|
||||
onGoToNextStory = goToNextStory,
|
||||
onCreateNewWalletButtonClick = onCreateNewWalletButtonClick,
|
||||
onAddExistingWalletButtonClick = onAddExistingWalletButtonClick,
|
||||
onScanButtonClick = onScanButtonClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Deprecated("Use StoriesContainer from core/ui")
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
private fun StoriesScreenContentV2(config: StoriesScreenContentV2Config, modifier: Modifier = Modifier) {
|
||||
var isPressed by remember { mutableStateOf(value = false) }
|
||||
|
||||
val isPaused = isPressed || config.isScanInProgress
|
||||
val currentStoryDuration = config.currentStory.duration
|
||||
|
||||
Box(
|
||||
modifier = modifier.background(Color(0xFF010101)),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
) {
|
||||
Box(
|
||||
Modifier
|
||||
.weight(1f)
|
||||
.fillMaxHeight()
|
||||
.pointerInput(Unit) {
|
||||
detectTapGestures(
|
||||
onPress = {
|
||||
val pressStartTime = System.currentTimeMillis()
|
||||
isPressed = true
|
||||
this.tryAwaitRelease()
|
||||
val pressEndTime = System.currentTimeMillis()
|
||||
val totalPressTime = pressEndTime - pressStartTime
|
||||
if (totalPressTime < 200) config.onGoToPreviousStory()
|
||||
isPressed = false
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
Box(
|
||||
Modifier
|
||||
.weight(1f)
|
||||
.fillMaxHeight()
|
||||
.pointerInput(Unit) {
|
||||
detectTapGestures(
|
||||
onPress = {
|
||||
val pressStartTime = System.currentTimeMillis()
|
||||
isPressed = true
|
||||
this.tryAwaitRelease()
|
||||
val pressEndTime = System.currentTimeMillis()
|
||||
val totalPressTime = pressEndTime - pressStartTime
|
||||
if (totalPressTime < 200) config.onGoToNextStory()
|
||||
isPressed = false
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.statusBarsPadding()
|
||||
.fillMaxSize(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center,
|
||||
) {
|
||||
StoriesProgressBar(
|
||||
steps = config.storiesSize,
|
||||
currentStep = config.currentStoryIndex,
|
||||
stepDuration = currentStoryDuration,
|
||||
paused = isPaused,
|
||||
onStepFinish = config.onGoToNextStory,
|
||||
)
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.ic_tangem_logo),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.FillHeight,
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
start = TangemTheme.dimens.spacing16,
|
||||
top = TangemTheme.dimens.spacing16,
|
||||
)
|
||||
.height(TangemTheme.dimens.size18)
|
||||
.align(Alignment.Start),
|
||||
)
|
||||
when (config.currentStory) {
|
||||
Stories.TangemIntro -> FirstStoriesContent(
|
||||
isPaused = isPaused,
|
||||
duration = currentStoryDuration,
|
||||
)
|
||||
Stories.RevolutionaryWallet -> StoriesRevolutionaryWallet()
|
||||
Stories.UltraSecureBackup -> StoriesUltraSecureBackup(
|
||||
isPaused = isPaused,
|
||||
stepDuration = currentStoryDuration,
|
||||
)
|
||||
Stories.Currencies -> StoriesCurrencies(isPaused, currentStoryDuration)
|
||||
Stories.Web3 -> StoriesWeb3(isPaused, currentStoryDuration)
|
||||
Stories.WalletForEveryone -> StoriesWalletForEveryone(currentStoryDuration)
|
||||
}
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.navigationBarsPadding()
|
||||
.padding(bottom = TangemTheme.dimens.spacing16)
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||
.align(Alignment.BottomCenter)
|
||||
.fillMaxWidth(),
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
HomeButtonsV2(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
btnScanStateInProgress = config.isScanInProgress,
|
||||
onScanButtonClick = config.onScanButtonClick,
|
||||
onCreateNewWalletButtonClick = config.onCreateNewWalletButtonClick,
|
||||
onAddExistingWalletButtonClick = config.onAddExistingWalletButtonClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private data class StoriesScreenContentV2Config(
|
||||
val storiesSize: Int,
|
||||
val currentStoryIndex: Int,
|
||||
val currentStory: Stories,
|
||||
val isScanInProgress: Boolean,
|
||||
val onGoToPreviousStory: () -> Unit = {},
|
||||
val onGoToNextStory: () -> Unit = {},
|
||||
val onCreateNewWalletButtonClick: () -> Unit = {},
|
||||
val onAddExistingWalletButtonClick: () -> Unit = {},
|
||||
val onScanButtonClick: () -> Unit = {},
|
||||
)
|
||||
|
||||
// region Preview
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun StoriesScreenContentV2Preview(
|
||||
@PreviewParameter(StoriesScreenContentV2ConfigProvider::class) config: StoriesScreenContentV2Config,
|
||||
) {
|
||||
TangemThemePreview {
|
||||
StoriesScreenContentV2(config = config)
|
||||
}
|
||||
}
|
||||
|
||||
private class StoriesScreenContentV2ConfigProvider : CollectionPreviewParameterProvider<StoriesScreenContentV2Config>(
|
||||
collection = listOf(
|
||||
StoriesScreenContentV2Config(
|
||||
storiesSize = 6,
|
||||
currentStoryIndex = 0,
|
||||
currentStory = Stories.TangemIntro,
|
||||
isScanInProgress = true,
|
||||
),
|
||||
StoriesScreenContentV2Config(
|
||||
storiesSize = 6,
|
||||
currentStoryIndex = 1,
|
||||
currentStory = Stories.RevolutionaryWallet,
|
||||
isScanInProgress = false,
|
||||
),
|
||||
StoriesScreenContentV2Config(
|
||||
storiesSize = 6,
|
||||
currentStoryIndex = 2,
|
||||
currentStory = Stories.UltraSecureBackup,
|
||||
isScanInProgress = false,
|
||||
),
|
||||
StoriesScreenContentV2Config(
|
||||
storiesSize = 6,
|
||||
currentStoryIndex = 3,
|
||||
currentStory = Stories.Currencies,
|
||||
isScanInProgress = false,
|
||||
),
|
||||
StoriesScreenContentV2Config(
|
||||
storiesSize = 6,
|
||||
currentStoryIndex = 4,
|
||||
currentStory = Stories.Web3,
|
||||
isScanInProgress = false,
|
||||
),
|
||||
StoriesScreenContentV2Config(
|
||||
storiesSize = 6,
|
||||
currentStoryIndex = 5,
|
||||
currentStory = Stories.WalletForEveryone,
|
||||
isScanInProgress = false,
|
||||
),
|
||||
),
|
||||
)
|
||||
// endregion Preview
|
||||
|
|
@ -1,229 +0,0 @@
|
|||
package com.tangem.tap.features.home.compose.content
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.SpacerH
|
||||
import com.tangem.core.ui.components.SpacerH16
|
||||
import com.tangem.core.ui.components.SpacerH32
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemColorPalette
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.tap.features.home.compose.StoriesBottomImageAnimation
|
||||
import com.tangem.tap.features.home.compose.StoriesTextAnimation
|
||||
import com.tangem.wallet.R
|
||||
|
||||
@Composable
|
||||
fun StoriesRevolutionaryWallet() {
|
||||
SplitContent(
|
||||
topContent = {
|
||||
TopContent(
|
||||
titleText = stringResourceSafe(id = R.string.story_awe_title),
|
||||
subtitleText = stringResourceSafe(id = R.string.story_awe_description),
|
||||
)
|
||||
},
|
||||
bottomContent = {
|
||||
SpacerH32()
|
||||
StoriesImage(
|
||||
modifier = Modifier,
|
||||
drawableResId = R.drawable.img_revolutionary_wallet,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun StoriesUltraSecureBackup(isPaused: Boolean, stepDuration: Int) {
|
||||
SplitContent(
|
||||
topContent = {
|
||||
TopContent(
|
||||
titleText = stringResourceSafe(id = R.string.story_backup_title),
|
||||
subtitleText = stringResourceSafe(id = R.string.story_backup_description),
|
||||
)
|
||||
},
|
||||
bottomContent = {
|
||||
SpacerH32()
|
||||
FloatingCardsContent(
|
||||
isPaused = isPaused,
|
||||
stepDuration = stepDuration,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun StoriesCurrencies(isPaused: Boolean, stepDuration: Int) {
|
||||
SplitContent(
|
||||
topContent = {
|
||||
TopContent(
|
||||
titleText = stringResourceSafe(id = R.string.story_currencies_title),
|
||||
subtitleText = stringResourceSafe(id = R.string.story_currencies_description),
|
||||
)
|
||||
},
|
||||
bottomContent = {
|
||||
SpacerH32()
|
||||
StoriesCurrenciesContent(paused = isPaused, duration = stepDuration)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun StoriesWeb3(isPaused: Boolean, stepDuration: Int) {
|
||||
SplitContent(
|
||||
topContent = {
|
||||
TopContent(
|
||||
titleText = stringResourceSafe(id = R.string.story_web3_title),
|
||||
subtitleText = stringResourceSafe(id = R.string.story_web3_description),
|
||||
)
|
||||
},
|
||||
bottomContent = {
|
||||
SpacerH(TangemTheme.dimens.spacing70)
|
||||
StoriesWeb3Content(paused = isPaused, duration = stepDuration)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun StoriesWalletForEveryone(stepDuration: Int) {
|
||||
SplitContent(
|
||||
topContent = {
|
||||
TopContent(
|
||||
titleText = stringResourceSafe(id = R.string.story_finish_title),
|
||||
subtitleText = stringResourceSafe(id = R.string.story_finish_description),
|
||||
)
|
||||
},
|
||||
bottomContent = {
|
||||
SpacerH32()
|
||||
BoxWithGradient {
|
||||
StoriesBottomImageAnimation(
|
||||
initialScale = 2.6f,
|
||||
secondStageScale = 1.2f,
|
||||
targetScale = 1.1f,
|
||||
totalDuration = stepDuration,
|
||||
firstStepDuration = 500,
|
||||
) { modifier ->
|
||||
StoriesImage(
|
||||
modifier = modifier,
|
||||
drawableResId = R.drawable.img_tangem_for_everyone,
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SplitContent(topContent: @Composable () -> Unit, bottomContent: @Composable () -> Unit) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Top,
|
||||
) {
|
||||
topContent()
|
||||
bottomContent()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TopContent(titleText: String, subtitleText: String) {
|
||||
SpacerH(TangemTheme.dimens.spacing36)
|
||||
StoriesTitleText(
|
||||
text = titleText,
|
||||
)
|
||||
SpacerH16()
|
||||
StoriesSubtitleText(
|
||||
subtitleText = subtitleText,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
@Composable
|
||||
private fun StoriesTitleText(text: String) {
|
||||
StoriesTextAnimation(
|
||||
slideInDuration = 500,
|
||||
slideInDelay = 150,
|
||||
) { modifier ->
|
||||
Text(
|
||||
modifier = modifier
|
||||
.padding(start = 40.dp, end = 40.dp),
|
||||
text = text,
|
||||
style = TangemTheme.typography.head,
|
||||
color = TangemColorPalette.White,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
@Composable
|
||||
private fun StoriesSubtitleText(subtitleText: String) {
|
||||
StoriesTextAnimation(
|
||||
slideInDuration = 500,
|
||||
slideInDelay = 400,
|
||||
) { modifier ->
|
||||
Text(
|
||||
modifier = modifier
|
||||
.padding(start = 40.dp, end = 40.dp),
|
||||
text = subtitleText,
|
||||
style = TangemTheme.typography.body1,
|
||||
color = TangemColorPalette.Dark1,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun StoriesImage(@DrawableRes drawableResId: Int, modifier: Modifier = Modifier) {
|
||||
Image(
|
||||
painter = painterResource(id = drawableResId),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.Inside,
|
||||
modifier = modifier.fillMaxSize(),
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun RevolutionaryWalletPreview() {
|
||||
StoriesRevolutionaryWallet()
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun UltraSecureBackupPreview() {
|
||||
StoriesUltraSecureBackup(
|
||||
false,
|
||||
6000,
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun CurrenciesPreview() {
|
||||
StoriesCurrencies(false, 6000)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Web3Preview() {
|
||||
StoriesWeb3(false, 6000)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun WalletForEveryonePreview() {
|
||||
StoriesWalletForEveryone(6000)
|
||||
}
|
||||
|
|
@ -1,141 +0,0 @@
|
|||
package com.tangem.tap.features.home.compose.content
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.SpacerH12
|
||||
import com.tangem.core.ui.res.TangemColorPalette
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.tap.common.compose.extensions.dpSize
|
||||
import com.tangem.tap.common.compose.extensions.halfHeight
|
||||
import com.tangem.tap.common.compose.extensions.toPx
|
||||
import com.tangem.tap.common.extensions.isEven
|
||||
import com.tangem.tap.features.home.compose.HorizontalSlidingImage
|
||||
import com.tangem.wallet.R
|
||||
|
||||
@Composable
|
||||
fun StoriesCurrenciesContent(paused: Boolean, duration: Int) {
|
||||
val currencyDrawableList = remember {
|
||||
listOf(
|
||||
R.drawable.currency0,
|
||||
R.drawable.currency1,
|
||||
R.drawable.currency2,
|
||||
R.drawable.currency3,
|
||||
R.drawable.currency4,
|
||||
)
|
||||
}
|
||||
|
||||
val screenWidth = LocalConfiguration.current.screenWidthDp.dp
|
||||
val decreaseRate = remember { 1f / currencyDrawableList.size }
|
||||
val designItemHeight = remember { 82.dp }
|
||||
|
||||
BoxWithGradient {
|
||||
Column(modifier = Modifier.graphicsLayer(clip = false)) {
|
||||
currencyDrawableList.forEachIndexed { index, drawableResId ->
|
||||
val painter = painterResource(id = drawableResId)
|
||||
val scaledItemSize = scaleToDesignSize(painter.dpSize(), designItemHeight = designItemHeight)
|
||||
val itemOversizedScreenWidthBy = scaledItemSize.width - screenWidth
|
||||
val moveItemToStartOfScreen = itemOversizedScreenWidthBy / 2
|
||||
|
||||
val chessOffset = if (index.isEven()) 0.dp else scaledItemSize.halfHeight()
|
||||
val animateFrom = chessOffset - moveItemToStartOfScreen
|
||||
val animateTo = 50.dp - 50.dp * index * decreaseRate
|
||||
|
||||
HorizontalSlidingImage(
|
||||
paused = paused,
|
||||
duration = duration,
|
||||
painter = painter,
|
||||
itemSize = scaledItemSize,
|
||||
startOffset = animateFrom.toPx(),
|
||||
targetOffset = animateTo.toPx(),
|
||||
contentDescription = "Currency row",
|
||||
)
|
||||
SpacerH12()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
@Composable
|
||||
fun StoriesWeb3Content(paused: Boolean, duration: Int) {
|
||||
val dappsItemList = remember {
|
||||
listOf(
|
||||
R.drawable.dapps1,
|
||||
R.drawable.dapps1,
|
||||
R.drawable.dapps2,
|
||||
R.drawable.dapps3,
|
||||
R.drawable.dapps4,
|
||||
R.drawable.dapps5,
|
||||
)
|
||||
}
|
||||
val screenWidth = LocalConfiguration.current.screenWidthDp.dp
|
||||
val decreaseRate = remember { 1f / dappsItemList.size }
|
||||
val designItemHeight = 75.dp
|
||||
|
||||
BoxWithGradient {
|
||||
Column(modifier = Modifier.graphicsLayer(clip = false)) {
|
||||
dappsItemList.forEachIndexed { index, drawableResId ->
|
||||
val painter = painterResource(id = drawableResId)
|
||||
val scaledItemSize = scaleToDesignSize(painter.dpSize(), designItemHeight = designItemHeight)
|
||||
val itemOversizedScreenWidthBy = scaledItemSize.width - screenWidth
|
||||
val moveItemToStartOfScreen = itemOversizedScreenWidthBy / 2
|
||||
|
||||
val chessOffset = if (index.isEven()) 0.dp else scaledItemSize.width / 3
|
||||
val animateFrom = chessOffset - moveItemToStartOfScreen
|
||||
val animateTo = 70.dp - 70.dp * index * decreaseRate
|
||||
|
||||
HorizontalSlidingImage(
|
||||
paused = paused,
|
||||
duration = duration,
|
||||
painter = painter,
|
||||
itemSize = scaledItemSize,
|
||||
startOffset = animateFrom.toPx(),
|
||||
targetOffset = animateTo.toPx(),
|
||||
contentDescription = "Web3 row",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun BoxWithGradient(content: @Composable () -> Unit) {
|
||||
val bottomInsetsPx = WindowInsets.navigationBars.getBottom(LocalDensity.current)
|
||||
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
content()
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.fillMaxWidth()
|
||||
.height(TangemTheme.dimens.size164 + bottomInsetsPx.dp)
|
||||
.background(BottomGradient),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun scaleToDesignSize(itemSize: DpSize, designItemHeight: Dp): DpSize {
|
||||
val scaleRate = itemSize.height / designItemHeight
|
||||
return itemSize / scaleRate
|
||||
}
|
||||
|
||||
private val BottomGradient: Brush = Brush.verticalGradient(
|
||||
colors = listOf(
|
||||
TangemColorPalette.Black.copy(alpha = 0f),
|
||||
TangemColorPalette.Black.copy(alpha = 0.75f),
|
||||
TangemColorPalette.Black.copy(alpha = 0.95f),
|
||||
TangemColorPalette.Black,
|
||||
),
|
||||
)
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
package com.tangem.tap.features.home.compose.content
|
||||
|
||||
import androidx.compose.animation.core.Animatable
|
||||
import androidx.compose.animation.core.LinearEasing
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.tangem.core.ui.components.SpacerH
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemColorPalette
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.tap.features.home.compose.StoriesTextAnimation
|
||||
import com.tangem.wallet.R
|
||||
|
||||
@Suppress("LongMethod", "ComplexMethod", "MagicNumber")
|
||||
@Composable
|
||||
fun FirstStoriesContent(isPaused: Boolean, duration: Int) {
|
||||
val progress = remember { Animatable(0f) }
|
||||
|
||||
LaunchedEffect(isPaused) {
|
||||
if (isPaused) {
|
||||
progress.stop()
|
||||
} else {
|
||||
progress.animateTo(
|
||||
targetValue = 2f,
|
||||
animationSpec = tween(
|
||||
durationMillis = duration,
|
||||
easing = LinearEasing,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val style = TextStyle(
|
||||
fontSize = 46.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = Color.White,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
SpacerH(TangemTheme.dimens.spacing94)
|
||||
StoriesTextAnimation(
|
||||
slideInDuration = 500,
|
||||
slideInDelay = 150,
|
||||
) { modifier ->
|
||||
Text(
|
||||
modifier = modifier,
|
||||
text = stringResourceSafe(R.string.story_meet_title),
|
||||
style = style,
|
||||
color = TangemColorPalette.White,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
SpacerH(TangemTheme.dimens.spacing46)
|
||||
Image(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
painter = painterResource(R.drawable.img_meet_tangem),
|
||||
contentScale = ContentScale.Inside,
|
||||
contentDescription = "Tangem Wallet card",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun FirstStoriesPreview() {
|
||||
FirstStoriesContent(
|
||||
false,
|
||||
8000,
|
||||
)
|
||||
}
|
||||
|
|
@ -1,97 +0,0 @@
|
|||
package com.tangem.tap.features.home.compose.content
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.ImageBitmap
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import com.tangem.tap.common.compose.extensions.AnimatedValue
|
||||
import com.tangem.tap.common.compose.extensions.asImageBitmap
|
||||
import com.tangem.tap.common.compose.extensions.toAnimatable
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
fun FloatingCardsContent(isPaused: Boolean, stepDuration: Int) {
|
||||
val imageBitmap = asImageBitmap(R.drawable.img_card_placeholder_wallet_2)
|
||||
val cards = listOf(
|
||||
FloatingCard.first(),
|
||||
FloatingCard.second(),
|
||||
FloatingCard.third(),
|
||||
)
|
||||
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
cards.forEach { floatingCard ->
|
||||
FloatingCard.Item(
|
||||
isPaused = isPaused,
|
||||
imageBitmap = imageBitmap,
|
||||
cardValues = floatingCard,
|
||||
stepDuration = stepDuration,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private data class CardValues(
|
||||
val translateX: AnimatedValue = AnimatedValue(0f, 0f),
|
||||
val translateY: AnimatedValue = AnimatedValue(0f, 0f),
|
||||
val rotationX: AnimatedValue = AnimatedValue(0f, 0f),
|
||||
val rotationY: AnimatedValue = AnimatedValue(0f, 0f),
|
||||
val rotationZ: AnimatedValue = AnimatedValue(0f, 0f),
|
||||
val scale: AnimatedValue = AnimatedValue(1f, 1f),
|
||||
)
|
||||
|
||||
private object FloatingCard {
|
||||
|
||||
@Suppress("TopLevelComposableFunctions")
|
||||
@Composable
|
||||
fun Item(isPaused: Boolean, stepDuration: Int, imageBitmap: ImageBitmap, cardValues: CardValues) {
|
||||
Image(
|
||||
bitmap = imageBitmap,
|
||||
contentDescription = "Floating Tangem card",
|
||||
modifier = Modifier
|
||||
.graphicsLayer(
|
||||
translationX = cardValues.translateX.toAnimatable(isPaused, stepDuration).value,
|
||||
translationY = cardValues.translateY.toAnimatable(isPaused, stepDuration).value,
|
||||
rotationX = cardValues.rotationX.toAnimatable(isPaused, stepDuration).value,
|
||||
rotationY = cardValues.rotationY.toAnimatable(isPaused, stepDuration).value,
|
||||
rotationZ = cardValues.rotationZ.toAnimatable(isPaused, stepDuration).value,
|
||||
scaleX = cardValues.scale.toAnimatable(isPaused, stepDuration).value,
|
||||
scaleY = cardValues.scale.toAnimatable(isPaused, stepDuration).value,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
fun first(): CardValues = CardValues(
|
||||
translateX = -400f to -350f,
|
||||
translateY = 30f to 32f,
|
||||
rotationX = 10f to 15f,
|
||||
rotationY = 15f to 15f,
|
||||
rotationZ = 40f to 27f,
|
||||
scale = 0.6f to 0.6f,
|
||||
)
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
fun second(): CardValues = CardValues(
|
||||
translateX = 350f to 300f,
|
||||
translateY = -70f to 0f,
|
||||
rotationX = 30f to 48f,
|
||||
rotationY = 0f to 5f,
|
||||
rotationZ = -34f to -42f,
|
||||
scale = 0.47f to 0.35f,
|
||||
)
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
fun third(): CardValues = CardValues(
|
||||
translateX = 320f to 250f,
|
||||
translateY = 500f to 500f,
|
||||
rotationX = 0f to 3f,
|
||||
rotationY = 10f to 10f,
|
||||
rotationZ = -45f to -30f,
|
||||
scale = 0.6f to 0.75f,
|
||||
)
|
||||
}
|
||||
|
|
@ -1,105 +0,0 @@
|
|||
package com.tangem.tap.features.home.compose.views
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import com.tangem.core.ui.components.SpacerW12
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.test.StoriesScreenTestTags
|
||||
import com.tangem.wallet.R
|
||||
|
||||
@Composable
|
||||
internal fun HomeButtons(
|
||||
btnScanStateInProgress: Boolean,
|
||||
onScanButtonClick: () -> Unit,
|
||||
onShopButtonClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||
modifier = modifier,
|
||||
) {
|
||||
ScanCardButton(
|
||||
modifier = Modifier
|
||||
.weight(weight = 1f)
|
||||
.testTag(StoriesScreenTestTags.SCAN_BUTTON),
|
||||
showProgress = btnScanStateInProgress,
|
||||
onClick = onScanButtonClick,
|
||||
)
|
||||
SpacerW12()
|
||||
OrderCardButton(
|
||||
modifier = Modifier
|
||||
.weight(weight = 1f)
|
||||
.testTag(StoriesScreenTestTags.ORDER_BUTTON),
|
||||
onClick = onShopButtonClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ScanCardButton(showProgress: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||
StoriesButton(
|
||||
modifier = modifier,
|
||||
text = stringResourceSafe(id = R.string.home_button_scan),
|
||||
useDarkerColors = false,
|
||||
icon = TangemButtonIconPosition.End(iconResId = R.drawable.ic_tangem_24),
|
||||
onClick = onClick,
|
||||
showProgress = showProgress,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun OrderCardButton(onClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||
StoriesButton(
|
||||
modifier = modifier,
|
||||
text = stringResourceSafe(id = R.string.home_button_order),
|
||||
useDarkerColors = true,
|
||||
onClick = onClick,
|
||||
)
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun HomeButtonsPreview(@PreviewParameter(HomeButtonsParameterProvider::class) state: HomeButtonsState) {
|
||||
TangemThemePreview {
|
||||
Box(
|
||||
modifier = Modifier.background(Color.Black),
|
||||
) {
|
||||
HomeButtons(
|
||||
btnScanStateInProgress = state.btnScanStateInProgress,
|
||||
onScanButtonClick = {},
|
||||
onShopButtonClick = {},
|
||||
modifier = Modifier.padding(all = TangemTheme.dimens.spacing16),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class HomeButtonsParameterProvider : CollectionPreviewParameterProvider<HomeButtonsState>(
|
||||
collection = listOf(
|
||||
HomeButtonsState(
|
||||
btnScanStateInProgress = false,
|
||||
),
|
||||
HomeButtonsState(
|
||||
btnScanStateInProgress = true,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
private data class HomeButtonsState(
|
||||
val btnScanStateInProgress: Boolean,
|
||||
)
|
||||
// endregion Preview
|
||||
|
|
@ -1,124 +0,0 @@
|
|||
package com.tangem.tap.features.home.compose.views
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.test.StoriesScreenTestTags
|
||||
import com.tangem.wallet.R
|
||||
|
||||
@Composable
|
||||
internal fun HomeButtonsV2(
|
||||
btnScanStateInProgress: Boolean,
|
||||
onScanButtonClick: () -> Unit,
|
||||
onCreateNewWalletButtonClick: () -> Unit,
|
||||
onAddExistingWalletButtonClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxWidth(),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
CreateNewWalletButton(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(StoriesScreenTestTags.CREATE_NEW_WALLET_BUTTON),
|
||||
onClick = onCreateNewWalletButtonClick,
|
||||
)
|
||||
AddExistingWalletButton(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(StoriesScreenTestTags.ADD_EXISTING_WALLET_BUTTON),
|
||||
onClick = onAddExistingWalletButtonClick,
|
||||
)
|
||||
ScanCardButton(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(StoriesScreenTestTags.SCAN_BUTTON),
|
||||
showProgress = btnScanStateInProgress,
|
||||
onClick = onScanButtonClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CreateNewWalletButton(onClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||
StoriesButton(
|
||||
modifier = modifier,
|
||||
text = stringResourceSafe(id = R.string.home_button_create_new_wallet),
|
||||
useDarkerColors = false,
|
||||
onClick = onClick,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AddExistingWalletButton(onClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||
StoriesButton(
|
||||
modifier = modifier,
|
||||
text = stringResourceSafe(id = R.string.home_button_add_existing_wallet),
|
||||
useDarkerColors = true,
|
||||
onClick = onClick,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ScanCardButton(showProgress: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||
StoriesButton(
|
||||
modifier = modifier,
|
||||
text = stringResourceSafe(id = R.string.home_button_scan),
|
||||
useDarkerColors = true,
|
||||
icon = TangemButtonIconPosition.End(iconResId = R.drawable.ic_tangem_24),
|
||||
onClick = onClick,
|
||||
showProgress = showProgress,
|
||||
)
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun HomeButtonsV2Preview(@PreviewParameter(HomeButtonsV2ParameterProvider::class) state: HomeButtonsV2State) {
|
||||
TangemThemePreview {
|
||||
Box(
|
||||
modifier = Modifier.background(Color.Black),
|
||||
) {
|
||||
HomeButtonsV2(
|
||||
btnScanStateInProgress = state.btnScanStateInProgress,
|
||||
onCreateNewWalletButtonClick = {},
|
||||
onAddExistingWalletButtonClick = {},
|
||||
onScanButtonClick = {},
|
||||
modifier = Modifier.padding(all = TangemTheme.dimens.spacing16),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class HomeButtonsV2ParameterProvider : CollectionPreviewParameterProvider<HomeButtonsV2State>(
|
||||
collection = listOf(
|
||||
HomeButtonsV2State(
|
||||
btnScanStateInProgress = false,
|
||||
),
|
||||
HomeButtonsV2State(
|
||||
btnScanStateInProgress = true,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
private data class HomeButtonsV2State(
|
||||
val btnScanStateInProgress: Boolean,
|
||||
)
|
||||
// endregion Preview
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
package com.tangem.tap.features.home.compose.views
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.wallet.R
|
||||
|
||||
@Composable
|
||||
internal fun SearchCurrenciesButton(onClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||
StoriesButton(
|
||||
modifier = modifier,
|
||||
text = stringResourceSafe(id = R.string.common_search_tokens),
|
||||
icon = TangemButtonIconPosition.Start(R.drawable.ic_search_24),
|
||||
showProgress = false,
|
||||
useDarkerColors = true,
|
||||
onClick = onClick,
|
||||
)
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun SearchCurrenciesButtonPreview() {
|
||||
TangemThemePreview {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.background(color = Color.Black)
|
||||
.padding(all = TangemTheme.dimens.spacing16),
|
||||
) {
|
||||
SearchCurrenciesButton(modifier = Modifier.fillMaxWidth(), onClick = {})
|
||||
}
|
||||
}
|
||||
}
|
||||
// endregion Preview
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
package com.tangem.tap.features.home.compose.views
|
||||
|
||||
import androidx.compose.material3.ButtonColors
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButton
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
|
||||
import com.tangem.core.ui.res.TangemColorPalette
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
@Composable
|
||||
internal fun StoriesButton(
|
||||
text: String,
|
||||
useDarkerColors: Boolean,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
icon: TangemButtonIconPosition = TangemButtonIconPosition.None,
|
||||
showProgress: Boolean = false,
|
||||
) {
|
||||
TangemButton(
|
||||
modifier = modifier,
|
||||
text = text,
|
||||
icon = icon,
|
||||
colors = if (useDarkerColors) DarkerButtonColors else LighterButtonColors,
|
||||
showProgress = showProgress,
|
||||
enabled = true,
|
||||
shape = TangemTheme.shapes.roundedCornersXMedium,
|
||||
textStyle = TangemTheme.typography.subtitle1,
|
||||
iconPadding = when (icon) {
|
||||
is TangemButtonIconPosition.Start -> TangemTheme.dimens.spacing4
|
||||
is TangemButtonIconPosition.End,
|
||||
is TangemButtonIconPosition.None,
|
||||
-> TangemTheme.dimens.spacing8
|
||||
},
|
||||
onClick = onClick,
|
||||
)
|
||||
}
|
||||
|
||||
private val LighterButtonColors: ButtonColors = ButtonColors(
|
||||
containerColor = TangemColorPalette.Light4,
|
||||
contentColor = TangemColorPalette.Dark6,
|
||||
disabledContainerColor = TangemColorPalette.Dark5,
|
||||
disabledContentColor = TangemColorPalette.Dark6,
|
||||
)
|
||||
|
||||
private val DarkerButtonColors: ButtonColors = ButtonColors(
|
||||
containerColor = TangemColorPalette.Dark4,
|
||||
contentColor = TangemColorPalette.White,
|
||||
disabledContainerColor = TangemColorPalette.Dark4,
|
||||
disabledContentColor = TangemColorPalette.White,
|
||||
)
|
||||
|
|
@ -1,112 +0,0 @@
|
|||
package com.tangem.tap.features.home.compose.views
|
||||
|
||||
import android.provider.Settings
|
||||
import androidx.compose.animation.core.Animatable
|
||||
import androidx.compose.animation.core.LinearEasing
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.components.SpacerW4
|
||||
import com.tangem.core.ui.res.TangemColorPalette
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
private const val STORIES_ANIMATION_SPEED_ZERO_DURATION = 3000L
|
||||
|
||||
@Composable
|
||||
fun StoriesProgressBar(
|
||||
steps: Int,
|
||||
currentStep: Int,
|
||||
paused: Boolean = false,
|
||||
stepDuration: Int = 8_000,
|
||||
onStepFinish: () -> Unit = {},
|
||||
) {
|
||||
val progress = remember(currentStep) { Animatable(initialValue = 0f) }
|
||||
|
||||
val context = LocalContext.current
|
||||
val animatorSpeed = Settings.Global.getFloat(
|
||||
context.contentResolver,
|
||||
Settings.Global.ANIMATOR_DURATION_SCALE,
|
||||
1f,
|
||||
)
|
||||
|
||||
LaunchedEffect(paused, currentStep, animatorSpeed) {
|
||||
if (paused) {
|
||||
progress.stop()
|
||||
} else {
|
||||
if (animatorSpeed == 0f) {
|
||||
progress.snapTo(1f)
|
||||
delay(STORIES_ANIMATION_SPEED_ZERO_DURATION)
|
||||
} else {
|
||||
progress.animateTo(
|
||||
targetValue = 1f,
|
||||
animationSpec = tween(
|
||||
durationMillis = (stepDuration * (1f - progress.value)).toInt(),
|
||||
easing = LinearEasing,
|
||||
),
|
||||
)
|
||||
progress.snapTo(0f)
|
||||
}
|
||||
onStepFinish()
|
||||
}
|
||||
}
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
start = TangemTheme.dimens.spacing16,
|
||||
end = TangemTheme.dimens.spacing16,
|
||||
top = TangemTheme.dimens.spacing16,
|
||||
),
|
||||
) {
|
||||
for (index in 0..steps) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.height(TangemTheme.dimens.size2)
|
||||
.weight(1f)
|
||||
.clip(RoundedCornerShape(TangemTheme.dimens.radius2))
|
||||
.background(TangemColorPalette.White.copy(alpha = .2f)),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(TangemTheme.dimens.radius2))
|
||||
.background(TangemColorPalette.White)
|
||||
.fillMaxHeight()
|
||||
.let {
|
||||
when (index) {
|
||||
currentStep -> it.fillMaxWidth(progress.value)
|
||||
in 0..currentStep -> it.fillMaxWidth(fraction = 1f)
|
||||
else -> it
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
if (index != steps) {
|
||||
SpacerW4()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun StoriesProgressBarPreview() {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.background(TangemColorPalette.Black)
|
||||
.padding(vertical = TangemTheme.dimens.spacing16),
|
||||
) {
|
||||
StoriesProgressBar(steps = 5, currentStep = 3, paused = false)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
package com.tangem.tap.features.home.redux
|
||||
|
||||
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 {
|
||||
|
||||
val firstStory: Stories get() = stories[0]
|
||||
|
||||
fun stepOf(story: Stories): Int = stories.indexOf(story)
|
||||
}
|
||||
|
||||
enum class Stories(val duration: Int = 6000) {
|
||||
TangemIntro,
|
||||
RevolutionaryWallet,
|
||||
UltraSecureBackup,
|
||||
Currencies,
|
||||
Web3,
|
||||
WalletForEveryone,
|
||||
}
|
||||
|
||||
/**
|
||||
* For FCA restriction stories
|
||||
*/
|
||||
fun getRestrictedStories(): List<Stories> {
|
||||
return Stories.entries.filterNot { it == Stories.Currencies }
|
||||
}
|
||||
|
|
@ -4,21 +4,12 @@ import android.content.Intent
|
|||
import android.nfc.NfcAdapter
|
||||
import android.nfc.Tag
|
||||
import android.os.Build
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.features.home.redux.HomeAction
|
||||
import com.tangem.tap.features.intentHandler.IntentHandler
|
||||
import com.tangem.tap.features.intentHandler.AffectsNavigation
|
||||
import com.tangem.tap.features.welcome.redux.WelcomeAction
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import com.tangem.common.routing.entity.InitScreenLaunchMode
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class BackgroundScanIntentHandler(
|
||||
private val hasSavedUserWalletsProvider: () -> Boolean,
|
||||
private val scope: CoroutineScope,
|
||||
) : IntentHandler, AffectsNavigation {
|
||||
class BackgroundScanIntentHandler {
|
||||
|
||||
private val nfcActions = arrayOf(
|
||||
NfcAdapter.ACTION_NDEF_DISCOVERED,
|
||||
|
|
@ -26,8 +17,15 @@ class BackgroundScanIntentHandler(
|
|||
NfcAdapter.ACTION_TAG_DISCOVERED,
|
||||
)
|
||||
|
||||
override fun handleIntent(intent: Intent?, isFromForeground: Boolean): Boolean {
|
||||
if (isFromForeground) return true
|
||||
fun getInitScreenLaunchMode(intent: Intent?): InitScreenLaunchMode {
|
||||
return if (shouldOpenScanCard(intent)) {
|
||||
InitScreenLaunchMode.WithCardScan
|
||||
} else {
|
||||
InitScreenLaunchMode.Standard
|
||||
}
|
||||
}
|
||||
|
||||
private fun shouldOpenScanCard(intent: Intent?): Boolean {
|
||||
if (intent == null || intent.action !in nfcActions) return false
|
||||
|
||||
val tag: Tag? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
|
|
@ -36,15 +34,9 @@ class BackgroundScanIntentHandler(
|
|||
@Suppress("DEPRECATION")
|
||||
intent.getParcelableExtra(NfcAdapter.EXTRA_TAG)
|
||||
}
|
||||
if (tag == null) return false
|
||||
|
||||
intent.action = null
|
||||
if (hasSavedUserWalletsProvider.invoke()) {
|
||||
store.dispatchOnMain(WelcomeAction.ProceedWithCard)
|
||||
} else {
|
||||
store.dispatchOnMain(HomeAction.ReadCard(scope = scope))
|
||||
}
|
||||
|
||||
return true
|
||||
return tag != null
|
||||
}
|
||||
}
|
||||
|
|
@ -4,8 +4,8 @@ import android.content.Intent
|
|||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.removePrefixOrNull
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
|
||||
import com.tangem.tap.features.intentHandler.IntentHandler
|
||||
import com.tangem.tap.features.intentHandler.AffectsNavigation
|
||||
import com.tangem.tap.features.intentHandler.IntentHandler
|
||||
import com.tangem.tap.store
|
||||
import timber.log.Timber
|
||||
import java.net.URLDecoder
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.tap.features.welcome.component
|
||||
|
||||
import com.tangem.common.routing.entity.InitScreenLaunchMode
|
||||
import com.tangem.common.routing.entity.SerializableIntent
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
|
|
@ -7,6 +8,7 @@ import com.tangem.core.ui.decompose.ComposableContentComponent
|
|||
interface WelcomeComponent : ComposableContentComponent {
|
||||
|
||||
data class Params(
|
||||
val launchMode: InitScreenLaunchMode,
|
||||
val intent: SerializableIntent?,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.tap.features.welcome.model
|
||||
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.common.routing.entity.InitScreenLaunchMode
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
|
|
@ -44,10 +45,9 @@ internal class WelcomeModel @Inject constructor(
|
|||
subscribeToStoreChanges()
|
||||
initGlobalState()
|
||||
|
||||
val welcomeAction = if (params.intent != null) {
|
||||
WelcomeAction.ProceedWithIntent(params.intent.toIntent())
|
||||
} else {
|
||||
WelcomeAction.ProceedWithBiometrics()
|
||||
val welcomeAction = when (params.launchMode) {
|
||||
is InitScreenLaunchMode.WithCardScan -> WelcomeAction.ProceedWithCard
|
||||
is InitScreenLaunchMode.Standard -> WelcomeAction.ProceedWithBiometrics(params.intent?.toIntent())
|
||||
}
|
||||
|
||||
store.dispatch(welcomeAction)
|
||||
|
|
|
|||
|
|
@ -20,10 +20,8 @@ import com.tangem.tap.*
|
|||
import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.features.intentHandler.handlers.BackgroundScanIntentHandler
|
||||
import com.tangem.tap.features.intentHandler.handlers.WalletConnectLinkIntentHandler
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Middleware
|
||||
import timber.log.Timber
|
||||
|
|
@ -44,7 +42,7 @@ internal class WelcomeMiddleware {
|
|||
private fun handleAction(action: WelcomeAction, state: WelcomeState) {
|
||||
mainScope.launch {
|
||||
when (action) {
|
||||
is WelcomeAction.ProceedWithIntent -> proceedWithIntent(action.intent, scope = this)
|
||||
is WelcomeAction.ProceedWithIntent -> proceedWithIntent(action.intent)
|
||||
is WelcomeAction.ProceedWithBiometrics -> proceedWithBiometrics(
|
||||
afterUnlockIntent = action.afterUnlockIntent ?: state.intent,
|
||||
)
|
||||
|
|
@ -55,7 +53,7 @@ internal class WelcomeMiddleware {
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun proceedWithIntent(initialIntent: Intent, scope: CoroutineScope) {
|
||||
private suspend fun proceedWithIntent(initialIntent: Intent) {
|
||||
Timber.d(
|
||||
"""
|
||||
Proceeding with intent
|
||||
|
|
@ -63,15 +61,12 @@ internal class WelcomeMiddleware {
|
|||
""".trimIndent(),
|
||||
)
|
||||
|
||||
val handler = BackgroundScanIntentHandler(
|
||||
scope = scope,
|
||||
hasSavedUserWalletsProvider = { true },
|
||||
)
|
||||
val isBackgroundScanHandled = handler.handleIntent(initialIntent, isFromForeground = false)
|
||||
val hasUncompletedBackup = backupService.hasIncompletedBackup
|
||||
|
||||
if (!isBackgroundScanHandled && !hasUncompletedBackup) {
|
||||
if (!hasUncompletedBackup) {
|
||||
store.dispatchWithMain(WelcomeAction.ProceedWithBiometrics(initialIntent))
|
||||
} else {
|
||||
store.dispatchWithMain(WelcomeAction.ProceedWithCard)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ import com.tangem.tap.features.details.ui.cardsettings.coderecovery.api.AccessCo
|
|||
import com.tangem.tap.features.details.ui.resetcard.api.ResetCardComponent
|
||||
import com.tangem.tap.features.details.ui.securitymode.api.SecurityModeComponent
|
||||
import com.tangem.tap.features.details.ui.walletconnect.api.WalletConnectComponent
|
||||
import com.tangem.tap.features.home.api.HomeComponent
|
||||
import com.tangem.features.home.api.HomeComponent
|
||||
import com.tangem.tap.features.welcome.component.WelcomeComponent
|
||||
import com.tangem.tap.routing.component.RoutingComponent.Child
|
||||
import dagger.hilt.android.scopes.ActivityScoped
|
||||
|
|
@ -131,6 +131,7 @@ internal class ChildFactory @Inject constructor(
|
|||
createComponentChild(
|
||||
context = context,
|
||||
params = WelcomeComponent.Params(
|
||||
launchMode = route.launchMode,
|
||||
intent = route.intent,
|
||||
),
|
||||
componentFactory = welcomeComponentFactory,
|
||||
|
|
@ -288,7 +289,7 @@ internal class ChildFactory @Inject constructor(
|
|||
is AppRoute.Home -> {
|
||||
createComponentChild(
|
||||
context = context,
|
||||
params = Unit,
|
||||
params = HomeComponent.Params(route.launchMode),
|
||||
componentFactory = homeComponentFactory,
|
||||
)
|
||||
}
|
||||
|
|
@ -380,7 +381,7 @@ internal class ChildFactory @Inject constructor(
|
|||
is AppRoute.PushNotification -> {
|
||||
createComponentChild(
|
||||
context = context,
|
||||
params = PushNotificationsComponent.Params.Route(AppRoute.Home),
|
||||
params = PushNotificationsComponent.Params.Route(AppRoute.Home()),
|
||||
componentFactory = pushNotificationsComponentFactory,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue