From d7b674ea892ebbf31114df58d944ee1c723b86c6 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 1 Nov 2022 14:07:13 +0500 Subject: [PATCH] Updated on 2026-08-14 --- .../common/analytics/events/AnalyticsParam.kt | 4 +- .../common/analytics/events/ManageTokens.kt | 46 ++++++++-- .../addCustomToken/AddCustomTokenFragment.kt | 28 +++--- .../features/tokens/redux/TokensMiddleware.kt | 4 + .../features/tokens/ui/AddTokensFragment.kt | 54 ++++++----- .../tokens/ui/compose/CurrenciesScreen.kt | 91 +++++++++++++------ 6 files changed, 159 insertions(+), 68 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt index 5e3f8a81c5..9996f479b7 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt @@ -10,7 +10,7 @@ sealed class AnalyticsParam { class Token(token: com.tangem.blockchain.common.Token) : CurrencyType(token.symbol) } - // Multicurrency or CurrencyType + // MultiCurrency or CurrencyType sealed class CardCurrency(val value: String) { object MultiCurrency : CardCurrency("Multicurrency") class SingleCurrency(type: CurrencyType) : CardCurrency(type.value) @@ -43,7 +43,7 @@ sealed class AnalyticsParam { object LongTap : SecurityMode("Long Tap") companion object { - fun from(option: SecurityOption): SecurityMode = when(option){ + fun from(option: SecurityOption): SecurityMode = when (option) { SecurityOption.AccessCode -> AccessCode SecurityOption.PassCode -> Passcode SecurityOption.LongTap -> LongTap diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/ManageTokens.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/ManageTokens.kt index e5e3fdb96a..9f3ec194b7 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/ManageTokens.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/ManageTokens.kt @@ -1,5 +1,9 @@ package com.tangem.tap.common.analytics.events +import com.tangem.domain.common.extensions.toNetworkId +import com.tangem.domain.features.addCustomToken.CustomCurrency +import com.tangem.tap.common.extensions.filterNotNull + /** [REDACTED_AUTHOR] */ @@ -10,7 +14,18 @@ sealed class ManageTokens( class ScreenOpened : ManageTokens("Manage Tokens Screen Opened") class TokenSearched : ManageTokens("Token Searched") - class TokenSwitcherChanged : ManageTokens("Token Switcher Changed") + + class TokenSwitcherChanged( + type: AnalyticsParam.CurrencyType, + state: AnalyticsParam.OnOffState, + ) : ManageTokens( + "Token Switcher Changed", + params = mapOf( + "Token" to type.value, + "State" to state.value, + ), + ) + class ButtonSaveChanges : ManageTokens("Button - Save Changes") class ButtonCustomToken : ManageTokens("Button - Custom Token") @@ -21,13 +36,28 @@ sealed class ManageTokens( class ScreenOpened : ManageTokens("Custom Token Screen Opened") - class TokenWasAdded(symbol: String, network: String, address: String) : ManageTokens( + class TokenWasAdded(customCurrency: CustomCurrency) : ManageTokens( event = "Custom Token Was Added", - params = mapOf( - "Symbol" to symbol, - "Network" to network, - "Address" to address, - ), - ) + params = convertToParam(customCurrency), + ) { + companion object { + private fun convertToParam(customCurrency: CustomCurrency): Map = with(customCurrency) { + return when (this) { + is CustomCurrency.CustomBlockchain -> mapOf( + "Network" to network.toNetworkId(), + "Symbol" to network.currency, + "DerivationPath" to derivationPath?.rawPath, + ).filterNotNull() + is CustomCurrency.CustomToken -> mapOf( + "Network" to network.toNetworkId(), + "Symbol" to token.symbol, + "DerivationPath" to derivationPath?.rawPath, + "Token" to token.id, + "Address" to token.contractAddress, + ).filterNotNull() + } + } + } + } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/AddCustomTokenFragment.kt b/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/AddCustomTokenFragment.kt index 2192fd0477..3954882be5 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/AddCustomTokenFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/AddCustomTokenFragment.kt @@ -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 = 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(R.id.toolbar)?.let { - it.setTitle(R.string.add_custom_token_title) - } + requireActivity().window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) + view.findViewById(R.id.toolbar)?.setTitle(R.string.add_custom_token_title) val closePopupTrigger = initClosingPopupTriggerEvent() view.findViewById(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() + }, + ) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt index 9817b4db49..7bf90e4bfd 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt @@ -21,6 +21,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 @@ -52,6 +54,7 @@ class TokensMiddleware { handleAddingCustomToken(action) } is TokensAction.SetSearchInput -> { + Analytics.send(ManageTokens.TokenSearched()) handleLoadCurrencies( scanResponse = store.state.globalState.scanResponse, newSearchInput = action.searchInput @@ -345,6 +348,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) diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt index 9a9052115c..36b1f82337 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt @@ -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, blockchains: List -> + 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 = 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) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesScreen.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesScreen.kt index 49886cbf86..7cf23fb44c 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesScreen.kt @@ -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 = mutableStateOf(store.state.tokensState), onSaveChanges: (List, List) -> 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, addedBlockchainsState: MutableState>, - addedTokens: List + addedTokens: List, ) { 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), ) } \ No newline at end of file