Updated on 2026-08-14

This commit is contained in:
Tangem 2022-11-01 14:07:13 +05:00
parent e4ff41dc50
commit d7b674ea89
6 changed files with 159 additions and 68 deletions

View file

@ -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

View file

@ -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<String, String> = 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()
}
}
}
}
}
}

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

@ -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)

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),
)
}