Updated on 2026-08-14

This commit is contained in:
Tangem 2022-11-19 16:33:26 +05:00
commit 8843c047f8
105 changed files with 1749 additions and 1200 deletions

View file

@ -3,8 +3,9 @@ package com.tangem.tap.features.details.redux
import com.tangem.common.CompletionResult
import com.tangem.common.core.TangemSdkError
import com.tangem.domain.common.TapWorkarounds.isTangemTwins
import com.tangem.tap.common.analytics.AnalyticsAnOld
import com.tangem.tap.common.analytics.AnalyticsParamAnOld
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.Settings
import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.extensions.dispatchNotification
import com.tangem.tap.common.extensions.dispatchOnMain
@ -119,19 +120,16 @@ class DetailsMiddleware {
val card = store.state.detailsState.cardSettingsState?.card ?: return
scope.launch {
val result = tangemSdkManager.resetToFactorySettings(card)
when (result) {
is CompletionResult.Success -> {
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Home))
}
is CompletionResult.Failure -> {
(result.error as? TangemSdkError)?.let { error ->
store.state.globalState.analyticsHandler.handleCardSdkErrorEvent(
error,
AnalyticsAnOld.ActionToLog.PurgeWallet,
card = store.state.detailsState.scanResponse?.card,
)
}
when (result) {
is CompletionResult.Success -> {
Analytics.send(Settings.CardSettings.FactoryResetFinished())
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Home))
}
is CompletionResult.Failure -> {
(result.error as? TangemSdkError)?.let { error ->
Analytics.send(Settings.CardSettings.FactoryResetFinished(error))
}
}
}
}
}
@ -156,28 +154,20 @@ class DetailsMiddleware {
SecurityOption.LongTap -> tangemSdkManager.setLongTap(cardId)
SecurityOption.PassCode -> tangemSdkManager.setPasscode(cardId)
SecurityOption.AccessCode -> tangemSdkManager.setAccessCode(cardId)
else -> null
else -> return@launch
}
withContext(Dispatchers.Main) {
val paramValue = AnalyticsParam.SecurityMode.from(selectedOption)
when (result) {
is CompletionResult.Success -> {
selectedOption?.let {
store.dispatch(GlobalAction.UpdateSecurityOptions(it))
}
Analytics.send(Settings.CardSettings.SecurityModeChanged(paramValue))
store.dispatch(GlobalAction.UpdateSecurityOptions(selectedOption))
store.dispatch(NavigationAction.PopBackTo())
store.dispatch(DetailsAction.ManageSecurity.SaveChanges.Success)
}
is CompletionResult.Failure -> {
(result.error as? TangemSdkError)?.let { error ->
store.state.globalState.analyticsHandler.handleCardSdkErrorEvent(
error = error,
action = AnalyticsAnOld.ActionToLog.ChangeSecOptions,
params = mapOf(
AnalyticsParamAnOld.NEW_SECURITY_OPTION to
(selectedOption?.name ?: ""),
),
card = store.state.detailsState.scanResponse?.card,
)
Analytics.send(Settings.CardSettings.SecurityModeChanged(paramValue, error))
}
store.dispatch(DetailsAction.ManageSecurity.SaveChanges.Failure)
}
@ -190,7 +180,10 @@ class DetailsMiddleware {
is DetailsAction.ManageSecurity.ChangeAccessCode -> {
val card = store.state.detailsState.cardSettingsState?.card ?: return
scope.launch {
tangemSdkManager.setAccessCode(card.cardId)
when (tangemSdkManager.setAccessCode(card.cardId)) {
is CompletionResult.Success -> Analytics.send(Settings.CardSettings.UserCodeChanged())
is CompletionResult.Failure -> {}
}
}
}
else -> { /* no-op */

View file

@ -2,6 +2,9 @@ package com.tangem.tap.features.details.ui.cardsettings
import com.tangem.domain.common.TapWorkarounds.isTangemTwins
import com.tangem.domain.common.getTwinCardIdForUser
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.Settings
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.features.details.redux.CardSettingsState
import com.tangem.tap.features.details.redux.DetailsAction
@ -59,12 +62,15 @@ class CardSettingsViewModel(private val store: Store<AppState>) {
private fun handleClickingItem(item: CardInfo) {
when (item) {
is CardInfo.ChangeAccessCode -> {
Analytics.send(Settings.CardSettings.ButtonChangeUserCode(AnalyticsParam.UserCode.AccessCode))
store.dispatch(DetailsAction.ManageSecurity.ChangeAccessCode)
}
is CardInfo.ResetToFactorySettings -> {
Analytics.send(Settings.CardSettings.ButtonFactoryReset())
store.dispatch(DetailsAction.ResetToFactory.Start)
}
is CardInfo.SecurityMode -> {
Analytics.send(Settings.CardSettings.ButtonChangeSecurityMode())
store.dispatch(DetailsAction.ManageSecurity.OpenSecurity)
}
else -> {}

View file

@ -10,6 +10,8 @@ import androidx.compose.ui.platform.ComposeView
import androidx.fragment.app.Fragment
import androidx.transition.TransitionInflater
import com.google.accompanist.appcompattheme.AppCompatTheme
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.events.Settings
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.features.details.redux.DetailsState
import com.tangem.tap.store
@ -24,7 +26,7 @@ class DetailsFragment : Fragment(), StoreSubscriber<DetailsState> {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Analytics.send(Settings.ScreenOpened())
val inflater = TransitionInflater.from(requireContext())
enterTransition = inflater.inflateTransition(android.R.transition.fade)
exitTransition = inflater.inflateTransition(android.R.transition.fade)

View file

@ -162,8 +162,8 @@ fun DetailsItem(
@Composable
fun TangemSocialAccounts(
links: List<TangemLink>,
onSocialNetworkClick: (String) -> Unit,
links: List<SocialNetworkLink>,
onSocialNetworkClick: (SocialNetworkLink) -> Unit,
modifier: Modifier = Modifier,
) {
LazyRow(
@ -171,11 +171,11 @@ fun TangemSocialAccounts(
) {
items(links) {
Icon(
painter = painterResource(id = it.iconRes),
painter = painterResource(id = it.network.iconRes),
contentDescription = "",
modifier = modifier
.padding(8.dp)
.clickable { onSocialNetworkClick(it.url) },
.clickable { onSocialNetworkClick(it) },
tint = colorResource(id = R.color.icon_informative),
)
}

View file

@ -4,11 +4,11 @@ import com.tangem.wallet.R
data class DetailsScreenState(
val elements: List<SettingsElement>,
val tangemLinks: List<TangemLink>,
val tangemLinks: List<SocialNetworkLink>,
val tangemVersion: String,
val appCurrency: String,
val onItemsClick: (SettingsElement) -> Unit,
val onSocialNetworkClick: (String) -> Unit,
val onSocialNetworkClick: (SocialNetworkLink) -> Unit,
) {
val appNameRes: Int = R.string.app_name
}
@ -30,28 +30,38 @@ enum class SettingsElement(
;
}
data class TangemLink(
val iconRes: Int,
data class SocialNetworkLink(
val network: SocialNetwork,
val url: String,
)
sealed class SocialNetwork(val id: String, val iconRes: Int) {
object Telegram : SocialNetwork("Telegram", R.drawable.ic_telegram)
object Twitter : SocialNetwork("Twitter", R.drawable.ic_twitter)
object Facebook : SocialNetwork("Facebook", R.drawable.ic_facebook)
object Instagram : SocialNetwork("Instagram", R.drawable.ic_instagram)
object GitHub : SocialNetwork("GitHub", R.drawable.ic_github)
object Youtube : SocialNetwork("Youtube", R.drawable.ic_youtube)
object LinkedIn : SocialNetwork("LinkedIn", R.drawable.ic_linkedin)
}
object TangemSocialAccounts {
val accountsEn: List<TangemLink> = listOf(
TangemLink(R.drawable.ic_telegram, "https://t.me/TangemCards"),
TangemLink(R.drawable.ic_twitter, "https://twitter.com/tangem"),
TangemLink(R.drawable.ic_facebook, "https://m.facebook.com/TangemCards/"),
TangemLink(R.drawable.ic_instagram, "https://instagram.com/tangemcards"),
TangemLink(R.drawable.ic_github, "https://github.com/tangem"),
TangemLink(R.drawable.ic_youtube, "https://youtube.com/channel/UCFGwLS7yggzVkP6ozte0m1w"),
TangemLink(R.drawable.ic_linkedin, "https://www.linkedin.com/company/tangem"),
val accountsEn: List<SocialNetworkLink> = listOf(
SocialNetworkLink(SocialNetwork.Telegram, "https://t.me/TangemCards"),
SocialNetworkLink(SocialNetwork.Twitter, "https://twitter.com/tangem"),
SocialNetworkLink(SocialNetwork.Facebook, "https://m.facebook.com/TangemCards/"),
SocialNetworkLink(SocialNetwork.Instagram, "https://instagram.com/tangemcards"),
SocialNetworkLink(SocialNetwork.GitHub, "https://github.com/tangem"),
SocialNetworkLink(SocialNetwork.Youtube, "https://youtube.com/channel/UCFGwLS7yggzVkP6ozte0m1w"),
SocialNetworkLink(SocialNetwork.LinkedIn, "https://www.linkedin.com/company/tangem"),
)
val accountsRu: List<TangemLink> = listOf(
TangemLink(R.drawable.ic_telegram, "https://t.me/tangem_ru"),
TangemLink(R.drawable.ic_twitter, "https://twitter.com/tangem"),
TangemLink(R.drawable.ic_facebook, "https://m.facebook.com/TangemCards/"),
TangemLink(R.drawable.ic_instagram, "https://instagram.com/tangemcards"),
TangemLink(R.drawable.ic_github, "https://github.com/tangem"),
TangemLink(R.drawable.ic_youtube, "https://youtube.com/channel/UCFGwLS7yggzVkP6ozte0m1w"),
TangemLink(R.drawable.ic_linkedin, "https://www.linkedin.com/company/tangem"),
val accountsRu: List<SocialNetworkLink> = listOf(
SocialNetworkLink(SocialNetwork.Telegram, "https://t.me/tangem_ru"),
SocialNetworkLink(SocialNetwork.Twitter, "https://twitter.com/tangem"),
SocialNetworkLink(SocialNetwork.Facebook, "https://m.facebook.com/TangemCards/"),
SocialNetworkLink(SocialNetwork.Instagram, "https://instagram.com/tangemcards"),
SocialNetworkLink(SocialNetwork.GitHub, "https://github.com/tangem"),
SocialNetworkLink(SocialNetwork.Youtube, "https://youtube.com/channel/UCFGwLS7yggzVkP6ozte0m1w"),
SocialNetworkLink(SocialNetwork.LinkedIn, "https://www.linkedin.com/company/tangem"),
)
}

View file

@ -2,6 +2,8 @@ package com.tangem.tap.features.details.ui.details
import com.tangem.domain.common.TapWorkarounds.isSaltPay
import com.tangem.domain.common.TapWorkarounds.isStart2Coin
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.events.Settings
import com.tangem.tap.common.feedback.FeedbackEmail
import com.tangem.tap.common.feedback.SupportInfo
import com.tangem.tap.common.redux.AppState
@ -51,8 +53,9 @@ class DetailsViewModel(private val store: Store<AppState>) {
)
}
private fun handleSocialNetworkClick(url: String) {
store.dispatch(NavigationAction.OpenUrl(url))
private fun handleSocialNetworkClick(link: SocialNetworkLink) {
Analytics.send(Settings.ButtonSocialNetwork(link.network))
store.dispatch(NavigationAction.OpenUrl(link.url))
}
private fun handleClickingSettingsItem(item: SettingsElement) {
@ -61,21 +64,26 @@ class DetailsViewModel(private val store: Store<AppState>) {
store.dispatch(NavigationAction.NavigateTo(AppScreen.WalletConnectSessions))
}
SettingsElement.Chat -> {
Analytics.send(Settings.ButtonChat())
store.dispatch(GlobalAction.OpenChat(SupportInfo()))
}
SettingsElement.SendFeedback -> {
Analytics.send(Settings.ButtonSendFeedback())
store.dispatch(GlobalAction.SendEmail(FeedbackEmail()))
}
SettingsElement.CardSettings -> {
Analytics.send(Settings.ButtonCardSettings())
store.dispatch(NavigationAction.NavigateTo(AppScreen.CardSettings))
}
SettingsElement.AppCurrency -> {
store.dispatch(WalletAction.AppCurrencyAction.ChooseAppCurrency)
}
SettingsElement.AppSettings -> {
Analytics.send(Settings.ButtonAppSettings())
store.dispatch(NavigationAction.NavigateTo(AppScreen.AppSettings)) //TODO: To be available later
}
SettingsElement.LinkMoreCards -> {
Analytics.send(Settings.ButtonCreateBackup())
store.dispatch(DetailsAction.CreateBackup)
}
SettingsElement.TermsOfService -> {
@ -90,7 +98,7 @@ class DetailsViewModel(private val store: Store<AppState>) {
}
}
private fun getSocialLinks(): List<TangemLink> {
private fun getSocialLinks(): List<SocialNetworkLink> {
val locale = LocaleRegionProvider().getRegion()
return if (locale.lowercase() == RUSSIA_COUNTRY_CODE) {
TangemSocialAccounts.accountsRu

View file

@ -30,6 +30,8 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.events.Settings
import com.tangem.tap.common.compose.TangemTypography
import com.tangem.tap.common.extensions.getFromClipboard
import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold
@ -53,7 +55,12 @@ fun WalletConnectScreen(
},
fab = {
if (!state.isLoading) {
AddSessionFab(onAddSession = { state.onAddSession(context.getFromClipboard()?.toString()) })
AddSessionFab(
onAddSession = {
Analytics.send(Settings.ButtonStartWalletConnectSession())
state.onAddSession(context.getFromClipboard()?.toString())
},
)
}
},
titleRes = R.string.wallet_connect_title,
@ -147,7 +154,10 @@ private fun WalletConnectSessions(
modifier = modifier.weight(1f),
)
IconButton(
onClick = { state.onRemoveSession(session.sessionId) },
onClick = {
Analytics.send(Settings.ButtonStopWalletConnectSession())
state.onRemoveSession(session.sessionId)
},
) {
Icon(
painter = painterResource(id = R.drawable.ic_cross_rounded_24),

View file

@ -11,6 +11,8 @@ import androidx.compose.ui.platform.ComposeView
import androidx.core.view.WindowInsetsControllerCompat
import androidx.fragment.app.Fragment
import com.google.accompanist.appcompattheme.AppCompatTheme
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.events.IntroductionProcess
import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.features.home.compose.StoriesScreen
@ -28,6 +30,7 @@ class HomeFragment : Fragment(), StoreSubscriber<HomeState> {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Analytics.send(IntroductionProcess.ScreenOpened())
store.dispatch(HomeAction.Init)
}
@ -81,9 +84,16 @@ class HomeFragment : Fragment(), StoreSubscriber<HomeState> {
private fun ScreenContent() {
StoriesScreen(
homeState,
onScanButtonClick = { store.dispatch(HomeAction.ReadCard) },
onShopButtonClick = { store.dispatch(HomeAction.GoToShop(store.state.globalState.userCountryCode)) },
onScanButtonClick = {
Analytics.send(IntroductionProcess.ButtonScanCard())
store.dispatch(HomeAction.ReadCard)
},
onShopButtonClick = {
Analytics.send(IntroductionProcess.ButtonBuyCards())
store.dispatch(HomeAction.GoToShop(store.state.globalState.userCountryCode))
},
onSearchTokensClick = {
Analytics.send(IntroductionProcess.ButtonTokensList())
store.dispatch(NavigationAction.NavigateTo(AppScreen.AddTokens))
store.dispatch(TokensAction.AllowToAddTokens(false))
store.dispatch(TokensAction.LoadCurrencies())

View file

@ -9,10 +9,9 @@ import com.tangem.domain.common.extensions.withMainContext
import com.tangem.operations.backup.BackupService
import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE
import com.tangem.tap.backupService
import com.tangem.tap.common.analytics.AnalyticsEventAnOld
import com.tangem.tap.common.analytics.AnalyticsParamAnOld
import com.tangem.tap.common.analytics.GetCardSourceParamsAnOld
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.events.IntroductionProcess
import com.tangem.tap.common.analytics.paramsInterceptor.BatchIdParamsInterceptor
import com.tangem.tap.common.entities.IndeterminateProgressButton
import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.extensions.dispatchOpenUrl
@ -105,10 +104,6 @@ private fun handleHomeAction(appState: () -> AppState?, action: Action, dispatch
RUSSIA_COUNTRY_CODE, BELARUS_COUNTRY_CODE -> store.dispatchOpenUrl(BUY_WALLET_URL)
else -> store.dispatch(NavigationAction.NavigateTo(AppScreen.Shop))
}
store.state.globalState.analyticsHandler.handleAnalyticsEvent(
event = AnalyticsEventAnOld.GET_CARD,
params = mapOf(AnalyticsParamAnOld.SOURCE.param to GetCardSourceParamsAnOld.WELCOME.param),
)
}
}
}
@ -158,11 +153,12 @@ private fun showDisclaimerIfNeed(scanResponse: ScanResponse, nextHandler: (ScanR
}
private fun onScanSuccess(scanResponse: ScanResponse) {
Analytics.send(IntroductionProcess.CardWasScanned())
val globalState = store.state.globalState
val tapWalletManager = globalState.tapWalletManager
tapWalletManager.updateConfigManager(scanResponse)
globalState.analyticsHandler.attachToAllEvents(AnalyticsParam.BatchId, scanResponse.card.batchId)
Analytics.addParamsInterceptor(BatchIdParamsInterceptor(scanResponse.card.batchId))
store.dispatch(TwinCardsAction.IfTwinsPrepareState(scanResponse))
@ -178,7 +174,7 @@ private fun onScanSuccess(scanResponse: ScanResponse) {
val isOnboardingCase = result.data
if (isOnboardingCase) {
store.dispatch(GlobalAction.Onboarding.Start(scanResponse, canSkipBackup = false))
store.dispatch(OnboardingSaltPayAction.Init.SetDependencies(manager, config))
store.dispatch(OnboardingSaltPayAction.SetDependencies(manager, config))
store.dispatch(OnboardingSaltPayAction.Update)
navigateTo(AppScreen.OnboardingWallet)
} else {

View file

@ -5,6 +5,8 @@ import android.os.Bundle
import android.view.LayoutInflater
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.events.Token
import com.tangem.tap.common.extensions.copyToClipboard
import com.tangem.tap.common.extensions.dispatchDialogHide
import com.tangem.tap.common.extensions.dispatchShare
@ -21,7 +23,7 @@ import com.tangem.wallet.databinding.DialogOnboardingAddressInfoBinding
*/
class AddressInfoBottomSheetDialog(
private val stateDialog: AppDialog.AddressInfoDialog,
context: Context
context: Context,
) : BottomSheetDialog(context) {
var binding: DialogOnboardingAddressInfoBinding? = null
@ -40,6 +42,7 @@ class AddressInfoBottomSheetDialog(
override fun show() {
super.show()
Analytics.send(Token.Recieve.ScreenOpened())
showData(data = stateDialog.addressData)
}
@ -51,10 +54,12 @@ class AddressInfoBottomSheetDialog(
imvQrCode.setImageBitmap(data.qrCode)
tvAddress.text = data.address
btnFlCopyAddress.setOnClickListener {
Analytics.send(Token.Recieve.ButtonCopyAddress())
context.copyToClipboard(data.address)
store.dispatchToastNotification(R.string.copy_toast_msg)
}
btnFlShare.setOnClickListener {
Analytics.send(Token.Recieve.ButtonShareAddress())
store.dispatchShare(data.shareUrl)
}
tvReceiveMessage.text = getQRReceiveMessage(tvReceiveMessage.context, stateDialog.currency)
@ -67,7 +72,7 @@ fun getQRReceiveMessage(context: Context, currency: Currency): String {
context.getString(
R.string.address_qr_code_message_format,
currency.blockchain.fullName,
currency.currencySymbol
currency.currencySymbol,
)
}
is Currency.Token -> {
@ -75,7 +80,7 @@ fun getQRReceiveMessage(context: Context, currency: Currency): String {
R.string.address_qr_code_message_token_format,
currency.token.name,
currency.currencySymbol,
currency.blockchain.fullName
currency.blockchain.fullName,
)
}
}

View file

@ -14,15 +14,16 @@ class OnboardingHelper {
fun isOnboardingCase(response: ScanResponse): Boolean {
val cardInfoStorage = preferencesStorage.usedCardsPrefStorage
val cardId = response.card.cardId
return when {
response.isTangemTwins() -> {
if (!response.twinsIsTwinned()) {
true
} else {
cardInfoStorage.activationIsStarted(response.card.cardId)
cardInfoStorage.isActivationInProgress(cardId)
}
}
response.card.hasWallets() -> cardInfoStorage.activationIsStarted(response.card.cardId)
response.card.hasWallets() -> cardInfoStorage.isActivationInProgress(cardId)
else -> true
}
}

View file

@ -67,8 +67,10 @@ class OnboardingManager(
}
}
return balance.copy(currency = Currency.Blockchain(
walletManager.wallet.blockchain, walletManager.wallet.publicKey.derivationPath?.rawPath)
return balance.copy(
currency = Currency.Blockchain(
walletManager.wallet.blockchain, walletManager.wallet.publicKey.derivationPath?.rawPath,
),
)
}
@ -79,6 +81,14 @@ class OnboardingManager(
fun activationFinished(cardId: String) {
usedCardsPrefStorage.activationFinished(cardId)
}
fun isActivationFinished(cardId: String): Boolean {
return usedCardsPrefStorage.isActivationFinished(cardId)
}
fun isActivationStarted(cardId: String): Boolean {
return usedCardsPrefStorage.isActivationStarted(cardId)
}
}
data class OnboardingWalletBalance(
@ -98,23 +108,23 @@ data class OnboardingWalletBalance(
companion object {
fun error(error: TapError): OnboardingWalletBalance = OnboardingWalletBalance(
state = ProgressState.Error,
error = error
error = error,
)
fun criticalError(error: TapError): OnboardingWalletBalance = OnboardingWalletBalance(
state = ProgressState.Error,
criticalError = error
criticalError = error,
)
fun loading(value: BigDecimal = BigDecimal.ZERO): OnboardingWalletBalance = OnboardingWalletBalance(
value,
state = ProgressState.Loading
state = ProgressState.Loading,
)
fun done(value: BigDecimal, hasTransactions: Boolean): OnboardingWalletBalance = OnboardingWalletBalance(
value,
hasIncomingTransaction = hasTransactions,
state = ProgressState.Done
state = ProgressState.Done,
)
}
}

View file

@ -9,6 +9,8 @@ import androidx.core.view.isVisible
import androidx.transition.TransitionManager
import coil.load
import com.tangem.blockchain.common.Blockchain
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.events.Onboarding
import com.tangem.tap.common.extensions.getDrawableCompat
import com.tangem.tap.common.extensions.stripZeroPlainString
import com.tangem.tap.common.redux.navigation.ShareElement
@ -41,6 +43,7 @@ class OnboardingNoteFragment : BaseOnboardingFragment<OnboardingNoteState>() {
binding.toolbar.setTitle(R.string.onboarding_title)
btnRefreshBalanceWidget = RefreshBalanceWidget(mainBinding.onboardingTopContainer.onboardingWalletContainer)
store.dispatch(OnboardingNoteAction.Init)
store.dispatch(OnboardingNoteAction.LoadCardArtwork)
store.dispatch(OnboardingNoteAction.DetermineStepOfScreen)
}
@ -108,7 +111,10 @@ class OnboardingNoteFragment : BaseOnboardingFragment<OnboardingNoteState>() {
private fun setupCreateWalletState(state: OnboardingNoteState) =
with(mainBinding.onboardingActionContainer) {
btnMainAction.setText(R.string.onboarding_create_wallet_button_create_wallet)
btnMainAction.setOnClickListener { store.dispatch(OnboardingNoteAction.CreateWallet) }
btnMainAction.setOnClickListener {
Analytics.send(Onboarding.CreateWallet.ButtonCreateWallet())
store.dispatch(OnboardingNoteAction.CreateWallet)
}
btnAlternativeAction.setText(R.string.onboarding_button_what_does_it_mean)
btnAlternativeAction.setOnClickListener { }

View file

@ -10,6 +10,7 @@ import org.rekotlin.Action
*/
sealed class OnboardingNoteAction : Action {
// from user, ui
object Init : OnboardingNoteAction()
object LoadCardArtwork : OnboardingNoteAction()
object CreateWallet : OnboardingNoteAction()
object TopUp : OnboardingNoteAction()

View file

@ -4,12 +4,16 @@ import com.tangem.common.CompletionResult
import com.tangem.common.extensions.guard
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.Onboarding
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.extensions.dispatchErrorNotification
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.dispatchOpenUrl
import com.tangem.tap.common.extensions.getAddressData
import com.tangem.tap.common.extensions.getToUpUrl
import com.tangem.tap.common.extensions.getTopUpUrl
import com.tangem.tap.common.extensions.onCardScanned
import com.tangem.tap.common.postUi
import com.tangem.tap.common.redux.AppDialog
@ -25,6 +29,8 @@ import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.features.wallet.redux.ProgressState
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.features.wallet.redux.models.WalletDialog
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.tap.tangemSdkManager
@ -61,6 +67,11 @@ private fun handleNoteAction(appState: () -> AppState?, action: Action, dispatch
val noteState = store.state.onboardingNoteState
when (action) {
is OnboardingNoteAction.Init -> {
if (!onboardingManager.isActivationStarted(card.cardId)) {
Analytics.send(Onboarding.Started())
}
}
is OnboardingNoteAction.LoadCardArtwork -> {
scope.launch {
val artworkUrl = onboardingManager.loadArtworkUrl()
@ -77,10 +88,15 @@ private fun handleNoteAction(appState: () -> AppState?, action: Action, dispatch
}
is OnboardingNoteAction.SetStepOfScreen -> {
when (action.step) {
OnboardingNoteStep.CreateWallet -> {
Analytics.send(Onboarding.CreateWallet.ScreenOpened())
}
OnboardingNoteStep.TopUpWallet -> {
Analytics.send(Onboarding.Topup.ScreenOpened())
store.dispatch(OnboardingNoteAction.Balance.Update)
}
OnboardingNoteStep.Done -> {
Analytics.send(Onboarding.Finished())
onboardingManager.activationFinished(card.cardId)
postUi(DELAY_SDK_DIALOG_CLOSE) { store.dispatch(OnboardingNoteAction.Confetti.Show) }
}
@ -92,6 +108,7 @@ private fun handleNoteAction(appState: () -> AppState?, action: Action, dispatch
withMainContext {
when (result) {
is CompletionResult.Success -> {
Analytics.send(Onboarding.CreateWallet.WalletCreatedSuccessfully())
val updatedResponse = scanResponse.copy(card = result.data.card)
onboardingManager.scanResponse = updatedResponse
onboardingManager.activationStarted(updatedResponse.card.cardId)
@ -150,15 +167,27 @@ private fun handleNoteAction(appState: () -> AppState?, action: Action, dispatch
is OnboardingNoteAction.ShowAddressInfoDialog -> {
val addressData = noteState.walletManager?.getAddressData() ?: return
Analytics.send(Onboarding.Topup.ButtonShowWalletAddress())
val appDialog = AppDialog.AddressInfoDialog(noteState.walletBalance.currency, addressData)
store.dispatchDialogShow(appDialog)
}
is OnboardingNoteAction.TopUp -> {
val topUpUrl = noteState.walletManager?.getToUpUrl() ?: return
if (globalState.userCountryCode == RUSSIA_COUNTRY_CODE) {
store.dispatchOnMain(WalletAction.DialogAction.RussianCardholdersWarningDialog(topUpUrl))
val walletManager = noteState.walletManager.guard {
store.dispatchDebugErrorNotification("NPE: WalletManager")
return
}
val topUpUrl = walletManager.getTopUpUrl() ?: return
val blockchain = walletManager.wallet.blockchain
if (globalState.userCountryCode == RUSSIA_COUNTRY_CODE) {
val dialogData = WalletDialog.RussianCardholdersWarningDialog.Data(topUpUrl, blockchain)
store.dispatchOnMain(WalletAction.DialogAction.RussianCardholdersWarningDialog(dialogData))
return
}
val currencyType = AnalyticsParam.CurrencyType.Blockchain(blockchain)
Analytics.send(Onboarding.Topup.ButtonBuyCrypto(currencyType))
store.dispatchOpenUrl(topUpUrl)
}
OnboardingNoteAction.Done -> {

View file

@ -8,6 +8,8 @@ import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.view.isVisible
import androidx.transition.TransitionManager
import coil.load
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.events.Onboarding
import com.tangem.tap.common.extensions.getDrawableCompat
import com.tangem.tap.common.redux.navigation.ShareElement
import com.tangem.tap.common.transitions.InternalNoteLayoutTransition
@ -33,6 +35,7 @@ class OnboardingOtherCardsFragment : BaseOnboardingFragment<OnboardingOtherCards
mainBinding.onboardingTopContainer.imvFrontCard.transitionName = ShareElement.imvFrontCard
binding.toolbar.setTitle(R.string.onboarding_title)
store.dispatch(OnboardingOtherCardsAction.Init)
store.dispatch(OnboardingOtherCardsAction.LoadCardArtwork)
store.dispatch(OnboardingOtherCardsAction.DetermineStepOfScreen)
}
@ -68,7 +71,10 @@ class OnboardingOtherCardsFragment : BaseOnboardingFragment<OnboardingOtherCards
private fun setupCreateWalletState() = with(mainBinding.onboardingActionContainer) {
btnMainAction.setText(R.string.onboarding_create_wallet_button_create_wallet)
btnMainAction.setOnClickListener { store.dispatch(OnboardingOtherCardsAction.CreateWallet) }
btnMainAction.setOnClickListener {
Analytics.send(Onboarding.CreateWallet.ButtonCreateWallet())
store.dispatch(OnboardingOtherCardsAction.CreateWallet)
}
btnAlternativeAction.setText(R.string.onboarding_button_what_does_it_mean)
btnAlternativeAction.setOnClickListener { }

View file

@ -7,6 +7,7 @@ import org.rekotlin.Action
*/
sealed class OnboardingOtherCardsAction : Action {
// from user, ui
object Init : OnboardingOtherCardsAction()
object LoadCardArtwork : OnboardingOtherCardsAction()
object CreateWallet : OnboardingOtherCardsAction()

View file

@ -4,6 +4,8 @@ import com.tangem.blockchain.common.Blockchain
import com.tangem.common.CompletionResult
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.events.Onboarding
import com.tangem.tap.common.extensions.onCardScanned
import com.tangem.tap.common.postUi
import com.tangem.tap.common.redux.AppState
@ -45,6 +47,11 @@ private fun handleOtherCardsAction(action: Action, dispatch: DispatchFunction) {
val card = onboardingManager.scanResponse.card
when (action) {
is OnboardingOtherCardsAction.Init -> {
if (!onboardingManager.isActivationStarted(card.cardId)) {
Analytics.send(Onboarding.Started())
}
}
is OnboardingOtherCardsAction.LoadCardArtwork -> {
scope.launch {
val artworkUrl = onboardingManager.loadArtworkUrl()
@ -59,9 +66,15 @@ private fun handleOtherCardsAction(action: Action, dispatch: DispatchFunction) {
store.dispatch((OnboardingOtherCardsAction.SetStepOfScreen(step)))
}
is OnboardingOtherCardsAction.SetStepOfScreen -> {
if (action.step == OnboardingOtherCardsStep.Done) {
onboardingManager.activationFinished(card.cardId)
postUi(200) { store.dispatch(OnboardingOtherCardsAction.Confetti.Show) }
when (action.step) {
OnboardingOtherCardsStep.CreateWallet -> {
Analytics.send(Onboarding.CreateWallet.ScreenOpened())
}
OnboardingOtherCardsStep.Done -> {
Analytics.send(Onboarding.Finished())
onboardingManager.activationFinished(card.cardId)
postUi(200) { store.dispatch(OnboardingOtherCardsAction.Confetti.Show) }
}
}
}
is OnboardingOtherCardsAction.CreateWallet -> {
@ -70,8 +83,9 @@ private fun handleOtherCardsAction(action: Action, dispatch: DispatchFunction) {
withMainContext {
when (result) {
is CompletionResult.Success -> {
Analytics.send(Onboarding.CreateWallet.WalletCreatedSuccessfully())
val updatedResponse = onboardingManager.scanResponse.copy(
card = result.data.card
card = result.data.card,
)
onboardingManager.scanResponse = updatedResponse
onboardingManager.activationStarted(updatedResponse.card.cardId)

View file

@ -1,16 +1,20 @@
package com.tangem.tap.features.onboarding.products.twins.redux
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.extensions.Result
import com.tangem.common.CompletionResult
import com.tangem.common.extensions.guard
import com.tangem.domain.common.ScanResponse
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.Onboarding
import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.extensions.dispatchErrorNotification
import com.tangem.tap.common.extensions.dispatchOpenUrl
import com.tangem.tap.common.extensions.getAddressData
import com.tangem.tap.common.extensions.getToUpUrl
import com.tangem.tap.common.extensions.getTopUpUrl
import com.tangem.tap.common.extensions.onCardScanned
import com.tangem.tap.common.postUi
import com.tangem.tap.common.redux.AppDialog
@ -76,6 +80,7 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
fun finishCardActivation() {
if (twinCardsState.mode == CreateTwinWalletMode.CreateWallet) {
Analytics.send(Onboarding.Finished())
onboardingManager?.activationFinished(getScanResponse().card.cardId)
twinCardsState.pairCardId?.let { onboardingManager?.activationFinished(it) }
}
@ -85,11 +90,21 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
is TwinCardsAction.Init -> {
if (twinCardsState.currentStep == TwinCardsStep.WelcomeOnly) return
val scanResponse = getScanResponse()
onboardingManager?.apply {
if (!isActivationStarted(scanResponse.card.cardId)) {
Analytics.send(Onboarding.Started())
}
}
when (twinCardsState.mode) {
CreateTwinWalletMode.CreateWallet -> {
if (preferencesStorage.wasTwinsOnboardingShown()) {
val step = when {
!getScanResponse().twinsIsTwinned() -> TwinCardsStep.CreateFirstWallet
!scanResponse.twinsIsTwinned() -> {
Analytics.send(Onboarding.CreateWallet.ScreenOpened())
TwinCardsStep.CreateFirstWallet
}
twinCardsState.walletBalance.balanceIsToppedUp() -> TwinCardsStep.Done
else -> TwinCardsStep.TopUpWallet
}
@ -105,8 +120,8 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
}
is TwinCardsAction.Wallet.HandleOnBackPressed -> {
val shouldReturnCardBack = twinCardsState.mode == CreateTwinWalletMode.CreateWallet
&& twinCardsState.currentStep != TwinCardsStep.TopUpWallet
&& twinCardsState.currentStep != TwinCardsStep.Done
&& twinCardsState.currentStep != TwinCardsStep.TopUpWallet
&& twinCardsState.currentStep != TwinCardsStep.Done
if (twinCardsState.showAlert) {
val onInterruptPrompt = {
@ -126,8 +141,10 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
}
is TwinCardsAction.SetStepOfScreen -> {
when (action.step) {
TwinCardsStep.WelcomeOnly -> preferencesStorage.saveTwinsOnboardingShown()
TwinCardsStep.Welcome -> preferencesStorage.saveTwinsOnboardingShown()
TwinCardsStep.WelcomeOnly, TwinCardsStep.Welcome -> {
Analytics.send(Onboarding.Twins.ScreenOpened())
preferencesStorage.saveTwinsOnboardingShown()
}
TwinCardsStep.TopUpWallet -> store.dispatch(TwinCardsAction.Balance.Update)
TwinCardsStep.Done -> {
finishCardActivation()
@ -136,16 +153,17 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
}
}
is TwinCardsAction.Wallet.LaunchFirstStep -> {
Analytics.send(Onboarding.Twins.SetupStarted())
val manager = TwinCardsManager(
card = getScanResponse().card,
assetReader = action.reader,
analyticsHandler = store.state.globalState.analyticsHandler
)
store.dispatch(TwinCardsAction.CardsManager.Set(manager))
scope.launch {
when (val result = manager.createFirstWallet(action.initialMessage)) {
is CompletionResult.Success -> {
Analytics.send(Onboarding.CreateWallet.WalletCreatedSuccessfully())
startCardActivation(result.data.cardId)
delay(DELAY_SDK_DIALOG_CLOSE)
withMainContext {
@ -164,7 +182,7 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
val result = manager.createSecondWallet(
action.initialMessage,
action.preparingMessage,
action.creatingWalletMessage
action.creatingWalletMessage,
)
when (result) {
is CompletionResult.Success -> {
@ -186,6 +204,7 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
scope.launch {
when (val result = manager.complete(action.message)) {
is Result.Success -> {
Analytics.send(Onboarding.Twins.SetupFinished())
updateScanResponse(result.data)
delay(DELAY_SDK_DIALOG_CLOSE)
withMainContext {
@ -222,11 +241,11 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
val balanceIsLoading = twinCardsState.walletBalance.copy(
currency = Currency.Blockchain(
walletManager.wallet.blockchain,
walletManager.wallet.publicKey.derivationPath?.rawPath
walletManager.wallet.publicKey.derivationPath?.rawPath,
),
state = ProgressState.Loading,
error = null,
criticalError = null
criticalError = null,
)
store.dispatch(TwinCardsAction.Balance.Set(balanceIsLoading))
@ -240,7 +259,6 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
store.dispatch(TwinCardsAction.Balance.SetNonCriticalError(loadedBalance.error))
}
}
}
is TwinCardsAction.Balance.Set -> {
if (action.balance.balanceIsToppedUp()) {
@ -256,7 +274,11 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
store.dispatchDialogShow(appDialog)
}
is TwinCardsAction.TopUp -> {
val topUpUrl = twinCardsState.walletManager?.getToUpUrl() ?: return
val topUpUrl = twinCardsState.walletManager?.getTopUpUrl() ?: return
val currencyType = AnalyticsParam.CurrencyType.Blockchain(Blockchain.Bitcoin)
Analytics.send(Onboarding.Topup.ButtonBuyCrypto(currencyType))
store.dispatchOpenUrl(topUpUrl)
}
TwinCardsAction.Done -> {

View file

@ -16,6 +16,8 @@ import com.tangem.common.extensions.VoidCallback
import com.tangem.domain.common.TwinCardNumber
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget
import com.tangem.tap.common.AndroidAssetReader
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.events.Onboarding
import com.tangem.tap.common.extensions.beginDelayedTransition
import com.tangem.tap.common.extensions.getDrawableCompat
import com.tangem.tap.common.extensions.hide
@ -238,6 +240,7 @@ class TwinsCardsFragment : BaseOnboardingFragment<TwinCardsState>() {
btnMainAction.text = getString(R.string.twins_recreate_button_format, twinIndexNumber)
btnMainAction.setOnClickListener {
Analytics.send(Onboarding.CreateWallet.ButtonCreateWallet())
store.dispatch(
TwinCardsAction.Wallet.LaunchFirstStep(
Message(getString(R.string.twins_recreate_title_format, twinIndexNumber)),

View file

@ -5,7 +5,6 @@ import org.rekotlin.Action
sealed class OnboardingWalletAction : Action {
object Init : OnboardingWalletAction()
object StartSaltPayCardActivation : OnboardingWalletAction()
object GetToCreateWalletStep : OnboardingWalletAction()
object GetToSaltPayStep : OnboardingWalletAction()
object CreateWallet : OnboardingWalletAction()

View file

@ -3,10 +3,14 @@ package com.tangem.tap.features.onboarding.products.wallet.redux
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.CompletionResult
import com.tangem.common.card.Card
import com.tangem.common.extensions.ifNotNull
import com.tangem.domain.common.ScanResponse
import com.tangem.domain.common.TapWorkarounds.isSaltPay
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.operations.backup.BackupService
import com.tangem.tap.backupService
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.events.Onboarding
import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.primaryCardIsSaltPayVisa
@ -61,6 +65,12 @@ private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch:
when (action) {
OnboardingWalletAction.Init -> {
ifNotNull(onboardingManager, card) { manager, card ->
if (!card.isSaltPay && !manager.isActivationStarted(card.cardId)) {
Analytics.send(Onboarding.Started())
}
}
when {
card == null -> {
// it's possible when found unfinished backup for standard Wallet cards
@ -84,16 +94,10 @@ private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch:
}
else -> {
store.dispatch(OnboardingWalletAction.GetToCreateWalletStep)
Analytics.send(Onboarding.CreateWallet.ScreenOpened())
}
}
}
is OnboardingWalletAction.StartSaltPayCardActivation -> {
// we need to do this because the standard activation happens in the OnboardingWalletAction.CreateWallet
// step which is omitted for SaltPay cards
if (onboardingWalletState.isSaltPay && scanResponse != null) {
startCardActivation(scanResponse)
}
}
is OnboardingWalletAction.LoadArtwork -> {
scope.launch {
val artwork = onboardingManager?.loadArtworkUrl()
@ -112,6 +116,7 @@ private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch:
withMainContext {
when (result) {
is CompletionResult.Success -> {
Analytics.send(Onboarding.CreateWallet.WalletCreatedSuccessfully())
//here we must use updated scanResponse after createWallet & derivation
val updatedResponse = globalState.onboardingState.onboardingManager.scanResponse.copy(
card = result.data.card,
@ -148,9 +153,6 @@ private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch:
} else {
val backupState = store.state.onboardingWalletState.backupState
val updatedScanResponse = updateScanResponseAfterBackup(scanResponse, backupState)
if (onboardingWalletState.isSaltPay) {
finishCardActivation(backupState, scanResponse.card)
}
scope.launch { globalState.tapWalletManager.onCardScanned(updatedScanResponse) }
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet))
}
@ -166,6 +168,7 @@ private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch:
if (onboardingWalletState.isSaltPay) {
BackupAction.StartBackup
} else {
Analytics.send(Onboarding.Backup.ScreenOpened())
BackupAction.IntroduceBackup
}
} else {
@ -221,6 +224,7 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction)
when (action) {
is BackupAction.StartBackup -> {
Analytics.send(Onboarding.Backup.Started())
backupService.discardSavedBackup()
val primaryCard = scanResponse?.primaryCard
if (primaryCard != null) {
@ -266,7 +270,11 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction)
store.dispatchOnMain(GlobalAction.ShowDialog(BackupDialog.AddMoreBackupCards))
}
}
is BackupAction.ShowEnterAccessCodeScreen -> {
Analytics.send(Onboarding.Backup.SettingAccessCodeStarted())
}
is BackupAction.CheckAccessCode -> {
Analytics.send(Onboarding.Backup.AccessCodeEntered())
if (action.accessCode.length < 4) {
store.dispatch(BackupAction.SetAccessCodeError(AccessCodeError.CodeTooShort))
} else {
@ -275,6 +283,7 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction)
}
}
is BackupAction.SaveAccessCodeConfirmation -> {
Analytics.send(Onboarding.Backup.AccessCodeReEntered())
if (action.accessCodeConfirmation == backupState.accessCode) {
store.dispatch(BackupAction.SetAccessCodeError(null))
backupService.setAccessCode(action.accessCodeConfirmation)
@ -312,7 +321,9 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction)
}
}
is BackupAction.FinishBackup -> {
Analytics.send(Onboarding.Backup.Finished(backupState.backupCardsNumber))
if (!onboardingWalletState.isSaltPay) {
Analytics.send(Onboarding.Finished())
finishCardActivation(backupState, card)
}
}
@ -320,7 +331,10 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction)
backupService.discardSavedBackup()
}
is BackupAction.DiscardSavedBackup -> {
backupService.primaryCardId?.let { finishCardsActivationForDiscardedUnfinishedBackup(it) }
backupService.primaryCardId?.let {
Analytics.send(Onboarding.Finished())
finishCardsActivationForDiscardedUnfinishedBackup(it)
}
backupService.discardSavedBackup()
}
is BackupAction.CheckForUnfinishedBackup -> {
@ -333,6 +347,7 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction)
store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingWallet))
}
is BackupAction.DismissBackup -> {
Analytics.send(Onboarding.Backup.Skipped())
if (onboardingWalletState.isSaltPay) throw UnsupportedOperationException()
store.dispatch(BackupAction.FinishBackup)
}
@ -351,7 +366,7 @@ private fun startCardActivation(scanResponse: ScanResponse) {
* Standard Wallet cards finish activation at BackupAction.FinishBackup
* SaltPay cards finish activation at OnboardingWalletAction.FinishOnboarding
*/
private fun finishCardActivation(backupState: BackupState, card: Card?) {
internal fun finishCardActivation(backupState: BackupState, card: Card?) {
(listOf(backupState.primaryCardId, card?.cardId) + backupState.backupCardIds)
.distinct().filterNotNull()
.forEach { cardId ->
@ -377,7 +392,7 @@ private fun initSaltPayOnBackupFinishedIfNeeded(
if (scanResponse == null) throw IllegalArgumentException()
val (manager, config) = OnboardingSaltPayState.initDependency(scanResponse)
store.dispatchOnMain(OnboardingSaltPayAction.Init.SetDependencies(manager, config))
store.dispatchOnMain(OnboardingSaltPayAction.SetDependencies(manager, config))
store.dispatchOnMain(OnboardingSaltPayAction.Update)
}
}

View file

@ -10,12 +10,12 @@ import java.math.BigDecimal
[REDACTED_AUTHOR]
*/
sealed class OnboardingSaltPayAction : Action {
sealed class Init : OnboardingSaltPayAction() {
data class SetDependencies(
val registrationManager: SaltPayActivationManager,
val saltPayConfig: SaltPayConfig,
) : Init()
}
object Init : OnboardingSaltPayAction()
data class SetDependencies(
val registrationManager: SaltPayActivationManager,
val saltPayConfig: SaltPayConfig,
) : OnboardingSaltPayAction()
data class SetInProgress(val isInProgress: Boolean) : OnboardingSaltPayAction()
data class SetClaimRefreshInProgress(val isInProgress: Boolean) : OnboardingSaltPayAction()

View file

@ -10,15 +10,18 @@ import com.tangem.domain.common.extensions.successOr
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.network.api.paymentology.KYCStatus
import com.tangem.network.api.paymentology.RegistrationResponse
import com.tangem.tap.common.analytics.GlobalAnalyticsEventHandler
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.events.Onboarding
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.isPositive
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.TangemSigner
import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.features.onboarding.OnboardingManager
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState
import com.tangem.tap.features.onboarding.products.wallet.redux.finishCardActivation
import com.tangem.tap.features.onboarding.products.wallet.saltPay.AllSymbolsTheSameFilter
import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayActivationManager
import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayExceptionHandler
@ -58,10 +61,23 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App
fun getAppState(): AppState = appState()!!
fun getOnboardingWalletState(): OnboardingWalletState = getAppState().onboardingWalletState
fun getOnboardingManager(): OnboardingManager? = getAppState().globalState.onboardingState.onboardingManager
fun getState(): OnboardingSaltPayState = getOnboardingWalletState().onboardingSaltPayState!!
val analyticsHandler = getAppState().globalState.analyticsHandler
when (action) {
is OnboardingSaltPayAction.Init -> {
if (!getOnboardingWalletState().isSaltPay) return
val onboardingManager = getOnboardingManager().guard {
// onboardingManager may be null if it's started from standard Wallet unfinished backup
return
}
val card = onboardingManager.scanResponse.card
if (!onboardingManager.isActivationStarted(card.cardId)) {
Analytics.send(Onboarding.Started())
onboardingManager.activationStarted(card.cardId)
}
}
is OnboardingSaltPayAction.Update -> {
handleInProgress = true
@ -77,8 +93,6 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App
}
}
is OnboardingSaltPayAction.RegisterCard -> {
analyticsHandler.handleAnalyticsEvent(Onboarding.ButtonConnect())
handleInProgress = true
val state = getState()
@ -151,7 +165,7 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App
is OnboardingSaltPayAction.TrySetPin -> {
try {
assertPinValid(action.pin, getState().pinLength)
analyticsHandler.handleAnalyticsEvent(Onboarding.PinCodeSet())
Analytics.send(Onboarding.PinCodeSet())
store.dispatch(OnboardingSaltPayAction.SetPin(action.pin))
store.dispatch(OnboardingSaltPayAction.SetStep(SaltPayActivationStep.CardRegistration))
} catch (error: SaltPayActivationError) {
@ -159,7 +173,6 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App
}
}
is OnboardingSaltPayAction.Claim -> {
analyticsHandler.handleAnalyticsEvent(Onboarding.ButtonClaim())
val state = getState()
handleInProgress = true
@ -192,7 +205,7 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App
}
handleInProgress = false
analyticsHandler.handleAnalyticsEvent(Onboarding.ClaimWasSuccessfully())
Analytics.send(Onboarding.ClaimWasSuccessfully())
dispatchOnMain(OnboardingSaltPayAction.SetStep(SaltPayActivationStep.ClaimInProgress))
dispatchOnMain(OnboardingSaltPayAction.RefreshClaim)
}
@ -233,23 +246,31 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App
}
}
}
is OnboardingSaltPayAction.SetStep -> handleAnalytics(analyticsHandler, action.newStep)
is OnboardingSaltPayAction.SetStep -> {
when (action.newStep) {
SaltPayActivationStep.KycStart -> Analytics.send(Onboarding.KYCStarted())
SaltPayActivationStep.KycWaiting -> Analytics.send(Onboarding.KYCInProgress())
SaltPayActivationStep.KycReject -> Analytics.send(Onboarding.KYCRejected())
SaltPayActivationStep.Claim -> Analytics.send(Onboarding.ClaimScreenOpened())
SaltPayActivationStep.ClaimSuccess, SaltPayActivationStep.Success -> {
Analytics.send(Onboarding.Finished())
val onboardingManager = getOnboardingManager().guard {
// Null is possible if it is started from a standard pending backup and in this case
// it is impossible to get here
store.dispatchDebugErrorNotification("OnboardingManager can't be NULL")
return
}
finishCardActivation(getOnboardingWalletState().backupState, onboardingManager.scanResponse.card)
}
else -> {}
}
}
else -> {
/* do nothing, only reduce */
}
}
}
private fun handleAnalytics(analyticsHandler: GlobalAnalyticsEventHandler, step: SaltPayActivationStep) {
when (step) {
SaltPayActivationStep.KycStart -> analyticsHandler.handleAnalyticsEvent(Onboarding.KYCStarted())
SaltPayActivationStep.KycWaiting -> analyticsHandler.handleAnalyticsEvent(Onboarding.KYCInProgress())
SaltPayActivationStep.KycReject -> analyticsHandler.handleAnalyticsEvent(Onboarding.KYCRejected())
SaltPayActivationStep.Claim -> analyticsHandler.handleAnalyticsEvent(Onboarding.ClaimScreenOpened())
else -> {}
}
}
suspend fun SaltPayActivationManager.update(
currentStep: SaltPayActivationStep,
currentAmountToClaim: Amount?,
@ -265,7 +286,7 @@ suspend fun SaltPayActivationManager.update(
return Result.Failure(ex)
}
checkGasIfNeeded(this, newStep).successOr { return it }
// checkGasIfNeeded(this, newStep).successOr { return it }
Timber.d("update: success: %s", newStep)
return Result.Success(newStep)

View file

@ -17,7 +17,7 @@ class OnboardingSaltPayReducer {
private fun internalReduce(anyAction: Action, onboardingWalletState: OnboardingWalletState): OnboardingWalletState {
val action = anyAction as? OnboardingSaltPayAction ?: return onboardingWalletState
if (onboardingWalletState.onboardingSaltPayState == null && action is OnboardingSaltPayAction.Init.SetDependencies) {
if (onboardingWalletState.onboardingSaltPayState == null && action is OnboardingSaltPayAction.SetDependencies) {
return onboardingWalletState.copy(
onboardingSaltPayState = OnboardingSaltPayState(
saltPayManager = action.registrationManager,

View file

@ -14,6 +14,8 @@ import coil.size.Scale
import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.button.MaterialButton
import com.tangem.common.extensions.guard
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.events.Onboarding
import com.tangem.tap.common.compose.PinCodeWidget
import com.tangem.tap.common.extensions.configureSettings
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
@ -178,6 +180,7 @@ internal class OnboardingSaltPayView(
showOnlyView(connectCard.root) {
progressButton = SaltPayProgressButton(connectCard.root)
connectCard.btnConnect.setOnClickListener {
Analytics.send(Onboarding.ButtonConnect())
store.dispatch(OnboardingSaltPayAction.RegisterCard)
}
}
@ -286,6 +289,7 @@ internal class OnboardingSaltPayView(
btnMain.text = getText(R.string.onboarding_button_claim)
btnMain.isEnabled
btnMain.setOnClickListener {
Analytics.send(Onboarding.ButtonClaim())
store.dispatch(OnboardingSaltPayAction.Claim)
}
progressButton = SaltPayProgressButton(actionContainer.root)

View file

@ -68,7 +68,7 @@ class WalletBackupAnimator(
when (backupCardsCounts) {
0 -> {
when (currentStep) {
STEP_CREATE_WALLET -> {
UNDEFINED, STEP_CREATE_WALLET -> {
cardsWidget.toFolded(false) { cardsWidget.toFan() }
FIRST_BACKUP.animateAlpha(0.6f, 0)

View file

@ -19,6 +19,8 @@ import com.tangem.common.CardIdFormatter
import com.tangem.common.core.CardIdDisplayFormat
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.PropertyCalculator
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.events.Onboarding
import com.tangem.tap.common.extensions.hide
import com.tangem.tap.common.extensions.inflate
import com.tangem.tap.common.extensions.show
@ -32,6 +34,7 @@ import com.tangem.tap.features.onboarding.products.wallet.redux.BackupStep
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletAction
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletStep
import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayAction
import com.tangem.tap.features.onboarding.products.wallet.saltPay.ui.OnboardingSaltPayView
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.AccessCodeDialog
import com.tangem.tap.store
@ -81,7 +84,7 @@ class OnboardingWalletFragment : BaseFragment(R.layout.fragment_onboarding_walle
addBackPressHandler(this)
store.dispatch(OnboardingWalletAction.Init)
store.dispatch(OnboardingWalletAction.StartSaltPayCardActivation)
store.dispatch(OnboardingSaltPayAction.Init)
store.dispatch(OnboardingWalletAction.LoadArtwork)
}
@ -170,7 +173,10 @@ class OnboardingWalletFragment : BaseFragment(R.layout.fragment_onboarding_walle
private fun setupCreateWalletState() = with(binding) {
layoutButtonsCommon.btnWalletMainAction.setText(R.string.onboarding_create_wallet_button_create_wallet)
layoutButtonsCommon.btnWalletMainAction.setOnClickListener { store.dispatch(OnboardingWalletAction.CreateWallet) }
layoutButtonsCommon.btnWalletMainAction.setOnClickListener {
Analytics.send(Onboarding.CreateWallet.ButtonCreateWallet())
store.dispatch(OnboardingWalletAction.CreateWallet)
}
layoutButtonsCommon.btnWalletAlternativeAction.hide()
toolbar.title = getText(R.string.onboarding_getting_started)

View file

@ -17,9 +17,9 @@ import com.tangem.common.services.Result
import com.tangem.domain.common.TapWorkarounds.isStart2Coin
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE
import com.tangem.tap.common.analytics.AnalyticsAnOld
import com.tangem.tap.common.analytics.AnalyticsEventAnOld
import com.tangem.tap.common.analytics.AnalyticsParamAnOld
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.Token
import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.extensions.dispatchErrorNotification
import com.tangem.tap.common.extensions.dispatchOnMain
@ -219,13 +219,10 @@ private fun sendTransaction(
dispatch(SendAction.ChangeSendButtonState(ButtonState.ENABLED))
tangemSdk.config.linkedTerminal = isLinkedTerminal
val currencyType = AnalyticsParam.CurrencyType.Amount(amountToSend)
when (sendResult) {
is SimpleResult.Success -> {
store.state.globalState.analyticsHandler?.handleAnalyticsEvent(
event = AnalyticsEventAnOld.TRANSACTION_IS_SENT,
card = card,
blockchain = walletManager.wallet.blockchain.currency,
)
Analytics.send(Token.Send.TransactionSent(currencyType))
dispatch(SendAction.SendSuccess)
if (externalTransactionData != null) {
@ -250,15 +247,10 @@ private fun sendTransaction(
feeAmount = feeAmount,
destinationAddress = destinationAddress,
)
store.state.globalState.analyticsHandler?.handleBlockchainSdkErrorEvent(
error = sendResult.error,
action = AnalyticsAnOld.ActionToLog.SendTransaction,
params = mapOf(AnalyticsParamAnOld.BLOCKCHAIN to walletManager.wallet.blockchain.currency),
card = card,
)
val error = (sendResult.error as? BlockchainSdkError) ?: return@withMainContext
Analytics.send(Token.Send.TransactionSent(currencyType, error))
when (error) {
is BlockchainSdkError.WrappedTangemError -> {
val tangemSdkError = (error.tangemError as? TangemSdkError) ?: return@withMainContext

View file

@ -16,6 +16,8 @@ import com.google.android.material.textfield.TextInputEditText
import com.tangem.Message
import com.tangem.tangem_sdk_new.extensions.hideSoftKeyboard
import com.tangem.tap.common.KeyboardObserver
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.events.Token
import com.tangem.tap.common.entities.FiatCurrency
import com.tangem.tap.common.extensions.getFromClipboard
import com.tangem.tap.common.extensions.setOnImeActionListener
@ -28,10 +30,25 @@ import com.tangem.tap.common.toggleWidget.IndeterminateProgressButtonWidget
import com.tangem.tap.common.toggleWidget.ViewStateWidget
import com.tangem.tap.features.BaseStoreFragment
import com.tangem.tap.features.addBackPressHandler
import com.tangem.tap.features.send.redux.*
import com.tangem.tap.features.send.redux.AddressPayIdActionUi.*
import com.tangem.tap.features.send.redux.AmountActionUi.*
import com.tangem.tap.features.send.redux.FeeActionUi.*
import com.tangem.tap.features.send.redux.AddressPayIdActionUi
import com.tangem.tap.features.send.redux.AddressPayIdActionUi.CheckClipboard
import com.tangem.tap.features.send.redux.AddressPayIdActionUi.PasteAddressPayId
import com.tangem.tap.features.send.redux.AddressPayIdActionUi.SetTruncateHandler
import com.tangem.tap.features.send.redux.AddressPayIdActionUi.TruncateOrRestore
import com.tangem.tap.features.send.redux.AmountAction
import com.tangem.tap.features.send.redux.AmountActionUi
import com.tangem.tap.features.send.redux.AmountActionUi.CheckAmountToSend
import com.tangem.tap.features.send.redux.AmountActionUi.SetMainCurrency
import com.tangem.tap.features.send.redux.AmountActionUi.SetMaxAmount
import com.tangem.tap.features.send.redux.AmountActionUi.ToggleMainCurrency
import com.tangem.tap.features.send.redux.FeeActionUi.ChangeIncludeFee
import com.tangem.tap.features.send.redux.FeeActionUi.ChangeSelectedFee
import com.tangem.tap.features.send.redux.FeeActionUi.ToggleControlsVisibility
import com.tangem.tap.features.send.redux.ReceiptAction
import com.tangem.tap.features.send.redux.ReleaseSendState
import com.tangem.tap.features.send.redux.SendAction
import com.tangem.tap.features.send.redux.SendActionUi
import com.tangem.tap.features.send.redux.TransactionExtrasAction
import com.tangem.tap.features.send.redux.states.FeeType
import com.tangem.tap.features.send.redux.states.MainCurrencyType
import com.tangem.tap.features.send.ui.stateSubscribers.SendStateSubscriber
@ -43,7 +60,12 @@ import com.tangem.wallet.R
import com.tangem.wallet.databinding.FragmentSendBinding
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import java.text.DecimalFormatSymbols
/**
@ -61,6 +83,11 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
val binding: FragmentSendBinding by viewBinding(FragmentSendBinding::bind)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Analytics.send(Token.Send.ScreenOpened())
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
addBackPressHandler(this)
@ -101,10 +128,12 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
.launchIn(mainScope)
imvPaste.setOnClickListener {
Analytics.send(Token.Send.ButtonPaste())
store.dispatch(PasteAddressPayId(requireContext().getFromClipboard()?.toString() ?: ""))
store.dispatch(TruncateOrRestore(!etAddressOrPayId.isFocused))
}
imvQrCode.setOnClickListener {
Analytics.send(Token.Send.ButtonQRCode())
startActivityForResult(
Intent(requireContext(), ScanQrCodeActivity::class.java),
ScanQrCodeActivity.SCAN_QR_REQUEST_CODE
@ -165,6 +194,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
store.dispatch(AmountAction.SetDecimalSeparator(decimalSeparator))
binding.lSendAmount.tvAmountCurrency.setOnClickListener {
Analytics.send(Token.Send.ButtonSwapCurrency())
store.dispatch(ToggleMainCurrency)
store.dispatch(ReceiptAction.RefreshReceipt)
store.dispatch(SendAction.ChangeSendButtonState(store.state.sendState.getButtonState()))

View file

@ -119,13 +119,8 @@ private fun handle(action: Action) {
is ShopAction.FinishSuccessfulOrder -> {
scope.launch {
shopService.waitForCheckout(
productType = shopState.selectedProduct,
analyticsHandler = store.state.globalState.analyticsHandler
)
shopService.waitForCheckout(shopState.selectedProduct)
}
}
}
}

View file

@ -12,6 +12,8 @@ import androidx.activity.OnBackPressedCallback
import by.kirich1409.viewbindingdelegate.viewBinding
import com.tangem.tap.common.GlobalLayoutStateHandler
import com.tangem.tap.common.KeyboardObserver
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.events.Shop
import com.tangem.tap.common.extensions.show
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.common.shop.data.ProductType
@ -41,6 +43,8 @@ class ShopFragment : BaseStoreFragment(R.layout.fragment_shop), StoreSubscriber<
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Analytics.send(Shop.ScreenOpened())
activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
store.dispatch(NavigationAction.PopBackTo())

View file

@ -13,6 +13,8 @@ import androidx.compose.ui.platform.ComposeView
import com.google.accompanist.appcompattheme.AppCompatTheme
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenState
import com.tangem.domain.redux.domainStore
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.events.ManageTokens
import com.tangem.tap.common.compose.ClosePopupTrigger
import com.tangem.tap.features.BaseStoreFragment
import com.tangem.tap.features.FragmentOnBackPressedHandler
@ -28,6 +30,11 @@ class AddCustomTokenFragment : BaseStoreFragment(R.layout.view_compose_fragment)
private var state: MutableState<AddCustomTokenState> = mutableStateOf(domainStore.state.addCustomTokensState)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Analytics.send(ManageTokens.CustomToken.ScreenOpened())
}
override fun subscribeToStore() {
domainStore.subscribe(this) { state ->
state.skipRepeats { oldState, newState ->
@ -45,30 +52,29 @@ class AddCustomTokenFragment : BaseStoreFragment(R.layout.view_compose_fragment)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
requireActivity().window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
view.findViewById<Toolbar>(R.id.toolbar)?.let {
it.setTitle(R.string.add_custom_token_title)
}
requireActivity().window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
view.findViewById<Toolbar>(R.id.toolbar)?.setTitle(R.string.add_custom_token_title)
val closePopupTrigger = initClosingPopupTriggerEvent()
view.findViewById<ComposeView>(R.id.view_compose)?.setContent {
AppCompatTheme(requireContext()) {
Box(modifier = Modifier
.fillMaxSize()
Box(
modifier = Modifier
.fillMaxSize(),
) {
AddCustomTokenScreen(state, closePopupTrigger)
}
}
}
}
private fun initClosingPopupTriggerEvent(): ClosePopupTrigger = ClosePopupTrigger().apply {
onCloseComplete = ::handleOnBackPressed
addBackPressHandler(object : FragmentOnBackPressedHandler {
override fun handleOnBackPressed() = close()
})
addBackPressHandler(
object : FragmentOnBackPressedHandler {
override fun handleOnBackPressed() = close()
},
)
}
}

View file

@ -20,6 +20,8 @@ import com.tangem.domain.redux.domainStore
import com.tangem.operations.derivation.ExtendedPublicKeysMap
import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE
import com.tangem.tap.assetReader
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.events.ManageTokens
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.redux.AppState
@ -51,6 +53,7 @@ class TokensMiddleware {
handleAddingCustomToken(action)
}
is TokensAction.SetSearchInput -> {
Analytics.send(ManageTokens.TokenSearched())
handleLoadCurrencies(
scanResponse = store.state.globalState.scanResponse,
newSearchInput = action.searchInput
@ -335,6 +338,7 @@ class TokensMiddleware {
store.dispatchOnMain(NavigationAction.PopBackTo())
}
Analytics.send(ManageTokens.CustomToken.TokenWasAdded(customCurrency))
val currency = Currency.fromCustomCurrency(customCurrency)
val isNeedToDerive = isNeedToDerive(scanResponse, currency)
val currencyList = listOf(currency)

View file

@ -15,6 +15,8 @@ import androidx.transition.TransitionInflater
import by.kirich1409.viewbindingdelegate.viewBinding
import com.google.accompanist.appcompattheme.AppCompatTheme
import com.tangem.blockchain.common.Blockchain
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.events.ManageTokens
import com.tangem.tap.common.extensions.copyToClipboard
import com.tangem.tap.common.extensions.dispatchNotification
import com.tangem.tap.common.extensions.dispatchOnMain
@ -48,12 +50,17 @@ class AddTokensFragment : Fragment(R.layout.fragment_add_tokens),
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
store.dispatch(NavigationAction.PopBackTo())
store.dispatch(TokensAction.ResetState)
}
})
Analytics.send(ManageTokens.ScreenOpened())
activity?.onBackPressedDispatcher?.addCallback(
this,
object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
store.dispatch(NavigationAction.PopBackTo())
store.dispatch(TokensAction.ResetState)
}
},
)
val inflater = TransitionInflater.from(requireContext())
enterTransition = inflater.inflateTransition(R.transition.slide_right)
exitTransition = inflater.inflateTransition(R.transition.fade)
@ -80,6 +87,7 @@ class AddTokensFragment : Fragment(R.layout.fragment_add_tokens),
toolbar.setNavigationOnClickListener { activity?.onBackPressed() }
val onSaveChanges = { tokens: List<TokenWithBlockchain>, blockchains: List<Blockchain> ->
Analytics.send(ManageTokens.ButtonSaveChanges())
store.dispatch(TokensAction.SaveChanges(tokens, blockchains))
}
val onNetworkItemClicked = { contractAddress: ContractAddress ->
@ -88,11 +96,11 @@ class AddTokensFragment : Fragment(R.layout.fragment_add_tokens),
}
val onLoadMore = {
store.dispatch(
TokensAction.LoadMore(
scanResponse = store.state.globalState.scanResponse
)
)
store.dispatch(
TokensAction.LoadMore(
scanResponse = store.state.globalState.scanResponse,
),
)
}
cvCurrencies.setContent {
@ -101,7 +109,7 @@ class AddTokensFragment : Fragment(R.layout.fragment_add_tokens),
tokensState = tokensState,
onSaveChanges = onSaveChanges,
onNetworkItemClicked = onNetworkItemClicked,
onLoadMore = onLoadMore
onLoadMore = onLoadMore,
)
}
}
@ -119,6 +127,7 @@ class AddTokensFragment : Fragment(R.layout.fragment_add_tokens),
return when (item.itemId) {
R.id.menu_search -> true
R.id.menu_navigate_add_custom_token -> {
Analytics.send(ManageTokens.ButtonCustomToken())
store.dispatch(TokensAction.PrepareAndNavigateToAddCustomToken)
true
}
@ -154,15 +163,18 @@ class AddTokensFragment : Fragment(R.layout.fragment_add_tokens),
}
fun SearchView.inputtedTextAsFlow(): Flow<String> = callbackFlow {
val watcher = setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean {
return false
}
val watcher =
setOnQueryTextListener(
object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean {
return false
}
override fun onQueryTextChange(newText: String?): Boolean {
trySend(newText ?: "")
return false
}
})
override fun onQueryTextChange(newText: String?): Boolean {
trySend(newText ?: "")
return false
}
},
)
awaitClose { (watcher) }
}

View file

@ -8,8 +8,16 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.ExtendedFloatingActionButton
import androidx.compose.material.FabPosition
import androidx.compose.material.Scaffold
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@ -20,6 +28,9 @@ import androidx.compose.ui.unit.dp
import com.tangem.blockchain.common.Blockchain
import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation
import com.tangem.domain.common.extensions.fromNetworkId
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.ManageTokens
import com.tangem.tap.common.compose.Keyboard
import com.tangem.tap.common.compose.extensions.addAndNotify
import com.tangem.tap.common.compose.extensions.removeAndNotify
@ -41,7 +52,7 @@ fun CurrenciesScreen(
tokensState: MutableState<TokensState> = mutableStateOf(store.state.tokensState),
onSaveChanges: (List<TokenWithBlockchain>, List<Blockchain>) -> Unit,
onNetworkItemClicked: (ContractAddress) -> Unit,
onLoadMore: () -> Unit
onLoadMore: () -> Unit,
) {
val tokensAddedOnMainScreen = remember { tokensState.value.addedTokens }
val blockchainsAddedOnMainScreen = remember { tokensState.value.addedBlockchains }
@ -82,21 +93,21 @@ fun CurrenciesScreen(
AnimatedVisibility(
visible = tokensState.value.loadCoinsState == LoadCoinsState.LOADING,
enter = fadeIn(),
exit = fadeOut()
exit = fadeOut(),
) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.fillMaxSize()
modifier = Modifier.fillMaxSize(),
) {
CircularProgressIndicator(
color = Color(0xFF1ACE80)
color = Color(0xFF1ACE80),
)
}
}
AnimatedVisibility(
visible = tokensState.value.loadCoinsState == LoadCoinsState.LOADED,
enter = fadeIn(animationSpec = tween(1000)),
exit = fadeOut(animationSpec = tween(1000))
exit = fadeOut(animationSpec = tween(1000)),
) {
Column {
val showHeader = tokensState.value.scanResponse?.card?.useOldStyleDerivation == true
@ -110,7 +121,7 @@ fun CurrenciesScreen(
onAddCurrencyToggleClick(currency, token)
},
onNetworkItemClicked = onNetworkItemClicked,
onLoadMore = onLoadMore
onLoadMore = onLoadMore,
)
}
}
@ -122,29 +133,39 @@ private fun toggleBlockchain(
blockchain: Blockchain,
blockchainsAddedOnMainScreen: List<Blockchain>,
addedBlockchainsState: MutableState<List<Blockchain>>,
addedTokens: List<TokenWithBlockchain>
addedTokens: List<TokenWithBlockchain>,
) {
val isTryingToRemove = addedBlockchainsState.value.contains(blockchain)
val isAddedOnMainScreen = blockchainsAddedOnMainScreen.contains(blockchain)
val isTokenWithSameBlockchainFound = addedTokens.any { it.blockchain == blockchain }
val analyticsCurrencyTypeParam = AnalyticsParam.CurrencyType.Blockchain(blockchain)
if (isTryingToRemove) {
if (isTokenWithSameBlockchainFound) {
store.dispatchDialogShow(WalletDialog.TokensAreLinkedDialog(
currencyTitle = blockchain.name,
currencySymbol = blockchain.currency
))
store.dispatchDialogShow(
WalletDialog.TokensAreLinkedDialog(
currencyTitle = blockchain.name,
currencySymbol = blockchain.currency,
),
)
} else {
if (isAddedOnMainScreen) {
store.dispatchDialogShow(WalletDialog.RemoveWalletDialog(
currencyTitle = blockchain.name,
onOk = { addedBlockchainsState.removeAndNotify(blockchain) }
))
store.dispatchDialogShow(
WalletDialog.RemoveWalletDialog(
currencyTitle = blockchain.name,
onOk = {
analyticsCurrencyTypeParam.sendOn()
addedBlockchainsState.removeAndNotify(blockchain)
},
),
)
} else {
analyticsCurrencyTypeParam.sendOff()
addedBlockchainsState.removeAndNotify(blockchain)
}
}
} else {
analyticsCurrencyTypeParam.sendOn()
addedBlockchainsState.addAndNotify(blockchain)
}
}
@ -158,28 +179,46 @@ private fun toggleToken(
val isTryingToRemove = addedTokensState.value.contains(token)
val isAddedOnMainScreen = tokensAddedOnMainScreen.contains(token)
val isUnsupportedToken = !tokensState.canHandleToken(token)
val analyticsCurrencyTypeParam = AnalyticsParam.CurrencyType.Token(token.token)
if (isTryingToRemove) {
if (isAddedOnMainScreen) {
store.dispatchDialogShow(WalletDialog.RemoveWalletDialog(
currencyTitle = token.token.name,
onOk = { addedTokensState.removeAndNotify(token) }
))
store.dispatchDialogShow(
WalletDialog.RemoveWalletDialog(
currencyTitle = token.token.name,
onOk = {
analyticsCurrencyTypeParam.sendOff()
addedTokensState.removeAndNotify(token)
},
),
)
} else {
analyticsCurrencyTypeParam.sendOff()
addedTokensState.removeAndNotify(token)
}
} else {
if (isUnsupportedToken) {
store.dispatchDialogShow(AppDialog.SimpleOkDialogRes(
headerId = R.string.common_warning,
messageId = R.string.alert_manage_tokens_unsupported_message,
))
store.dispatchDialogShow(
AppDialog.SimpleOkDialogRes(
headerId = R.string.common_warning,
messageId = R.string.alert_manage_tokens_unsupported_message,
),
)
} else {
analyticsCurrencyTypeParam.sendOn()
addedTokensState.addAndNotify(token)
}
}
}
private fun AnalyticsParam.CurrencyType.sendOn() {
Analytics.send(ManageTokens.TokenSwitcherChanged(this, AnalyticsParam.OnOffState.On))
}
private fun AnalyticsParam.CurrencyType.sendOff() {
Analytics.send(ManageTokens.TokenSwitcherChanged(this, AnalyticsParam.OnOffState.Off))
}
@Composable
fun SaveChangesButton(keyboardState: Keyboard, onSaveChanges: () -> Unit) {
@ -198,6 +237,6 @@ fun SaveChangesButton(keyboardState: Keyboard, onSaveChanges: () -> Unit) {
onClick = onSaveChanges,
backgroundColor = colorResource(id = R.color.accent),
contentColor = Color.White,
modifier = Modifier.padding(bottom = padding.dp)
modifier = Modifier.padding(bottom = padding.dp),
)
}

View file

@ -15,6 +15,7 @@ import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.features.wallet.redux.models.WalletDialog
import com.tangem.wallet.R
import org.rekotlin.Action
import java.math.BigDecimal
@ -154,7 +155,9 @@ sealed class WalletAction : Action {
object SignedHashesMultiWalletDialog : DialogAction()
object ChooseTradeActionDialog : DialogAction()
data class ChooseCurrency(val amounts: List<Amount>?) : DialogAction()
data class RussianCardholdersWarningDialog(val topUpUrl: String? = null) : DialogAction()
data class RussianCardholdersWarningDialog(
val dialogData: WalletDialog.RussianCardholdersWarningDialog.Data? = null
) : DialogAction()
object Hide : DialogAction()
}

View file

@ -3,6 +3,9 @@ package com.tangem.tap.features.wallet.redux.middlewares
import com.tangem.common.services.Result
import com.tangem.network.api.tangemTech.CurrenciesResponse
import com.tangem.network.api.tangemTech.TangemTechService
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.MainScreen
import com.tangem.tap.common.entities.FiatCurrency
import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.redux.global.GlobalAction
@ -61,6 +64,7 @@ class AppCurrencyMiddleware(
}
private fun selectCurrency(action: WalletAction.AppCurrencyAction.SelectAppCurrency) {
Analytics.send(MainScreen.MainCurrencyChanged(AnalyticsParam.CurrencyType.FiatCurrency(action.fiatCurrency)))
tapWalletManager.rates.clear()
fiatCurrenciesPrefStorage.saveAppCurrency(action.fiatCurrency)
store.dispatch(GlobalAction.ChangeAppCurrency(action.fiatCurrency))

View file

@ -5,6 +5,9 @@ import com.tangem.blockchain.common.Token
import com.tangem.blockchain.common.WalletManager
import com.tangem.common.extensions.guard
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.Token.ButtonRemoveToken
import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.extensions.dispatchErrorNotification
import com.tangem.tap.common.extensions.safeUpdate
@ -109,6 +112,7 @@ class MultiWalletMiddleware {
WalletDialog.RemoveWalletDialog(
currencyTitle = currency.currencyName,
onOk = {
Analytics.send(ButtonRemoveToken(AnalyticsParam.CurrencyType.Currency(currency)))
store.dispatch(WalletAction.MultiWallet.RemoveWallet(currency))
store.dispatch(NavigationAction.PopBackTo())
},

View file

@ -2,8 +2,12 @@ package com.tangem.tap.features.wallet.redux.middlewares
import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager
import com.tangem.blockchain.common.AmountType
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.Token
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.dispatchOpenUrl
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
@ -19,7 +23,6 @@ import com.tangem.tap.scope
import com.tangem.tap.store
import kotlinx.coroutines.launch
class TradeCryptoMiddleware {
fun handle(state: () -> AppState?, action: WalletAction.TradeCryptoAction) {
if (DemoHelper.tryHandle(state, action)) return
@ -50,6 +53,7 @@ class TradeCryptoMiddleware {
val exchangeManager = store.state.globalState.exchangeManager
val appCurrency = store.state.globalState.appCurrency
val currency = selectedWalletData.currency
Analytics.send(Token.ButtonBuy(AnalyticsParam.CurrencyType.Currency(currency)))
if (currency is Currency.Token && currency.blockchain.isTestnet()) {
val walletManager = store.state.walletState.getWalletManager(currency)
@ -62,7 +66,7 @@ class TradeCryptoMiddleware {
exchangeManager.buyErc20TestnetTokens(
card = card,
walletManager = walletManager,
token = currency.token
token = currency.token,
)
}
return
@ -73,8 +77,11 @@ class TradeCryptoMiddleware {
blockchain = currency.blockchain,
cryptoCurrencyName = currency.currencySymbol,
fiatCurrencyName = appCurrency.code,
walletAddress = addresses[0].address
)?.let { store.dispatchOnMain(NavigationAction.OpenUrl(it)) }
walletAddress = addresses[0].address,
)?.let {
store.dispatchOpenUrl(it)
Analytics.send(Token.Topup.ScreenOpened())
}
}
private fun proceedSellAction() {
@ -85,29 +92,40 @@ class TradeCryptoMiddleware {
if (addresses.isEmpty()) return
val currency = selectedWalletData.currency
Analytics.send(Token.ButtonSell(AnalyticsParam.CurrencyType.Currency(currency)))
store.state.globalState.exchangeManager.getUrl(
action = CurrencyExchangeManager.Action.Sell,
blockchain = currency.blockchain,
cryptoCurrencyName = currency.currencySymbol,
fiatCurrencyName = appCurrency.code,
walletAddress = addresses[0].address
)?.let { store.dispatchOnMain(NavigationAction.OpenUrl(it)) }
walletAddress = addresses[0].address,
)?.let {
store.dispatchOpenUrl(it)
Analytics.send(Token.Withdraw.ScreenOpened())
}
}
private fun preconfigureAndOpenSendScreen(action: WalletAction.TradeCryptoAction.SendCrypto) {
val selectedWalletData = store.state.walletState.getSelectedWalletData() ?: return
Analytics.send(Token.ButtonSend(AnalyticsParam.CurrencyType.Currency(selectedWalletData.currency)))
val walletManager = store.state.walletState.getWalletManager(selectedWalletData.currency)
store.dispatchOnMain(PrepareSendScreen(
coinAmount = walletManager?.wallet?.amounts?.get(AmountType.Coin),
coinRate = selectedWalletData.fiatRate,
walletManager = walletManager
))
store.dispatchOnMain(SendAction.SendSpecificTransaction(
sendAmount = action.amount,
destinationAddress = action.destinationAddress,
transactionId = action.transactionId
))
store.dispatchOnMain(
PrepareSendScreen(
coinAmount = walletManager?.wallet?.amounts?.get(AmountType.Coin),
coinRate = selectedWalletData.fiatRate,
walletManager = walletManager,
),
)
store.dispatchOnMain(
SendAction.SendSpecificTransaction(
sendAmount = action.amount,
destinationAddress = action.destinationAddress,
transactionId = action.transactionId,
),
)
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Send))
}
@ -116,6 +134,6 @@ class TradeCryptoMiddleware {
store.state.globalState.exchangeManager.getSellCryptoReceiptUrl(
action = CurrencyExchangeManager.Action.Sell,
transactionId = transactionId,
)?.let { store.dispatchOnMain(NavigationAction.OpenUrl(it)) }
)?.let { store.dispatchOpenUrl(it) }
}
}

View file

@ -1,5 +1,8 @@
package com.tangem.tap.features.wallet.redux.middlewares
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.Token
import com.tangem.tap.common.extensions.dispatchDialogHide
import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.redux.AppDialog
@ -14,6 +17,9 @@ class WalletDialogsMiddleware {
store.dispatchDialogShow(WalletDialog.SignedHashesMultiWalletDialog)
}
is WalletAction.DialogAction.ChooseTradeActionDialog -> {
store.state.walletState.getSelectedWalletData()?.let {
Analytics.send(Token.ButtonExchange(AnalyticsParam.CurrencyType.Currency(it.currency)))
}
store.dispatchDialogShow(WalletDialog.ChooseTradeActionDialog)
}
is WalletAction.DialogAction.QrCode -> {
@ -21,18 +27,18 @@ class WalletDialogsMiddleware {
AppDialog.AddressInfoDialog(
currency = action.currency,
addressData = action.selectedAddress,
)
),
)
}
is WalletAction.DialogAction.ChooseCurrency -> {
store.dispatchDialogShow(
WalletDialog.SelectAmountToSendDialog(
amounts = action.amounts
)
amounts = action.amounts,
),
)
}
is WalletAction.DialogAction.RussianCardholdersWarningDialog -> {
store.dispatchDialogShow(WalletDialog.RussianCardholdersWarningDialog(action.topUpUrl))
store.dispatchDialogShow(WalletDialog.RussianCardholdersWarningDialog(action.dialogData))
}
is WalletAction.DialogAction.Hide -> {
store.dispatchDialogHide()

View file

@ -6,13 +6,13 @@ import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.WalletManager
import com.tangem.common.CompletionResult
import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.isZero
import com.tangem.common.services.Result
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.operations.attestation.Attestation
import com.tangem.operations.attestation.OnlineCardVerifier
import com.tangem.tap.common.analytics.AnalyticsAnOld
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.events.Token
import com.tangem.tap.common.extensions.copyToClipboard
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
import com.tangem.tap.common.extensions.dispatchErrorNotification
@ -189,15 +189,7 @@ class WalletMiddleware {
globalState.scanResponse?.copy(card = result.data)
scanNoteResponse?.let { store.onCardScanned(scanNoteResponse) }
}
is CompletionResult.Failure -> {
(result.error as? TangemSdkError)?.let { error ->
store.state.globalState.analyticsHandler.handleCardSdkErrorEvent(
error,
AnalyticsAnOld.ActionToLog.CreateWallet,
card = store.state.detailsState.scanResponse?.card,
)
}
}
is CompletionResult.Failure -> {}
}
}
}
@ -251,6 +243,7 @@ class WalletMiddleware {
action.context.shareText(action.address)
}
is WalletAction.ExploreAddress -> {
Analytics.send(Token.ButtonExplore())
store.dispatchOpenUrl(action.exploreUrl)
}
is WalletAction.Send -> {

View file

@ -8,7 +8,6 @@ import com.tangem.common.card.Card
import com.tangem.common.card.FirmwareVersion
import com.tangem.domain.common.ScanResponse
import com.tangem.domain.common.TapWorkarounds.isTestCard
import com.tangem.tap.common.analytics.AnalyticsEventAnOld
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.isGreaterThan
import com.tangem.tap.common.redux.global.GlobalState
@ -54,14 +53,10 @@ class WarningsMiddleware {
if (action.remainingSignatures != null &&
action.remainingSignatures <= WarningMessagesManager.REMAINING_SIGNATURES_WARNING
) {
store.state.globalState.warningManager
?.removeWarnings(
messageRes = R.string.warning_low_signatures_format
)
store.state.globalState.warningManager?.removeWarnings(R.string.warning_low_signatures_format)
addWarningMessage(
warning =
WarningMessagesManager.remainingSignaturesNotEnough(action.remainingSignatures),
autoUpdate = true
warning = WarningMessagesManager.remainingSignaturesNotEnough(action.remainingSignatures),
autoUpdate = true,
)
}
}
@ -69,7 +64,8 @@ class WarningsMiddleware {
is WalletAction.Warnings.CheckHashesCount,
is WalletAction.Warnings.CheckHashesCount.ConfirmHashesCount,
is WalletAction.Warnings.CheckHashesCount.NeedToCheckHashesCountOnline,
is WalletAction.Warnings.Set -> Unit
is WalletAction.Warnings.Set,
-> Unit
}
}
@ -81,7 +77,6 @@ class WarningsMiddleware {
preferencesStorage.appRatingLaunchObserver.foundWalletWithFunds()
}
if (preferencesStorage.appRatingLaunchObserver.isReadyToShow()) {
store.state.globalState.analyticsHandler?.handleAnalyticsEvent(AnalyticsEventAnOld.APP_RATING_DISPLAYED)
addWarningMessage(WarningMessagesManager.appRatingWarning(), true)
}
}
@ -118,11 +113,7 @@ class WarningsMiddleware {
if (remainingSignatures != null &&
remainingSignatures <= WarningMessagesManager.REMAINING_SIGNATURES_WARNING
) {
addWarningMessage(
WarningMessagesManager.remainingSignaturesNotEnough(
remainingSignatures
)
)
addWarningMessage(WarningMessagesManager.remainingSignaturesNotEnough(remainingSignatures))
}
}
@ -178,15 +169,9 @@ class WarningsMiddleware {
}
is SimpleResult.Failure ->
if (result.error is BlockchainSdkError.SignatureCountNotMatched) {
addWarningMessage(
WarningMessagesManager.alreadySignedHashesWarning(),
true
)
addWarningMessage(WarningMessagesManager.alreadySignedHashesWarning(), true)
} else if (signedHashes > 0) {
addWarningMessage(
WarningMessagesManager.alreadySignedHashesWarning(),
true
)
addWarningMessage(WarningMessagesManager.alreadySignedHashesWarning(), true)
}
null -> Unit
}
@ -207,7 +192,7 @@ class WarningsMiddleware {
val warningManager = store.state.globalState.warningManager ?: return emptyList()
return warningManager.getWarnings(
WarningMessage.Location.MainScreen,
store.state.walletState.blockchains
store.state.walletState.blockchains,
)
}
}

View file

@ -1,6 +1,7 @@
package com.tangem.tap.features.wallet.redux.models
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.Blockchain
import com.tangem.tap.common.entities.FiatCurrency
import com.tangem.tap.common.redux.StateDialog
import com.tangem.wallet.R
@ -31,5 +32,7 @@ sealed interface WalletDialog : StateDialog {
val titleRes: Int = R.string.token_details_unable_hide_alert_title
}
data class RussianCardholdersWarningDialog(val topUpUrl: String?) : WalletDialog
data class RussianCardholdersWarningDialog(val data: Data?) : WalletDialog {
data class Data(val topUpUrl: String, val blockchain: Blockchain)
}
}

View file

@ -17,6 +17,12 @@ fun List<WalletData>.calculateTotalFiatAmount(): BigDecimal {
.reduce(BigDecimal::plus)
}
fun List<WalletData>.calculateTotalCryptoAmount(): BigDecimal {
return this
.map { it.currencyData.amount ?: BigDecimal.ZERO }
.reduce(BigDecimal::plus)
}
private fun List<WalletData>.mapToProgressState(): List<ProgressState> {
return this.map { wallet ->
if (wallet.fiatRate == null) ProgressState.Error
@ -25,12 +31,15 @@ private fun List<WalletData>.mapToProgressState(): List<ProgressState> {
BalanceStatus.VerifiedOnline,
BalanceStatus.SameCurrencyTransactionInProgress,
BalanceStatus.TransactionInProgress,
BalanceStatus.NoAccount -> ProgressState.Done
BalanceStatus.NoAccount,
-> ProgressState.Done
BalanceStatus.Unreachable,
BalanceStatus.EmptyCard,
BalanceStatus.UnknownBlockchain -> ProgressState.Error
BalanceStatus.UnknownBlockchain,
-> ProgressState.Error
BalanceStatus.Loading,
null -> ProgressState.Loading
null,
-> ProgressState.Loading
}
}
}
@ -41,20 +50,24 @@ private infix fun ProgressState.or(newState: ProgressState): ProgressState {
ProgressState.Loading,
ProgressState.Refreshing,
ProgressState.Error,
ProgressState.Done -> this
ProgressState.Done,
-> this
}
ProgressState.Done,
ProgressState.Error -> when (newState) {
ProgressState.Error,
-> when (newState) {
ProgressState.Loading,
ProgressState.Refreshing,
ProgressState.Error -> newState
ProgressState.Error,
-> newState
ProgressState.Done -> this
}
ProgressState.Refreshing -> when (newState) {
ProgressState.Loading -> this
ProgressState.Refreshing,
ProgressState.Error,
ProgressState.Done -> newState
ProgressState.Done,
-> newState
}
}
}

View file

@ -19,6 +19,9 @@ import com.tangem.domain.common.TapWorkarounds.derivationStyle
import com.tangem.tangem_sdk_new.extensions.dpToPx
import com.tangem.tap.common.SnackbarHandler
import com.tangem.tap.common.TestActions
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.events.DetailsScreen
import com.tangem.tap.common.analytics.events.Token
import com.tangem.tap.common.extensions.appendIfNotNull
import com.tangem.tap.common.extensions.beginDelayedTransition
import com.tangem.tap.common.extensions.fitChipsByGroupWidth
@ -59,12 +62,17 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
store.dispatch(WalletAction.MultiWallet.SelectWallet(null))
store.dispatch(NavigationAction.PopBackTo())
}
})
Analytics.send(DetailsScreen.ScreenOpened())
activity?.onBackPressedDispatcher?.addCallback(
this,
object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
store.dispatch(WalletAction.MultiWallet.SelectWallet(null))
store.dispatch(NavigationAction.PopBackTo())
}
},
)
val inflater = TransitionInflater.from(requireContext())
enterTransition = inflater.inflateTransition(R.transition.slide_right)
exitTransition = inflater.inflateTransition(R.transition.fade)
@ -125,7 +133,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
view?.findViewById<View>(R.id.l_balance)?.let { view ->
TestActions.initFor(
view = view,
actions = TestWallet.solanaRentExemptWarning()
actions = TestWallet.solanaRentExemptWarning(),
)
}
}
@ -149,13 +157,15 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
binding.srlWalletDetails.setOnRefreshListener {
if (selectedWallet.currencyData.status != BalanceStatus.Loading) {
store.dispatch(WalletAction.LoadWallet(
blockchain = BlockchainNetwork(
selectedWallet.currency.blockchain,
selectedWallet.currency.derivationPath,
emptyList()
)
)
Analytics.send(Token.Refreshed())
store.dispatch(
WalletAction.LoadWallet(
blockchain = BlockchainNetwork(
selectedWallet.currency.blockchain,
selectedWallet.currency.derivationPath,
emptyList(),
),
),
)
}
}
@ -183,7 +193,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
tvCurrencyTitle.text = currencyData.currency
tvCurrencySubtitle.text = tvCurrencySubtitle.getString(
R.string.wallet_currency_subtitle,
currency.blockchain.fullName
currency.blockchain.fullName,
)
}
@ -225,7 +235,6 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
)
}
private fun showPendingTransactionsIfPresent(pendingTransactions: List<PendingTransaction>) {
pendingTransactionAdapter.submitList(pendingTransactions)
binding.rvPendingTransaction.show(pendingTransactions.isNotEmpty())
@ -256,8 +265,8 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
store.dispatch(
WalletAction.ExploreAddress(
state.walletAddresses.selectedAddress.exploreUrl,
requireContext()
)
requireContext(),
),
)
}
ivQrCode.setImageBitmap(state.walletAddresses.selectedAddress.shareUrl.toQrCode())
@ -272,7 +281,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
binding.srlWalletDetails.isRefreshing = false
(activity as? SnackbarHandler)?.showSnackbar(
text = R.string.wallet_notification_no_internet,
buttonTitle = R.string.common_retry
buttonTitle = R.string.common_retry,
) { store.dispatch(WalletAction.LoadData) }
}
} else {
@ -292,7 +301,8 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
lBalance.tvStatus.setLoadingStatus(R.string.wallet_balance_loading)
}
BalanceStatus.VerifiedOnline, BalanceStatus.SameCurrencyTransactionInProgress,
BalanceStatus.TransactionInProgress -> {
BalanceStatus.TransactionInProgress,
-> {
lBalanceError.root.hide()
lBalance.root.show()
lBalance.groupBalance.show()
@ -315,7 +325,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
lBalance.tvError.show()
lBalance.tvError.setWarningStatus(
R.string.wallet_balance_blockchain_unreachable,
data.errorMessage
data.errorMessage,
)
}
BalanceStatus.NoAccount -> {
@ -325,7 +335,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
lBalanceError.tvErrorDescriptions.text =
getString(
R.string.wallet_error_no_account_subtitle_format,
data.amountToCreateAccount, data.currencySymbol
data.amountToCreateAccount, data.currencySymbol,
)
}
}
@ -364,7 +374,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
private fun TextView.setStatus(
text: String,
@ColorRes color: Int,
@DrawableRes drawable: Int?
@DrawableRes drawable: Int?,
) {
this.text = text
setTextColor(getColor(color))

View file

@ -16,6 +16,11 @@ import coil.load
import coil.size.Scale
import com.tangem.domain.common.TapWorkarounds.isSaltPay
import com.tangem.tap.MainActivity
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.converters.BasicSignInEventConverter
import com.tangem.tap.common.analytics.converters.BasicTopUpEventConverter
import com.tangem.tap.common.analytics.events.MainScreen
import com.tangem.tap.common.analytics.events.Portfolio
import com.tangem.tap.common.extensions.show
import com.tangem.tap.common.recyclerView.SpaceItemDecoration
import com.tangem.tap.common.redux.global.GlobalAction
@ -52,6 +57,8 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
Analytics.send(MainScreen.ScreenOpened())
activity?.onBackPressedDispatcher?.addCallback(
this,
object : OnBackPressedCallback(true) {
@ -71,8 +78,6 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
state.select { it.walletState }
}
walletView.setFragment(this, binding)
// store.dispatch(WalletAction.UpdateWallet(force = false))
// store.dispatch(WalletAction.LoadWallet())
}
override fun onStop() {
@ -86,6 +91,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
(activity as? AppCompatActivity)?.setSupportActionBar(binding.toolbar)
binding.toolbar.setNavigationOnClickListener {
Analytics.send(MainScreen.ButtonScanCard())
store.dispatch(WalletAction.Scan)
}
setupWarningsRecyclerView()
@ -115,6 +121,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
override fun newState(state: WalletState) {
if (activity == null || view == null) return
handleBasicAnalyticsEvent(state)
val isSaltPay = store.state.globalState.scanResponse?.card?.isSaltPay == true
when {
@ -152,6 +159,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
if (state.state != ProgressState.Loading ||
state.state != ProgressState.Refreshing
) {
Analytics.send(Portfolio.Refreshed())
store.dispatch(WalletAction.LoadData.Refresh)
}
}
@ -221,4 +229,11 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
if (store.state.walletState.shouldShowDetails) inflater.inflate(R.menu.menu_wallet, menu)
}
private fun handleBasicAnalyticsEvent(state: WalletState) {
val scanResponse = store.state.globalState.scanResponse ?: return
BasicSignInEventConverter(scanResponse).convert(state)?.let { Analytics.send(it) }
BasicTopUpEventConverter(scanResponse).convert(state)?.let { Analytics.send(it) }
}
}

View file

@ -9,6 +9,8 @@ import androidx.recyclerview.widget.RecyclerView
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
import com.tangem.domain.common.TapWorkarounds.derivationStyle
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.events.Portfolio
import com.tangem.tap.common.extensions.getString
import com.tangem.tap.common.extensions.hide
import com.tangem.tap.common.extensions.show
@ -20,8 +22,7 @@ import com.tangem.tap.store
import com.tangem.wallet.R
import com.tangem.wallet.databinding.ItemCurrencyWalletBinding
class WalletAdapter
: ListAdapter<WalletData, WalletAdapter.WalletsViewHolder>(DiffUtilCallback) {
class WalletAdapter : ListAdapter<WalletData, WalletAdapter.WalletsViewHolder>(DiffUtilCallback) {
override fun getItemId(position: Int): Long {
return currentList[position].currencyData.currencySymbol?.hashCode()?.toLong() ?: 0
@ -30,7 +31,7 @@ class WalletAdapter
fun submitList(
list: List<WalletData>,
primaryBlockchain: Blockchain?,
primaryToken: Token? = null
primaryToken: Token? = null,
) {
// We used this method to sort the list of currencies. Sorting is disabled for now.
super.submitList(list)
@ -38,7 +39,7 @@ class WalletAdapter
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WalletsViewHolder {
val layout = ItemCurrencyWalletBinding.inflate(
LayoutInflater.from(parent.context), parent, false
LayoutInflater.from(parent.context), parent, false,
)
return WalletsViewHolder(layout)
}
@ -49,11 +50,11 @@ class WalletAdapter
object DiffUtilCallback : DiffUtil.ItemCallback<WalletData>() {
override fun areContentsTheSame(
oldItem: WalletData, newItem: WalletData
oldItem: WalletData, newItem: WalletData,
) = oldItem == newItem
override fun areItemsTheSame(
oldItem: WalletData, newItem: WalletData
oldItem: WalletData, newItem: WalletData,
) = oldItem == newItem
}
@ -112,6 +113,7 @@ class WalletAdapter
if (wallet.walletAddresses != null) {
cardWallet.setOnClickListener {
Analytics.send(Portfolio.TokenTapped())
store.dispatch(WalletAction.MultiWallet.SelectWallet(wallet))
}
} else {

View file

@ -7,7 +7,9 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.google.android.play.core.review.ReviewManagerFactory
import com.tangem.tap.common.analytics.AnalyticsEventAnOld
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.MainScreen
import com.tangem.tap.common.extensions.getActivity
import com.tangem.tap.common.extensions.getColor
import com.tangem.tap.common.extensions.getString
@ -26,7 +28,7 @@ class WarningMessagesAdapter : ListAdapter<WarningMessage, WarningMessageVH>(Dif
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WarningMessageVH {
val binding = LayoutWarningCardActionBinding.inflate(
LayoutInflater.from(parent.context), parent, false
LayoutInflater.from(parent.context), parent, false,
)
return WarningMessageVH(binding)
}
@ -60,7 +62,7 @@ class WarningMessageVH(val binding: LayoutWarningCardActionBinding) : RecyclerVi
tvMessage.text = getString(
resId = warning.messageResId,
default = warning.message,
formatArgs = warning.messageFormatArg
formatArgs = warning.messageFormatArg,
)
}
@ -98,7 +100,7 @@ class WarningMessageVH(val binding: LayoutWarningCardActionBinding) : RecyclerVi
}
}
val buttonTitle = binding.root.getString(
warning.buttonTextId ?: R.string.how_to_got_it_button
warning.buttonTextId ?: R.string.how_to_got_it_button,
)
binding.btnGotIt.setOnClickListener(buttonAction)
binding.btnGotIt.text = buttonTitle
@ -107,14 +109,13 @@ class WarningMessageVH(val binding: LayoutWarningCardActionBinding) : RecyclerVi
binding.groupControlsTemporary.hide()
binding.groupControlsRating.show()
binding.btnClose.show()
val analyticsHandler = store.state.globalState.analyticsHandler
binding.btnClose.setOnClickListener {
analyticsHandler?.handleAnalyticsEvent(AnalyticsEventAnOld.APP_RATING_DISMISS)
Analytics.send(MainScreen.NoticeRateAppButton(AnalyticsParam.RateApp.Closed))
store.dispatch(GlobalAction.HideWarningMessage(warning))
store.dispatch(WalletAction.Warnings.AppRating.RemindLater)
}
binding.btnCanBeBetter.setOnClickListener {
analyticsHandler?.handleAnalyticsEvent(AnalyticsEventAnOld.APP_RATING_NEGATIVE)
Analytics.send(MainScreen.NoticeRateAppButton(AnalyticsParam.RateApp.Disliked))
store.dispatch(WalletAction.Warnings.AppRating.SetNeverToShow)
store.dispatch(GlobalAction.HideWarningMessage(warning))
store.dispatch(GlobalAction.SendEmail(RateCanBeBetterEmail()))
@ -122,7 +123,7 @@ class WarningMessageVH(val binding: LayoutWarningCardActionBinding) : RecyclerVi
binding.btnReallyCool.setOnClickListener {
val activity = binding.root.context.getActivity() ?: return@setOnClickListener
analyticsHandler?.handleAnalyticsEvent(AnalyticsEventAnOld.APP_RATING_POSITIVE)
Analytics.send(MainScreen.NoticeRateAppButton(AnalyticsParam.RateApp.Liked))
store.dispatch(WalletAction.Warnings.AppRating.SetNeverToShow)
val reviewManager = ReviewManagerFactory.create(activity)
val task = reviewManager.requestReviewFlow()

View file

@ -4,15 +4,21 @@ import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.Onboarding
import com.tangem.tap.common.analytics.events.Token
import com.tangem.tap.common.extensions.dispatchDialogHide
import com.tangem.tap.common.extensions.dispatchOpenUrl
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.features.wallet.redux.models.WalletDialog
import com.tangem.tap.store
import com.tangem.wallet.databinding.DialogRussiansCardholdersWarningBinding
class RussianCardholdersWarningBottomSheetDialog(
context: Context, private val topUpUrl: String?,
context: Context,
private val dialogData: WalletDialog.RussianCardholdersWarningDialog.Data?,
) : BottomSheetDialog
(context) {
@ -20,6 +26,7 @@ class RussianCardholdersWarningBottomSheetDialog(
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Analytics.send(Token.Topup.P2PScreenOpened())
binding = DialogRussiansCardholdersWarningBinding
.inflate(LayoutInflater.from(context))
.also { setContentView(it.root) }
@ -33,8 +40,10 @@ class RussianCardholdersWarningBottomSheetDialog(
}
binding?.btnYes?.setOnClickListener {
if (topUpUrl != null) {
store.dispatchOpenUrl(topUpUrl)
if (dialogData != null) {
val currencyType = AnalyticsParam.CurrencyType.Blockchain(dialogData.blockchain)
Analytics.send(Onboarding.Topup.ButtonBuyCrypto(currencyType))
store.dispatchOpenUrl(dialogData.topUpUrl)
} else {
store.dispatch(WalletAction.TradeCryptoAction.Buy(checkUserLocation = false))
}

View file

@ -2,6 +2,8 @@ package com.tangem.tap.features.wallet.ui.dialogs
import android.content.Context
import androidx.appcompat.app.AlertDialog
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.events.IntroductionProcess
import com.tangem.tap.common.extensions.dispatchDialogHide
import com.tangem.tap.common.feedback.ScanFailsEmail
import com.tangem.tap.common.redux.global.GlobalAction
@ -19,6 +21,7 @@ class ScanFailsDialog {
setTitle(context.getString(R.string.common_warning))
setMessage(R.string.alert_troubleshooting_scan_card_title)
setPositiveButton(R.string.alert_button_request_support) { _, _ ->
Analytics.send(IntroductionProcess.ButtonRequestSupport())
store.dispatch(GlobalAction.SendEmail(ScanFailsEmail()))
}
setNeutralButton(R.string.alert_troubleshooting_scan_card_ok) { _, _ -> }

View file

@ -6,6 +6,9 @@ import androidx.recyclerview.widget.LinearLayoutManager
import com.tangem.common.card.Card
import com.tangem.domain.common.TapWorkarounds.derivationStyle
import com.tangem.domain.common.TapWorkarounds.isTestCard
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.events.MainScreen
import com.tangem.tap.common.analytics.events.Portfolio
import com.tangem.tap.common.extensions.animateVisibility
import com.tangem.tap.common.extensions.formatAmountAsSpannedString
import com.tangem.tap.common.extensions.getQuantityString
@ -85,6 +88,7 @@ class MultiWalletView : WalletView() {
binding.btnAddToken.setOnClickListener {
val card = store.state.globalState.scanResponse!!.card
Analytics.send(Portfolio.ButtonManageTokens())
store.dispatch(
TokensAction.LoadCurrencies(
supportedBlockchains = CurrenciesRepository.getBlockchains(
@ -112,6 +116,7 @@ class MultiWalletView : WalletView() {
) = with(binding.lWalletBackupWarning) {
root.isVisible = showBackupWarning
root.setOnClickListener {
Analytics.send(MainScreen.NoticeBackupYourWalletTapped())
store.dispatch(WalletAction.MultiWallet.BackupWallet)
}
}
@ -122,6 +127,7 @@ class MultiWalletView : WalletView() {
) = with(binding.lWalletRescanWarning) {
root.isVisible = showRescanWarning
root.setOnClickListener {
Analytics.send(MainScreen.NoticeScanYourCardTapped())
store.dispatch(WalletAction.MultiWallet.ScanToGetDerivations)
}
}