Updated on 2026-08-14
This commit is contained in:
parent
a49ecbcabb
commit
855ee45378
30 changed files with 112 additions and 1440 deletions
|
|
@ -12,6 +12,7 @@ import com.tangem.blockchain.common.WalletManagerFactory
|
|||
import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||
import com.tangem.data.source.preferences.PreferencesDataSource
|
||||
import com.tangem.datasource.api.common.MoshiConverter
|
||||
import com.tangem.datasource.asset.AssetReader
|
||||
import com.tangem.datasource.config.ConfigManager
|
||||
|
|
@ -20,7 +21,6 @@ import com.tangem.datasource.config.models.Config
|
|||
import com.tangem.datasource.connection.NetworkConnectionManager
|
||||
import com.tangem.domain.DomainLayer
|
||||
import com.tangem.domain.common.LogConfig
|
||||
import com.tangem.data.source.preferences.PreferencesDataSource
|
||||
import com.tangem.tap.common.IntentHandler
|
||||
import com.tangem.tap.common.analytics.AnalyticsFactory
|
||||
import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
|
||||
|
|
@ -51,7 +51,6 @@ import com.tangem.tap.domain.walletStores.repository.WalletStoresRepository
|
|||
import com.tangem.tap.domain.walletStores.repository.di.provideDefaultImplementation
|
||||
import com.tangem.tap.domain.walletconnect.WalletConnectRepository
|
||||
import com.tangem.tap.features.customtoken.api.featuretoggles.CustomTokenFeatureToggles
|
||||
import com.tangem.tap.features.tokens.api.featuretoggles.TokensListFeatureToggles
|
||||
import com.tangem.tap.proxy.AppStateHolder
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.wallet.BuildConfig
|
||||
|
|
@ -128,9 +127,6 @@ class TapApplication : Application(), ImageLoaderFactory {
|
|||
@Inject
|
||||
lateinit var networkConnectionManager: NetworkConnectionManager
|
||||
|
||||
@Inject
|
||||
lateinit var tokensListFeatureToggles: TokensListFeatureToggles
|
||||
|
||||
@Inject
|
||||
lateinit var customTokenFeatureToggles: CustomTokenFeatureToggles
|
||||
|
||||
|
|
@ -149,7 +145,6 @@ class TapApplication : Application(), ImageLoaderFactory {
|
|||
daggerGraphState = DaggerGraphState(
|
||||
assetReader = assetReader,
|
||||
networkConnectionManager = networkConnectionManager,
|
||||
tokensListFeatureToggles = tokensListFeatureToggles,
|
||||
customTokenFeatureToggles = customTokenFeatureToggles,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ import com.tangem.tap.features.saveWallet.ui.SaveWalletBottomSheetFragment
|
|||
import com.tangem.tap.features.send.ui.SendFragment
|
||||
import com.tangem.tap.features.shop.ui.ShopFragment
|
||||
import com.tangem.tap.features.tokens.impl.presentation.TokensListFragment
|
||||
import com.tangem.tap.features.tokens.legacy.AddTokensFragment
|
||||
import com.tangem.tap.features.wallet.ui.WalletDetailsFragment
|
||||
import com.tangem.tap.features.wallet.ui.WalletFragment
|
||||
import com.tangem.tap.features.walletSelector.ui.WalletSelectorBottomSheetFragment
|
||||
|
|
@ -148,12 +147,7 @@ private fun fragmentFactory(screen: AppScreen): Fragment {
|
|||
AppScreen.ResetToFactory -> ResetCardFragment()
|
||||
AppScreen.AccessCodeRecovery -> AccessCodeRecoveryFragment()
|
||||
AppScreen.Disclaimer -> DisclaimerFragment()
|
||||
AppScreen.AddTokens -> {
|
||||
val featureToggles = store.state.daggerGraphState.get(
|
||||
getDependency = DaggerGraphState::tokensListFeatureToggles,
|
||||
)
|
||||
if (featureToggles.isRedesignedScreenEnabled) TokensListFragment() else AddTokensFragment()
|
||||
}
|
||||
AppScreen.AddTokens -> TokensListFragment()
|
||||
|
||||
AppScreen.AddCustomToken -> {
|
||||
val featureToggles = store.state.daggerGraphState.get(
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ data class AppState(
|
|||
SendMiddleware().sendMiddleware,
|
||||
DetailsMiddleware().detailsMiddleware,
|
||||
DisclaimerMiddleware().disclaimerMiddleware,
|
||||
TokensMiddleware().tokensMiddleware,
|
||||
TokensMiddleware.tokensMiddleware,
|
||||
WalletConnectMiddleware().walletConnectMiddleware,
|
||||
BackupMiddleware().backupMiddleware,
|
||||
ShopMiddleware().shopMiddleware,
|
||||
|
|
|
|||
|
|
@ -91,8 +91,7 @@ class HomeFragment : Fragment(), StoreSubscriber<HomeState> {
|
|||
onSearchTokensClick = {
|
||||
Analytics.send(IntroductionProcess.ButtonTokensList())
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.AddTokens))
|
||||
store.dispatch(TokensAction.AllowToAddTokens(false))
|
||||
store.dispatch(TokensAction.LoadCurrencies())
|
||||
store.dispatch(TokensAction.SetArgs.ReadAccess)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
package com.tangem.tap.features.tokens.api.featuretoggles
|
||||
|
||||
/**
|
||||
* TokensList feature toggles
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface TokensListFeatureToggles {
|
||||
|
||||
/** Availability of redesigned screen (internal feature) */
|
||||
val isRedesignedScreenEnabled: Boolean
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
package com.tangem.tap.features.tokens.impl.di
|
||||
|
||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||
import com.tangem.tap.features.tokens.api.featuretoggles.TokensListFeatureToggles
|
||||
import com.tangem.tap.features.tokens.impl.featuretoggles.DefaultTokensListFeatureToggles
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object TokensListFeatureTogglesModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesTokensListFeatureToggles(featureTogglesManager: FeatureTogglesManager): TokensListFeatureToggles {
|
||||
return DefaultTokensListFeatureToggles(featureTogglesManager)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
package com.tangem.tap.features.tokens.impl.featuretoggles
|
||||
|
||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||
import com.tangem.tap.features.tokens.api.featuretoggles.TokensListFeatureToggles
|
||||
|
||||
/**
|
||||
* Default implementation of TokensList feature toggles
|
||||
*
|
||||
* @property featureTogglesManager manager for getting information about the availability of feature toggles
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class DefaultTokensListFeatureToggles(
|
||||
private val featureTogglesManager: FeatureTogglesManager,
|
||||
) : TokensListFeatureToggles {
|
||||
|
||||
override val isRedesignedScreenEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(name = "REDESIGNED_TOKEN_LIST_SCREEN_ENABLED")
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import android.view.LayoutInflater
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
|
|
@ -35,7 +36,9 @@ internal class TokensListFragment : Fragment() {
|
|||
setContent {
|
||||
isTransitionGroup = true
|
||||
|
||||
val viewModel = hiltViewModel<TokensListViewModel>()
|
||||
val viewModel = hiltViewModel<TokensListViewModel>().apply {
|
||||
LocalLifecycleOwner.current.lifecycle.addObserver(this)
|
||||
}
|
||||
|
||||
TangemTheme {
|
||||
TokensListScreen(stateHolder = viewModel.uiState)
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import com.tangem.tap.store
|
|||
*/
|
||||
class TokensListArgs {
|
||||
/** Tokens list screen mode */
|
||||
val isManageAccess: Boolean get() = store.state.tokensState.allowToAdd
|
||||
val isManageAccess: Boolean get() = store.state.tokensState.isManageAccess
|
||||
|
||||
/** Tokens list that accessible from the main screen */
|
||||
val mainScreenTokenList: List<TokenWithBlockchain> get() = store.state.tokensState.addedTokens
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import androidx.compose.runtime.MutableState
|
|||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.core.ui.extensions.getActiveIconRes
|
||||
import com.tangem.tap.common.extensions.getGreyedOutIconRes
|
||||
import com.tangem.tap.features.tokens.legacy.redux.ContractAddress
|
||||
|
||||
/**
|
||||
* Network item state
|
||||
|
|
@ -62,7 +61,7 @@ sealed interface NetworkItemState {
|
|||
override val isMainNetwork: Boolean,
|
||||
val isAdded: MutableState<Boolean>,
|
||||
val id: String,
|
||||
val address: ContractAddress?,
|
||||
val address: String?,
|
||||
val decimalCount: Int?,
|
||||
val blockchain: Blockchain,
|
||||
val onToggleClick: (TokenItemState.ManageContent, ManageContent) -> Unit,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,10 @@ import com.tangem.tap.common.analytics.events.ManageTokens
|
|||
/** Analytics sender for tokens list screen */
|
||||
class TokensListAnalyticsSender(private val analyticsEventHandler: AnalyticsEventHandler) {
|
||||
|
||||
fun sendWhenScreenOpened() {
|
||||
analyticsEventHandler.send(event = ManageTokens.ScreenOpened())
|
||||
}
|
||||
|
||||
fun sendWhenTokenAdded(token: Token) {
|
||||
analyticsEventHandler.send(
|
||||
event = ManageTokens.TokenSwitcherChanged(
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ package com.tangem.tap.features.tokens.impl.presentation.viewmodels
|
|||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.paging.LoadState
|
||||
|
|
@ -59,7 +61,7 @@ internal class TokensListViewModel @Inject constructor(
|
|||
private val dispatchers: AppCoroutineDispatcherProvider,
|
||||
private val reduxStateHolder: AppStateHolder,
|
||||
analyticsEventHandler: AnalyticsEventHandler,
|
||||
) : ViewModel() {
|
||||
) : ViewModel(), DefaultLifecycleObserver {
|
||||
|
||||
private val args = TokensListArgs()
|
||||
private val analyticsSender = TokensListAnalyticsSender(analyticsEventHandler)
|
||||
|
|
@ -72,6 +74,10 @@ internal class TokensListViewModel @Inject constructor(
|
|||
private val changedTokensList: MutableList<TokenWithBlockchain> = args.mainScreenTokenList.toMutableList()
|
||||
private val changedBlockchainList: MutableList<Blockchain> = args.mainScreenBlockchainList.toMutableList()
|
||||
|
||||
override fun onCreate(owner: LifecycleOwner) {
|
||||
if (args.isManageAccess) analyticsSender.sendWhenScreenOpened()
|
||||
}
|
||||
|
||||
private fun getInitialUiState(): TokensListStateHolder {
|
||||
return if (args.isManageAccess) {
|
||||
TokensListStateHolder.ManageContent(
|
||||
|
|
|
|||
|
|
@ -1,174 +0,0 @@
|
|||
package com.tangem.tap.features.tokens.legacy
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.transition.TransitionInflater
|
||||
import by.kirich1409.viewbindingdelegate.viewBinding
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
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
|
||||
import com.tangem.tap.common.extensions.getString
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.features.BaseFragment
|
||||
import com.tangem.tap.features.addBackPressHandler
|
||||
import com.tangem.tap.features.tokens.legacy.redux.ContractAddress
|
||||
import com.tangem.tap.features.tokens.legacy.redux.TokenWithBlockchain
|
||||
import com.tangem.tap.features.tokens.legacy.redux.TokensAction
|
||||
import com.tangem.tap.features.tokens.legacy.redux.TokensState
|
||||
import com.tangem.tap.features.tokens.legacy.ui.compose.CurrenciesScreen
|
||||
import com.tangem.tap.mainScope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.FragmentAddTokensBinding
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.rekotlin.StoreSubscriber
|
||||
|
||||
@AndroidEntryPoint
|
||||
internal class AddTokensFragment : BaseFragment(R.layout.fragment_add_tokens), StoreSubscriber<TokensState> {
|
||||
|
||||
private val binding: FragmentAddTokensBinding by viewBinding(FragmentAddTokensBinding::bind)
|
||||
private var tokensState: MutableState<TokensState> = mutableStateOf(store.state.tokensState)
|
||||
private var searchInput = mutableStateOf("")
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setHasOptionsMenu(true)
|
||||
addBackPressHandler(this)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) = with(binding) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
(activity as? AppCompatActivity)?.setSupportActionBar(toolbar)
|
||||
toolbar.setNavigationOnClickListener { handleOnBackPressed() }
|
||||
|
||||
val onSaveChanges = { tokens: List<TokenWithBlockchain>, blockchains: List<Blockchain> ->
|
||||
Analytics.send(ManageTokens.ButtonSaveChanges())
|
||||
store.dispatch(TokensAction.SaveChanges(tokens, blockchains))
|
||||
}
|
||||
val onNetworkItemClicked = { contractAddress: ContractAddress ->
|
||||
context?.copyToClipboard(contractAddress)
|
||||
store.dispatchNotification(R.string.contract_address_copied_message)
|
||||
}
|
||||
|
||||
val onLoadMore = {
|
||||
store.dispatch(TokensAction.LoadMore(scanResponse = store.state.globalState.scanResponse))
|
||||
}
|
||||
|
||||
cvCurrencies.setContent {
|
||||
TangemTheme {
|
||||
CurrenciesScreen(
|
||||
tokensState = tokensState,
|
||||
onSaveChanges = onSaveChanges,
|
||||
onNetworkItemClick = onNetworkItemClicked,
|
||||
onLoadMore = onLoadMore,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
store.subscribe(this) { state ->
|
||||
state
|
||||
.skipRepeats { oldState, newState -> oldState.tokensState == newState.tokensState }
|
||||
.select { it.tokensState }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
store.unsubscribe(this)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
store.dispatch(TokensAction.ResetState)
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.menu_popular_tokens, menu)
|
||||
|
||||
val addCustomTokenAllowed = store.state.tokensState.allowToAdd
|
||||
menu.findItem(R.id.menu_navigate_add_custom_token).isVisible = addCustomTokenAllowed
|
||||
|
||||
val menuItem = menu.findItem(R.id.menu_search)
|
||||
val searchView: SearchView = menuItem.actionView as SearchView
|
||||
searchView.queryHint = searchView.getString(R.string.common_search)
|
||||
searchView.maxWidth = android.R.attr.width
|
||||
searchView.inputtedTextAsFlow()
|
||||
.debounce(800)
|
||||
.distinctUntilChanged()
|
||||
.onEach {
|
||||
searchInput.value = it.lowercase()
|
||||
dispatchOnMain(TokensAction.SetSearchInput(searchInput.value))
|
||||
}
|
||||
.launchIn(mainScope)
|
||||
return super.onCreateOptionsMenu(menu, inflater)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
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
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
override fun configureTransitions() {
|
||||
super.configureTransitions()
|
||||
val inflater = TransitionInflater.from(requireContext())
|
||||
enterTransition = inflater.inflateTransition(R.transition.slide_right)
|
||||
exitTransition = inflater.inflateTransition(R.transition.fade)
|
||||
}
|
||||
|
||||
override fun handleOnBackPressed() {
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
store.dispatch(TokensAction.ResetState)
|
||||
}
|
||||
|
||||
override fun newState(state: TokensState) {
|
||||
if (activity == null || view == null) return
|
||||
val toolbarTitle =
|
||||
if (state.allowToAdd) R.string.main_manage_tokens else R.string.search_tokens_title
|
||||
tokensState.value = state
|
||||
binding.toolbar.title = getString(toolbarTitle)
|
||||
}
|
||||
|
||||
private fun SearchView.inputtedTextAsFlow(): Flow<String> = callbackFlow {
|
||||
val watcher = setOnQueryTextListener(
|
||||
object : SearchView.OnQueryTextListener {
|
||||
override fun onQueryTextSubmit(query: String?): Boolean = false
|
||||
|
||||
override fun onQueryTextChange(newText: String?): Boolean {
|
||||
trySend(newText ?: "")
|
||||
return false
|
||||
}
|
||||
},
|
||||
)
|
||||
awaitClose { watcher }
|
||||
}
|
||||
}
|
||||
|
|
@ -2,37 +2,22 @@ package com.tangem.tap.features.tokens.legacy.redux
|
|||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.DerivationStyle
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.domain.model.WalletDataModel
|
||||
import com.tangem.tap.domain.tokens.Currency
|
||||
import org.rekotlin.Action
|
||||
|
||||
sealed class TokensAction : Action {
|
||||
sealed interface TokensAction : Action {
|
||||
|
||||
object ResetState : TokensAction()
|
||||
/** Single way to pass data to the screen */
|
||||
sealed interface SetArgs : TokensAction {
|
||||
|
||||
data class AllowToAddTokens(val allow: Boolean) : TokensAction()
|
||||
data class ManageAccess(val wallets: List<WalletDataModel>, val derivationStyle: DerivationStyle?) : SetArgs
|
||||
|
||||
data class LoadCurrencies(val scanResponse: ScanResponse? = null) : TokensAction() {
|
||||
data class Success(val currencies: List<Currency>, val loadMore: Boolean) : TokensAction()
|
||||
object Failure : TokensAction()
|
||||
object ReadAccess : SetArgs
|
||||
}
|
||||
|
||||
data class LoadMore(
|
||||
val scanResponse: ScanResponse? = null,
|
||||
) : TokensAction()
|
||||
// TODO: [REDACTED_TASK_KEY] Remove this action
|
||||
data class SaveChanges(val tokens: List<TokenWithBlockchain>, val blockchains: List<Blockchain>) : TokensAction
|
||||
|
||||
data class SetAddedCurrencies(
|
||||
val wallets: List<WalletDataModel>,
|
||||
val derivationStyle: DerivationStyle?,
|
||||
) : TokensAction()
|
||||
|
||||
data class SaveChanges(
|
||||
val addedTokens: List<TokenWithBlockchain>,
|
||||
val addedBlockchains: List<Blockchain>,
|
||||
) : TokensAction()
|
||||
|
||||
object PrepareAndNavigateToAddCustomToken : TokensAction()
|
||||
|
||||
data class SetSearchInput(val searchInput: String) : TokensAction()
|
||||
// TODO: Remove this action in 4.7 release
|
||||
object PrepareAndNavigateToAddCustomToken : TokensAction
|
||||
}
|
||||
|
|
@ -8,13 +8,10 @@ import com.tangem.common.extensions.ByteArrayKey
|
|||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.common.extensions.toMapKey
|
||||
import com.tangem.common.flatMap
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.domain.DomainWrapped
|
||||
import com.tangem.domain.common.TapWorkarounds.derivationStyle
|
||||
import com.tangem.domain.common.TapWorkarounds.isTestCard
|
||||
import com.tangem.domain.common.extensions.supportedBlockchains
|
||||
import com.tangem.domain.common.util.hasDerivation
|
||||
import com.tangem.domain.common.util.supportsHdWallet
|
||||
import com.tangem.domain.features.addCustomToken.CustomCurrency
|
||||
|
|
@ -32,132 +29,76 @@ import com.tangem.tap.common.redux.navigation.AppScreen
|
|||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.model.WalletDataModel
|
||||
import com.tangem.tap.domain.tokens.LoadAvailableCoinsService
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
import com.tangem.tap.userWalletsListManager
|
||||
import com.tangem.tap.walletCurrenciesManager
|
||||
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Middleware
|
||||
import timber.log.Timber
|
||||
|
||||
@Suppress("LargeClass")
|
||||
class TokensMiddleware {
|
||||
object TokensMiddleware {
|
||||
|
||||
val tokensMiddleware: Middleware<AppState> = { _, _ ->
|
||||
{ next ->
|
||||
{ action ->
|
||||
when (action) {
|
||||
is TokensAction.LoadCurrencies -> handleLoadCurrencies(action.scanResponse)
|
||||
is TokensAction.SaveChanges -> handleSaveChanges(action)
|
||||
is TokensAction.PrepareAndNavigateToAddCustomToken -> handleAddingCustomToken()
|
||||
is TokensAction.SetSearchInput -> {
|
||||
Analytics.send(ManageTokens.TokenSearched())
|
||||
handleLoadCurrencies(
|
||||
scanResponse = store.state.globalState.scanResponse,
|
||||
newSearchInput = action.searchInput,
|
||||
)
|
||||
}
|
||||
is TokensAction.LoadMore -> {
|
||||
if (store.state.tokensState.needToLoadMore &&
|
||||
store.state.tokensState.currencies.isNotEmpty()
|
||||
) {
|
||||
handleLoadCurrencies(action.scanResponse)
|
||||
}
|
||||
}
|
||||
}
|
||||
next(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleLoadCurrencies(scanResponse: ScanResponse?, newSearchInput: String? = null) {
|
||||
val tokensState = store.state.tokensState
|
||||
|
||||
val supportedBlockchains: List<Blockchain> = scanResponse?.card?.supportedBlockchains()
|
||||
?: Blockchain.values().toList().filterNot(Blockchain::isTestnet)
|
||||
|
||||
val loadCoinsService = LoadAvailableCoinsService(
|
||||
tangemTechApi = store.state.domainNetworks.tangemTechService.api,
|
||||
dispatchers = AppCoroutineDispatcherProvider(),
|
||||
assetReader = store.state.daggerGraphState.get(DaggerGraphState::assetReader),
|
||||
)
|
||||
|
||||
private fun handleSaveChanges(action: TokensAction.SaveChanges) {
|
||||
scope.launch {
|
||||
val result = loadCoinsService.getSupportedTokens(
|
||||
isTestNet = scanResponse?.card?.isTestCard ?: false,
|
||||
supportedBlockchains = supportedBlockchains,
|
||||
page = if (newSearchInput == null) tokensState.pageToLoad else 0,
|
||||
searchInput = if (newSearchInput == null) tokensState.searchInput else newSearchInput.ifBlank { null },
|
||||
val scanResponse = store.state.globalState.scanResponse ?: return@launch
|
||||
|
||||
val currentTokens = store.state.tokensState.addedTokens
|
||||
val currentBlockchains = store.state.tokensState.addedBlockchains
|
||||
|
||||
val blockchainsToAdd = action.blockchains.filterNot(currentBlockchains::contains)
|
||||
val blockchainsToRemove =
|
||||
store.state.tokensState.addedBlockchains.filterNot(action.blockchains::contains)
|
||||
|
||||
val tokensToAdd = action.tokens.filterNot(currentTokens::contains)
|
||||
val tokensToRemove = currentTokens.filterNot { token -> action.tokens.any { it.token == token.token } }
|
||||
|
||||
removeCurrenciesIfNeeded(
|
||||
currencies = convertToCurrencies(
|
||||
blockchains = blockchainsToRemove,
|
||||
tokens = tokensToRemove,
|
||||
derivationStyle = scanResponse.card.derivationStyle,
|
||||
),
|
||||
)
|
||||
|
||||
store.dispatchOnMain(
|
||||
action = when (result) {
|
||||
is Result.Success -> {
|
||||
TokensAction.LoadCurrencies.Success(
|
||||
currencies = result.data.currencies.filter(supportedBlockchains.toSet()),
|
||||
loadMore = result.data.moreAvailable,
|
||||
)
|
||||
}
|
||||
is Result.Failure -> {
|
||||
TokensAction.LoadCurrencies.Failure
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
val isNothingToDoWithTokens = tokensToAdd.isEmpty() && tokensToRemove.isEmpty()
|
||||
val isNothingToDoWithBlockchain = blockchainsToAdd.isEmpty() && blockchainsToRemove.isEmpty()
|
||||
if (isNothingToDoWithTokens && isNothingToDoWithBlockchain) {
|
||||
store.dispatchDebugErrorNotification(message = "Nothing to save")
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo())
|
||||
return@launch
|
||||
}
|
||||
|
||||
private fun handleSaveChanges(action: TokensAction.SaveChanges) = scope.launch {
|
||||
val scanResponse = store.state.globalState.scanResponse ?: return@launch
|
||||
|
||||
val currentTokens = store.state.tokensState.addedWallets.toNonCustomTokensWithBlockchains(
|
||||
derivationStyle = scanResponse.card.derivationStyle,
|
||||
)
|
||||
val currentBlockchains = store.state.tokensState.addedWallets.toNonCustomBlockchains(
|
||||
derivationStyle = scanResponse.card.derivationStyle,
|
||||
)
|
||||
|
||||
val blockchainsToAdd = action.addedBlockchains.filterNot(currentBlockchains::contains)
|
||||
val blockchainsToRemove = currentBlockchains.filterNot(action.addedBlockchains::contains)
|
||||
|
||||
val tokensToAdd = action.addedTokens.filterNot(currentTokens::contains)
|
||||
val tokensToRemove = currentTokens.filterNot { token -> action.addedTokens.any { it.token == token.token } }
|
||||
|
||||
removeCurrenciesIfNeeded(
|
||||
currencies = convertToCurrencies(
|
||||
blockchains = blockchainsToRemove,
|
||||
tokens = tokensToRemove,
|
||||
val currencyList = convertToCurrencies(
|
||||
blockchains = blockchainsToAdd,
|
||||
tokens = tokensToAdd,
|
||||
derivationStyle = scanResponse.card.derivationStyle,
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
val isNothingToDoWithTokens = tokensToAdd.isEmpty() && tokensToRemove.isEmpty()
|
||||
val isNothingToDoWithBlockchain = blockchainsToAdd.isEmpty() && blockchainsToRemove.isEmpty()
|
||||
if (isNothingToDoWithTokens && isNothingToDoWithBlockchain) {
|
||||
store.dispatchDebugErrorNotification(message = "Nothing to save")
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo())
|
||||
return@launch
|
||||
}
|
||||
|
||||
val currencyList = convertToCurrencies(
|
||||
blockchains = blockchainsToAdd,
|
||||
tokens = tokensToAdd,
|
||||
derivationStyle = scanResponse.card.derivationStyle,
|
||||
)
|
||||
|
||||
if (scanResponse.supportsHdWallet()) {
|
||||
deriveMissingBlockchains(scanResponse, currencyList) {
|
||||
submitAdd(it, currencyList)
|
||||
if (scanResponse.supportsHdWallet()) {
|
||||
deriveMissingBlockchains(scanResponse, currencyList) {
|
||||
submitAdd(it, currencyList)
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo())
|
||||
}
|
||||
} else {
|
||||
submitAdd(scanResponse, currencyList)
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo())
|
||||
}
|
||||
} else {
|
||||
submitAdd(scanResponse, currencyList)
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
package com.tangem.tap.features.tokens.legacy.redux
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.DerivationStyle
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.domain.model.WalletDataModel
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.features.wallet.models.Currency.Token
|
||||
import org.rekotlin.Action
|
||||
|
||||
object TokensReducer {
|
||||
|
|
@ -10,47 +15,41 @@ object TokensReducer {
|
|||
private fun internalReduce(action: Action, state: AppState): TokensState {
|
||||
if (action !is TokensAction) return state.tokensState
|
||||
|
||||
val tokensState = state.tokensState
|
||||
return when (action) {
|
||||
is TokensAction.ResetState -> TokensState()
|
||||
is TokensAction.LoadCurrencies -> {
|
||||
val loadingState = if (tokensState.currencies.isEmpty()) {
|
||||
LoadCoinsState.LOADING
|
||||
} else {
|
||||
LoadCoinsState.LOADED
|
||||
}
|
||||
tokensState.copy(
|
||||
scanResponse = action.scanResponse,
|
||||
loadCoinsState = loadingState,
|
||||
)
|
||||
}
|
||||
is TokensAction.LoadCurrencies.Success -> {
|
||||
tokensState.copy(
|
||||
currencies = tokensState.currencies + action.currencies,
|
||||
needToLoadMore = action.loadMore,
|
||||
pageToLoad = tokensState.pageToLoad + 1,
|
||||
loadCoinsState = LoadCoinsState.LOADED,
|
||||
)
|
||||
}
|
||||
is TokensAction.SetAddedCurrencies -> {
|
||||
tokensState.copy(
|
||||
is TokensAction.SetArgs.ManageAccess -> {
|
||||
state.tokensState.copy(
|
||||
isManageAccess = true,
|
||||
addedWallets = action.wallets,
|
||||
addedBlockchains = action.wallets.toNonCustomBlockchains(action.derivationStyle),
|
||||
addedTokens = action.wallets.toNonCustomTokensWithBlockchains(action.derivationStyle),
|
||||
addedWallets = action.wallets,
|
||||
)
|
||||
}
|
||||
is TokensAction.AllowToAddTokens -> {
|
||||
tokensState.copy(allowToAdd = action.allow)
|
||||
|
||||
is TokensAction.SetArgs.ReadAccess -> {
|
||||
state.tokensState.copy(isManageAccess = false)
|
||||
}
|
||||
is TokensAction.SetSearchInput -> {
|
||||
tokensState.copy(
|
||||
searchInput = action.searchInput.ifBlank { null },
|
||||
needToLoadMore = true,
|
||||
currencies = emptyList(),
|
||||
pageToLoad = 0,
|
||||
loadCoinsState = LoadCoinsState.LOADING,
|
||||
)
|
||||
}
|
||||
else -> tokensState
|
||||
|
||||
else -> state.tokensState
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<WalletDataModel>.toNonCustomBlockchains(derivationStyle: DerivationStyle?): List<Blockchain> {
|
||||
return mapNotNull { walletDataModel ->
|
||||
if (walletDataModel.currency.isCustomCurrency(derivationStyle)) {
|
||||
null
|
||||
} else {
|
||||
(walletDataModel.currency as? Currency.Blockchain)?.blockchain
|
||||
}
|
||||
}.distinct()
|
||||
}
|
||||
|
||||
private fun List<WalletDataModel>.toNonCustomTokensWithBlockchains(
|
||||
derivationStyle: DerivationStyle?,
|
||||
): List<TokenWithBlockchain> {
|
||||
return mapNotNull { walletDataModel ->
|
||||
if (walletDataModel.currency !is Token) return@mapNotNull null
|
||||
if (walletDataModel.currency.isCustomCurrency(derivationStyle)) return@mapNotNull null
|
||||
|
||||
TokenWithBlockchain(walletDataModel.currency.token, walletDataModel.currency.blockchain)
|
||||
}.distinct()
|
||||
}
|
||||
|
|
@ -1,76 +1,16 @@
|
|||
package com.tangem.tap.features.tokens.legacy.redux
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.DerivationStyle
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.common.extensions.canHandleToken
|
||||
import com.tangem.domain.common.extensions.fromNetworkId
|
||||
import com.tangem.tap.domain.model.WalletDataModel
|
||||
import com.tangem.tap.domain.tokens.Currency
|
||||
import org.rekotlin.StateType
|
||||
import com.tangem.tap.features.wallet.models.Currency.Token as CurrencyToken
|
||||
|
||||
data class TokensState(
|
||||
val isManageAccess: Boolean = false,
|
||||
val addedWallets: List<WalletDataModel> = emptyList(),
|
||||
val addedTokens: List<TokenWithBlockchain> = emptyList(),
|
||||
val addedBlockchains: List<Blockchain> = emptyList(),
|
||||
val currencies: List<Currency> = emptyList(),
|
||||
val searchInput: String? = null,
|
||||
val allowToAdd: Boolean = true,
|
||||
val scanResponse: ScanResponse? = null,
|
||||
val needToLoadMore: Boolean = true,
|
||||
val pageToLoad: Int = 0,
|
||||
val loadCoinsState: LoadCoinsState = LoadCoinsState.LOADING,
|
||||
) : StateType {
|
||||
) : StateType
|
||||
|
||||
fun canHandleToken(token: TokenWithBlockchain): Boolean {
|
||||
return scanResponse?.card?.canHandleToken(token.blockchain) ?: false
|
||||
}
|
||||
}
|
||||
|
||||
typealias ContractAddress = String
|
||||
|
||||
fun List<WalletDataModel>.toNonCustomTokensWithBlockchains(
|
||||
derivationStyle: DerivationStyle?,
|
||||
): List<TokenWithBlockchain> {
|
||||
return mapNotNull {
|
||||
if (it.currency !is CurrencyToken) return@mapNotNull null
|
||||
if (it.currency.isCustomCurrency(derivationStyle)) return@mapNotNull null
|
||||
TokenWithBlockchain(it.currency.token, it.currency.blockchain)
|
||||
}.distinct()
|
||||
}
|
||||
|
||||
fun List<WalletDataModel>.toNonCustomBlockchains(derivationStyle: DerivationStyle?): List<Blockchain> {
|
||||
return mapNotNull {
|
||||
if (it.currency.isCustomCurrency(derivationStyle)) {
|
||||
null
|
||||
} else {
|
||||
(it.currency as? com.tangem.tap.features.wallet.models.Currency.Blockchain)?.blockchain
|
||||
}
|
||||
}.distinct()
|
||||
}
|
||||
|
||||
data class TokenWithBlockchain(
|
||||
val token: Token,
|
||||
val blockchain: Blockchain,
|
||||
)
|
||||
|
||||
fun List<Currency>.filter(supportedBlockchains: Set<Blockchain>?): List<Currency> {
|
||||
if (supportedBlockchains == null) return this
|
||||
|
||||
return map { currency ->
|
||||
currency.copy(
|
||||
contracts = currency.contracts.filter { contract ->
|
||||
supportedBlockchains.contains(contract.blockchain) &&
|
||||
(contract.blockchain.canHandleTokens() || contract.address == null)
|
||||
},
|
||||
)
|
||||
}.filterNot { currency ->
|
||||
currency.contracts.isEmpty() && !supportedBlockchains.contains(Blockchain.fromNetworkId(currency.id))
|
||||
}
|
||||
}
|
||||
|
||||
enum class LoadCoinsState {
|
||||
LOADING, LOADED,
|
||||
}
|
||||
// TODO: [REDACTED_TASK_KEY] Remove this class
|
||||
data class TokenWithBlockchain(val token: Token, val blockchain: Blockchain)
|
||||
|
|
@ -1,232 +0,0 @@
|
|||
@file:Suppress("MagicNumber")
|
||||
|
||||
package com.tangem.tap.features.tokens.legacy.ui.compose
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.CircularProgressIndicator
|
||||
import androidx.compose.material.FabPosition
|
||||
import androidx.compose.material.Scaffold
|
||||
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
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.ui.components.Keyboard
|
||||
import com.tangem.core.ui.components.PrimaryButton
|
||||
import com.tangem.core.ui.components.SystemBarsEffect
|
||||
import com.tangem.core.ui.components.keyboardAsState
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation
|
||||
import com.tangem.domain.common.extensions.fromNetworkId
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.ManageTokens
|
||||
import com.tangem.tap.common.compose.extensions.addAndNotify
|
||||
import com.tangem.tap.common.compose.extensions.removeAndNotify
|
||||
import com.tangem.tap.common.extensions.dispatchDialogShow
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.domain.tokens.Currency
|
||||
import com.tangem.tap.features.tokens.legacy.redux.ContractAddress
|
||||
import com.tangem.tap.features.tokens.legacy.redux.LoadCoinsState
|
||||
import com.tangem.tap.features.tokens.legacy.redux.TokenWithBlockchain
|
||||
import com.tangem.tap.features.tokens.legacy.redux.TokensState
|
||||
import com.tangem.tap.features.wallet.redux.models.WalletDialog
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
fun CurrenciesScreen(
|
||||
tokensState: MutableState<TokensState>,
|
||||
onSaveChanges: (List<TokenWithBlockchain>, List<Blockchain>) -> Unit,
|
||||
onNetworkItemClick: (ContractAddress) -> Unit,
|
||||
onLoadMore: () -> Unit,
|
||||
) {
|
||||
val tokensAddedOnMainScreen = remember { tokensState.value.addedTokens }
|
||||
val blockchainsAddedOnMainScreen = remember { tokensState.value.addedBlockchains }
|
||||
|
||||
val addedTokensState = remember { mutableStateOf(tokensState.value.addedTokens) }
|
||||
val addedBlockchainsState = remember { mutableStateOf(tokensState.value.addedBlockchains) }
|
||||
|
||||
val keyboardState by keyboardAsState()
|
||||
|
||||
val onAddCurrencyToggleClick = { currency: Currency, token: TokenWithBlockchain? ->
|
||||
val blockchain = Blockchain.fromNetworkId(currency.id)
|
||||
if (blockchain != null && token == null) {
|
||||
toggleBlockchain(
|
||||
blockchain,
|
||||
blockchainsAddedOnMainScreen,
|
||||
addedBlockchainsState,
|
||||
addedTokensState.value,
|
||||
)
|
||||
} else if (token != null) {
|
||||
toggleToken(
|
||||
token,
|
||||
tokensAddedOnMainScreen,
|
||||
addedTokensState,
|
||||
tokensState.value,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val statusBarColor = colorResource(id = R.color.backgroundLightGray)
|
||||
SystemBarsEffect {
|
||||
setSystemBarsColor(color = statusBarColor)
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
floatingActionButton = {
|
||||
if (tokensState.value.allowToAdd) {
|
||||
SaveChangesButton(keyboardState) {
|
||||
onSaveChanges(addedTokensState.value, addedBlockchainsState.value)
|
||||
}
|
||||
}
|
||||
},
|
||||
floatingActionButtonPosition = FabPosition.Center,
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
visible = tokensState.value.loadCoinsState == LoadCoinsState.LOADING,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
) {
|
||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
CircularProgressIndicator(color = Color(0xFF1ACE80))
|
||||
}
|
||||
}
|
||||
AnimatedVisibility(
|
||||
visible = tokensState.value.loadCoinsState == LoadCoinsState.LOADED,
|
||||
enter = fadeIn(animationSpec = tween(1000)),
|
||||
exit = fadeOut(animationSpec = tween(1000)),
|
||||
) {
|
||||
Column {
|
||||
val showHeader = tokensState.value.scanResponse?.card?.useOldStyleDerivation == true
|
||||
ListOfCurrencies(
|
||||
header = { if (showHeader) CurrenciesWarning() },
|
||||
currencies = tokensState.value.currencies,
|
||||
addedTokens = addedTokensState.value,
|
||||
addedBlockchains = addedBlockchainsState.value,
|
||||
allowToAdd = tokensState.value.allowToAdd,
|
||||
onAddCurrencyToggle = { currency, token ->
|
||||
onAddCurrencyToggleClick(currency, token)
|
||||
},
|
||||
onNetworkItemClick = onNetworkItemClick,
|
||||
onLoadMore = onLoadMore,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun toggleBlockchain(
|
||||
blockchain: Blockchain,
|
||||
blockchainsAddedOnMainScreen: List<Blockchain>,
|
||||
addedBlockchainsState: MutableState<List<Blockchain>>,
|
||||
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,
|
||||
),
|
||||
)
|
||||
} else if (isAddedOnMainScreen) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
private fun toggleToken(
|
||||
token: TokenWithBlockchain,
|
||||
tokensAddedOnMainScreen: List<TokenWithBlockchain>,
|
||||
addedTokensState: MutableState<List<TokenWithBlockchain>>,
|
||||
tokensState: TokensState,
|
||||
) {
|
||||
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 = {
|
||||
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,
|
||||
),
|
||||
)
|
||||
} 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) {
|
||||
PrimaryButton(
|
||||
modifier = Modifier
|
||||
.padding(bottom = keyboardState.height)
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||
.fillMaxWidth(),
|
||||
text = stringResource(id = R.string.common_save_changes),
|
||||
onClick = onSaveChanges,
|
||||
)
|
||||
}
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
package com.tangem.tap.features.tokens.legacy.ui.compose
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.tangem.wallet.R
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
@Composable
|
||||
fun CurrenciesWarning() {
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = Modifier
|
||||
.padding(16.dp)
|
||||
.background(
|
||||
color = Color(0xFFF2F2F2),
|
||||
shape = RoundedCornerShape(10.dp),
|
||||
),
|
||||
) {
|
||||
val warning = stringResource(id = R.string.alert_manage_tokens_addresses_message)
|
||||
val end = warning.indexOf(" ")
|
||||
val spanStyles = listOf(
|
||||
AnnotatedString.Range(
|
||||
SpanStyle(fontWeight = FontWeight.Bold),
|
||||
start = 0,
|
||||
end = end,
|
||||
),
|
||||
)
|
||||
Text(
|
||||
text = AnnotatedString(text = warning, spanStyles = spanStyles),
|
||||
textAlign = TextAlign.Start,
|
||||
color = Color(0xFF848488),
|
||||
modifier = Modifier.padding(
|
||||
top = 8.dp,
|
||||
bottom = 8.dp,
|
||||
start = 16.dp,
|
||||
end = 16.dp,
|
||||
),
|
||||
fontSize = 13.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
package com.tangem.tap.features.tokens.legacy.ui.compose
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
@Composable
|
||||
fun CurrencyPlaceholderIcon(id: String) {
|
||||
val letterColor: Color = Color.White
|
||||
val circleColor: Color = Color.Black
|
||||
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = Modifier
|
||||
.background(circleColor, shape = CircleShape),
|
||||
) {
|
||||
Text(
|
||||
text = id.firstOrNull()?.titlecase() ?: "",
|
||||
textAlign = TextAlign.Center,
|
||||
color = letterColor,
|
||||
modifier = Modifier.padding(4.dp),
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
package com.tangem.tap.features.tokens.legacy.ui.compose
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.tap.common.compose.extensions.HideKeyboardOnScroll
|
||||
import com.tangem.tap.common.compose.extensions.OnBottomReached
|
||||
import com.tangem.tap.domain.tokens.Currency
|
||||
import com.tangem.tap.features.tokens.legacy.redux.ContractAddress
|
||||
import com.tangem.tap.features.tokens.legacy.redux.TokenWithBlockchain
|
||||
import com.tangem.tap.features.tokens.legacy.ui.compose.currencyItem.CurrencyItem
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
fun ListOfCurrencies(
|
||||
header: @Composable () -> Unit,
|
||||
currencies: List<Currency>,
|
||||
addedTokens: List<TokenWithBlockchain>,
|
||||
addedBlockchains: List<Blockchain>,
|
||||
allowToAdd: Boolean,
|
||||
onAddCurrencyToggle: (Currency, TokenWithBlockchain?) -> Unit,
|
||||
onNetworkItemClick: (ContractAddress) -> Unit,
|
||||
onLoadMore: () -> Unit,
|
||||
) {
|
||||
val expandedCurrencies = remember { mutableStateOf(listOf("")) }
|
||||
|
||||
val onCurrencyClick = { currencyId: String ->
|
||||
val mutableList = expandedCurrencies.value.toMutableList()
|
||||
if (mutableList.contains(currencyId)) {
|
||||
mutableList.remove(currencyId)
|
||||
} else {
|
||||
mutableList.add(currencyId)
|
||||
}
|
||||
expandedCurrencies.value = mutableList
|
||||
}
|
||||
|
||||
val listState = rememberLazyListState()
|
||||
listState.HideKeyboardOnScroll()
|
||||
listState.OnBottomReached(loadMoreThreshold = 40) { onLoadMore() }
|
||||
|
||||
LazyColumn(
|
||||
state = listState,
|
||||
modifier = Modifier
|
||||
.fillMaxSize(),
|
||||
contentPadding = PaddingValues(bottom = 90.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
item { header() }
|
||||
itemsIndexed(currencies) { _, currency ->
|
||||
CurrencyItem(
|
||||
currency = currency,
|
||||
addedTokens = addedTokens,
|
||||
addedBlockchains = addedBlockchains,
|
||||
allowToAdd = allowToAdd,
|
||||
isExpanded = expandedCurrencies.value.contains(currency.id),
|
||||
onCurrencyClick = { onCurrencyClick(currency.id) },
|
||||
onAddCurrencyToggle = onAddCurrencyToggle,
|
||||
onNetworkItemClick = onNetworkItemClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val Currency.fullName: String
|
||||
get() = "${this.name} (${this.symbol})"
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
package com.tangem.tap.features.tokens.legacy.ui.compose.currencyItem
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.expandVertically
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.shrinkVertically
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.tap.domain.tokens.Contract
|
||||
import com.tangem.tap.domain.tokens.Currency
|
||||
import com.tangem.tap.features.tokens.legacy.redux.ContractAddress
|
||||
import com.tangem.tap.features.tokens.legacy.redux.TokenWithBlockchain
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
fun CurrencyExpandedContent(
|
||||
currency: Currency,
|
||||
addedTokens: List<TokenWithBlockchain>,
|
||||
addedBlockchains: List<Blockchain>,
|
||||
allowToAdd: Boolean,
|
||||
isExpanded: Boolean,
|
||||
onAddCurrencyToggle: (Currency, TokenWithBlockchain?) -> Unit,
|
||||
onNetworkItemClick: (ContractAddress) -> Unit,
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
visible = isExpanded,
|
||||
enter = fadeIn() + expandVertically(),
|
||||
exit = fadeOut() + shrinkVertically(),
|
||||
) {
|
||||
val blockchains = currency.contracts.map { it.blockchain }
|
||||
Column(modifier = Modifier.fillMaxWidth()) {
|
||||
blockchains.mapIndexed { index, blockchain ->
|
||||
val currencyContract = currency.contracts
|
||||
.firstOrNull { it.blockchain == blockchain }
|
||||
?: return@mapIndexed
|
||||
|
||||
val added = if (currencyContract.address != null) {
|
||||
addedTokens.any(currencyContract::isInclude)
|
||||
} else {
|
||||
addedBlockchains.contains(blockchain)
|
||||
}
|
||||
NetworkItem(
|
||||
currency = currency,
|
||||
contract = currencyContract,
|
||||
blockchain = blockchain,
|
||||
allowToAdd = allowToAdd,
|
||||
added = added,
|
||||
index = index,
|
||||
size = blockchains.size,
|
||||
onAddCurrencyToggle = onAddCurrencyToggle,
|
||||
onNetworkItemClick = onNetworkItemClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Contract.isInclude(addedToken: TokenWithBlockchain): Boolean =
|
||||
address == addedToken.token.contractAddress && blockchain == addedToken.blockchain
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
package com.tangem.tap.features.tokens.legacy.ui.compose.currencyItem
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.runtime.Composable
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.tap.domain.tokens.Currency
|
||||
import com.tangem.tap.features.tokens.legacy.redux.ContractAddress
|
||||
import com.tangem.tap.features.tokens.legacy.redux.TokenWithBlockchain
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
fun CurrencyItem(
|
||||
currency: Currency,
|
||||
addedTokens: List<TokenWithBlockchain>,
|
||||
addedBlockchains: List<Blockchain>,
|
||||
allowToAdd: Boolean,
|
||||
isExpanded: Boolean,
|
||||
onCurrencyClick: () -> Unit,
|
||||
onAddCurrencyToggle: (Currency, TokenWithBlockchain?) -> Unit,
|
||||
onNetworkItemClick: (ContractAddress) -> Unit,
|
||||
) {
|
||||
Column {
|
||||
CurrencyItemHeader(
|
||||
currency = currency,
|
||||
addedTokens = addedTokens,
|
||||
addedBlockchains = addedBlockchains,
|
||||
isExpanded = isExpanded,
|
||||
onCurrencyClick = onCurrencyClick,
|
||||
)
|
||||
CurrencyExpandedContent(
|
||||
currency = currency,
|
||||
addedTokens = addedTokens,
|
||||
addedBlockchains = addedBlockchains,
|
||||
allowToAdd = allowToAdd,
|
||||
isExpanded = isExpanded,
|
||||
onAddCurrencyToggle = onAddCurrencyToggle,
|
||||
onNetworkItemClick = onNetworkItemClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,213 +0,0 @@
|
|||
package com.tangem.tap.features.tokens.legacy.ui.compose.currencyItem
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import coil.compose.SubcomposeAsyncImage
|
||||
import coil.request.ImageRequest
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.core.ui.extensions.getActiveIconRes
|
||||
import com.tangem.domain.common.extensions.fromNetworkId
|
||||
import com.tangem.tap.common.extensions.getGreyedOutIconRes
|
||||
import com.tangem.tap.domain.tokens.Contract
|
||||
import com.tangem.tap.domain.tokens.Currency
|
||||
import com.tangem.tap.features.tokens.legacy.redux.TokenWithBlockchain
|
||||
import com.tangem.tap.features.tokens.legacy.ui.compose.CurrencyPlaceholderIcon
|
||||
import com.tangem.tap.features.tokens.legacy.ui.compose.fullName
|
||||
import com.tangem.wallet.R
|
||||
|
||||
@Suppress("LongMethod", "MagicNumber")
|
||||
@Composable
|
||||
fun CurrencyItemHeader(
|
||||
currency: Currency,
|
||||
addedTokens: List<TokenWithBlockchain>,
|
||||
addedBlockchains: List<Blockchain>,
|
||||
isExpanded: Boolean,
|
||||
onCurrencyClick: () -> Unit,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(min = 62.dp)
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = null,
|
||||
onClick = onCurrencyClick,
|
||||
),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.align(Alignment.CenterVertically)
|
||||
.clip(RoundedCornerShape(6.dp)),
|
||||
) {
|
||||
SubcomposeAsyncImage(
|
||||
modifier = Modifier
|
||||
.size(46.dp),
|
||||
model = ImageRequest.Builder(LocalContext.current)
|
||||
.data(currency.iconUrl)
|
||||
.crossfade(true)
|
||||
.build(),
|
||||
contentDescription = currency.id,
|
||||
loading = { CurrencyPlaceholderIcon(currency.id) },
|
||||
error = { CurrencyPlaceholderIcon(currency.id) },
|
||||
)
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.align(Alignment.CenterVertically),
|
||||
) {
|
||||
Text(
|
||||
text = currency.fullName,
|
||||
fontSize = 17.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color(0xFF1C1C1E),
|
||||
)
|
||||
Spacer(modifier = Modifier.size(6.dp))
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(20.dp),
|
||||
) {
|
||||
if (isExpanded) {
|
||||
Text(
|
||||
modifier = Modifier.align(Alignment.TopStart),
|
||||
text = stringResource(id = R.string.currency_subtitle_expanded),
|
||||
fontSize = 13.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color(0xFF8E8E93),
|
||||
)
|
||||
} else {
|
||||
NetworksRow(
|
||||
currency = currency,
|
||||
addedTokens = addedTokens,
|
||||
addedBlockchains = addedBlockchains,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val expandCollapseArrow = if (isExpanded) R.drawable.ic_arrow_extended else R.drawable.ic_arrow_collapsed
|
||||
Image(
|
||||
painter = painterResource(id = expandCollapseArrow),
|
||||
contentDescription = null,
|
||||
Modifier
|
||||
.padding(20.dp)
|
||||
.align(Alignment.CenterVertically),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NetworksRow(
|
||||
currency: Currency,
|
||||
addedTokens: List<TokenWithBlockchain>,
|
||||
addedBlockchains: List<Blockchain>,
|
||||
) {
|
||||
Row {
|
||||
if (currency.contracts.isNotEmpty()) {
|
||||
currency.contracts.map { contract ->
|
||||
if (contract.address == null) {
|
||||
BlockchainNetworkItem(
|
||||
blockchain = contract.blockchain,
|
||||
isMainNetwork = true,
|
||||
addedBlockchains = addedBlockchains,
|
||||
)
|
||||
} else {
|
||||
val isAdded = addedTokens.any(contract::isInclude)
|
||||
val iconResId = if (isAdded) {
|
||||
getActiveIconRes(contract.blockchain.id)
|
||||
} else {
|
||||
contract.blockchain.getGreyedOutIconRes()
|
||||
}
|
||||
Image(
|
||||
modifier = Modifier
|
||||
.size(20.dp),
|
||||
painter = painterResource(id = iconResId),
|
||||
contentDescription = null,
|
||||
)
|
||||
Spacer(Modifier.size(5.dp))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
BlockchainNetworkItem(
|
||||
blockchain = Blockchain.fromNetworkId(currency.id),
|
||||
isMainNetwork = false,
|
||||
addedBlockchains = addedBlockchains,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
@Composable
|
||||
private fun BlockchainNetworkItem(
|
||||
blockchain: Blockchain?,
|
||||
isMainNetwork: Boolean,
|
||||
addedBlockchains: List<Blockchain>,
|
||||
) {
|
||||
val added = addedBlockchains.contains(blockchain)
|
||||
val icon = if (added) blockchain?.let { getActiveIconRes(it.id) } else blockchain?.getGreyedOutIconRes()
|
||||
if (icon != null) {
|
||||
Box(
|
||||
Modifier
|
||||
.size(20.dp),
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = icon),
|
||||
contentDescription = null,
|
||||
Modifier
|
||||
.size(20.dp),
|
||||
)
|
||||
if (isMainNetwork) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopEnd)
|
||||
.size(7.dp)
|
||||
.clip(CircleShape)
|
||||
.background(Color.White),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.align(Alignment.Center)
|
||||
.size(5.dp)
|
||||
.clip(CircleShape)
|
||||
.background(Color(0xFF1ACE80)),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.size(5.dp))
|
||||
}
|
||||
}
|
||||
|
||||
private fun Contract.isInclude(addedToken: TokenWithBlockchain): Boolean =
|
||||
address == addedToken.token.contractAddress && blockchain == addedToken.blockchain
|
||||
|
|
@ -1,186 +0,0 @@
|
|||
package com.tangem.tap.features.tokens.legacy.ui.compose.currencyItem
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.Switch
|
||||
import androidx.compose.material.SwitchDefaults
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import coil.compose.SubcomposeAsyncImage
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.core.ui.extensions.getActiveIconRes
|
||||
import com.tangem.tap.common.extensions.fullNameWithoutTestnet
|
||||
import com.tangem.tap.common.extensions.getGreyedOutIconRes
|
||||
import com.tangem.tap.common.extensions.getNetworkName
|
||||
import com.tangem.tap.domain.tokens.Contract
|
||||
import com.tangem.tap.domain.tokens.Currency
|
||||
import com.tangem.tap.features.tokens.impl.presentation.ui.NetworkItemArrow
|
||||
import com.tangem.tap.features.tokens.legacy.redux.ContractAddress
|
||||
import com.tangem.tap.features.tokens.legacy.redux.TokenWithBlockchain
|
||||
import com.tangem.tap.features.tokens.legacy.ui.compose.CurrencyPlaceholderIcon
|
||||
|
||||
@Suppress("LongParameterList", "LongMethod", "MagicNumber")
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun NetworkItem(
|
||||
currency: Currency,
|
||||
contract: Contract,
|
||||
blockchain: Blockchain,
|
||||
allowToAdd: Boolean,
|
||||
added: Boolean,
|
||||
index: Int,
|
||||
size: Int,
|
||||
onAddCurrencyToggle: (Currency, TokenWithBlockchain?) -> Unit,
|
||||
onNetworkItemClick: (ContractAddress) -> Unit,
|
||||
) {
|
||||
val rowHeight = 53.dp
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(rowHeight)
|
||||
.combinedClickable(
|
||||
enabled = allowToAdd,
|
||||
onLongClick = {
|
||||
contract.address?.let { onNetworkItemClick(it) }
|
||||
},
|
||||
onClick = {},
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
),
|
||||
) {
|
||||
Box(
|
||||
Modifier
|
||||
.width(78.dp)
|
||||
.padding(start = 38.dp),
|
||||
) {
|
||||
NetworkItemArrow(
|
||||
itemHeight = rowHeight,
|
||||
isLastItem = index == size - 1,
|
||||
)
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterVertically),
|
||||
) {
|
||||
SubcomposeAsyncImage(
|
||||
model = if (added) getActiveIconRes(blockchain.id) else blockchain.getGreyedOutIconRes(),
|
||||
contentDescription = blockchain.fullName,
|
||||
loading = { CurrencyPlaceholderIcon(blockchain.id) },
|
||||
error = { CurrencyPlaceholderIcon(blockchain.id) },
|
||||
modifier = Modifier
|
||||
.size(20.dp),
|
||||
)
|
||||
if (contract.address == null) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopEnd)
|
||||
.size(7.dp)
|
||||
.clip(CircleShape)
|
||||
.background(Color.White),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.align(Alignment.Center)
|
||||
.size(5.dp)
|
||||
.clip(CircleShape)
|
||||
.background(Color(0xFF1ACE80)),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.width(6.dp))
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.align(Alignment.CenterVertically),
|
||||
text = prepareNetworkNameSpannableText(
|
||||
blockchain = blockchain,
|
||||
contractAddress = contract.address,
|
||||
),
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
fontSize = 13.sp,
|
||||
color = if (added) Color.Black else Color(0xFF848488),
|
||||
)
|
||||
|
||||
if (allowToAdd) {
|
||||
val token = if (contract.address != null) {
|
||||
Token(
|
||||
id = currency.id,
|
||||
name = currency.name,
|
||||
symbol = currency.symbol,
|
||||
contractAddress = contract.address,
|
||||
decimals = contract.decimalCount!!,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
val tokenWithBlockchain = token?.let { TokenWithBlockchain(it, contract.blockchain) }
|
||||
|
||||
val currencyToSave = if (contract.address == null) {
|
||||
currency.copy(id = contract.networkId)
|
||||
} else {
|
||||
currency
|
||||
}
|
||||
|
||||
Switch(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.padding(start = 16.dp, end = 16.dp),
|
||||
checked = added,
|
||||
onCheckedChange = { onAddCurrencyToggle(currencyToSave, tokenWithBlockchain) },
|
||||
colors = SwitchDefaults.colors(
|
||||
checkedThumbColor = Color(0xFF1ACE80),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
@Composable
|
||||
private fun prepareNetworkNameSpannableText(blockchain: Blockchain, contractAddress: String?): AnnotatedString {
|
||||
val blockchainName = blockchain.fullNameWithoutTestnet.uppercase()
|
||||
val additionalText =
|
||||
if (contractAddress == null) "MAIN" else blockchain.getNetworkName().uppercase()
|
||||
|
||||
val text = "$blockchainName $additionalText"
|
||||
|
||||
val startOfAdditionalText =
|
||||
if (additionalText.isNotBlank()) text.indexOf(additionalText) else text.length
|
||||
|
||||
val spanStyles = listOf(
|
||||
AnnotatedString.Range(
|
||||
SpanStyle(
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = if (contractAddress != null) Color(0xFF8E8E93) else Color(0xFF1ACE80),
|
||||
),
|
||||
start = startOfAdditionalText,
|
||||
end = text.length,
|
||||
),
|
||||
)
|
||||
return AnnotatedString(text = text, spanStyles = spanStyles)
|
||||
}
|
||||
|
|
@ -6,7 +6,6 @@ import com.badoo.mvicore.modelWatcher
|
|||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.domain.common.TapWorkarounds.derivationStyle
|
||||
import com.tangem.tap.common.analytics.events.MainScreen
|
||||
import com.tangem.tap.common.analytics.events.ManageTokens
|
||||
import com.tangem.tap.common.analytics.events.Portfolio
|
||||
import com.tangem.tap.common.entities.FiatCurrency
|
||||
import com.tangem.tap.common.extensions.getQuantityString
|
||||
|
|
@ -108,16 +107,13 @@ class MultiWalletView : WalletView() {
|
|||
binding.btnAddToken.setOnClickListener {
|
||||
val card = store.state.globalState.scanResponse!!.card
|
||||
Analytics.send(Portfolio.ButtonManageTokens())
|
||||
store.dispatch(TokensAction.LoadCurrencies(scanResponse = store.state.globalState.scanResponse))
|
||||
store.dispatch(TokensAction.AllowToAddTokens(true))
|
||||
|
||||
store.dispatch(
|
||||
TokensAction.SetAddedCurrencies(
|
||||
TokensAction.SetArgs.ManageAccess(
|
||||
wallets = state.walletsDataFromStores,
|
||||
derivationStyle = card.derivationStyle,
|
||||
),
|
||||
)
|
||||
|
||||
Analytics.send(ManageTokens.ScreenOpened())
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.AddTokens))
|
||||
}
|
||||
handleErrorStates(state = state, binding = binding, fragment = fragment)
|
||||
|
|
@ -175,7 +171,8 @@ class MultiWalletView : WalletView() {
|
|||
fragment.getString(R.string.wallet_error_unsupported_blockchain_subtitle),
|
||||
)
|
||||
}
|
||||
else -> { /* no-op */ }
|
||||
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,14 +4,12 @@ import com.tangem.datasource.asset.AssetReader
|
|||
import com.tangem.datasource.connection.NetworkConnectionManager
|
||||
import com.tangem.features.tester.api.TesterRouter
|
||||
import com.tangem.tap.features.customtoken.api.featuretoggles.CustomTokenFeatureToggles
|
||||
import com.tangem.tap.features.tokens.api.featuretoggles.TokensListFeatureToggles
|
||||
import org.rekotlin.StateType
|
||||
|
||||
data class DaggerGraphState(
|
||||
val assetReader: AssetReader? = null,
|
||||
val testerRouter: TesterRouter? = null,
|
||||
val networkConnectionManager: NetworkConnectionManager? = null,
|
||||
val tokensListFeatureToggles: TokensListFeatureToggles? = null,
|
||||
val customTokenFeatureToggles: CustomTokenFeatureToggles? = null,
|
||||
) : StateType {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,36 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/coordinator_details_confirm"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/backgroundLightGray"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/app_bar"
|
||||
style="@style/Widget.MaterialComponents.Toolbar.Surface"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/backgroundLightGray"
|
||||
android:fitsSystemWindows="true">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
app:menu="@menu/menu_popular_tokens"
|
||||
app:navigationIcon="@drawable/ic_baseline_arrow_back_24"
|
||||
app:title="@string/main_manage_tokens" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
|
||||
<androidx.compose.ui.platform.ComposeView
|
||||
android:id="@+id/cv_currencies"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_search"
|
||||
android:icon="@drawable/ic_search"
|
||||
android:title="@string/common_search"
|
||||
app:actionViewClass="androidx.appcompat.widget.SearchView"
|
||||
app:showAsAction="always" />
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_navigate_add_custom_token"
|
||||
android:icon="@drawable/ic_add"
|
||||
android:title="@string/common_add"
|
||||
app:iconTint="@color/darkGray2"
|
||||
app:showAsAction="always" />
|
||||
|
||||
</menu>
|
||||
|
|
@ -3,12 +3,8 @@
|
|||
"name": "OPTIMISM_SWAP_FEATURE_ENABLED",
|
||||
"version": "4.3.1"
|
||||
},
|
||||
{
|
||||
"name": "REDESIGNED_TOKEN_LIST_SCREEN_ENABLED",
|
||||
"version": "4.4.0"
|
||||
},
|
||||
{
|
||||
"name": "REDESIGNED_CUSTOM_TOKEN_SCREEN_ENABLED",
|
||||
"version": "undefined"
|
||||
"version": "4.6.0"
|
||||
}
|
||||
]
|
||||
Loading…
Add table
Add a link
Reference in a new issue