Updated on 2026-08-14
This commit is contained in:
commit
5aa02ecff7
21 changed files with 214 additions and 151 deletions
|
|
@ -20,7 +20,7 @@ import com.tangem.tap.features.home.HomeFragment
|
|||
import com.tangem.tap.features.main.ui.ModalNotificationBottomSheetFragment
|
||||
import com.tangem.tap.features.onboarding.products.note.OnboardingNoteFragment
|
||||
import com.tangem.tap.features.onboarding.products.otherCards.OnboardingOtherCardsFragment
|
||||
import com.tangem.tap.features.onboarding.products.twins.ui.TwinsCardsFragment
|
||||
import com.tangem.tap.features.onboarding.products.twins.ui.OnboardingTwinsFragment
|
||||
import com.tangem.tap.features.onboarding.products.wallet.ui.OnboardingWalletFragment
|
||||
import com.tangem.tap.features.saveWallet.ui.SaveWalletBottomSheetFragment
|
||||
import com.tangem.tap.features.send.ui.SendFragment
|
||||
|
|
@ -125,7 +125,7 @@ private fun fragmentFactory(screen: AppScreen): Fragment {
|
|||
AppScreen.Shop -> ShopFragment()
|
||||
AppScreen.OnboardingNote -> OnboardingNoteFragment()
|
||||
AppScreen.OnboardingWallet -> OnboardingWalletFragment()
|
||||
AppScreen.OnboardingTwins -> TwinsCardsFragment()
|
||||
AppScreen.OnboardingTwins -> OnboardingTwinsFragment()
|
||||
AppScreen.OnboardingOther -> OnboardingOtherCardsFragment()
|
||||
AppScreen.Wallet -> {
|
||||
store.state.daggerGraphState
|
||||
|
|
|
|||
|
|
@ -1,21 +1,22 @@
|
|||
package com.tangem.tap.features.details.ui.appsettings
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.core.ui.screen.ComposeFragment
|
||||
import com.tangem.core.ui.theme.AppThemeModeHolder
|
||||
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
|
||||
import com.tangem.tap.features.details.redux.DetailsAction
|
||||
import com.tangem.tap.features.details.redux.DetailsState
|
||||
import com.tangem.tap.store
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.rekotlin.StoreSubscriber
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
internal class AppSettingsFragment : ComposeFragment(), StoreSubscriber<DetailsState> {
|
||||
internal class AppSettingsFragment : ComposeFragment() {
|
||||
|
||||
@Inject
|
||||
override lateinit var appThemeModeHolder: AppThemeModeHolder
|
||||
|
|
@ -23,43 +24,20 @@ internal class AppSettingsFragment : ComposeFragment(), StoreSubscriber<DetailsS
|
|||
@Inject
|
||||
lateinit var appCurrencyRepository: AppCurrencyRepository
|
||||
|
||||
private val viewModel by lazy(mode = LazyThreadSafetyMode.NONE) {
|
||||
AppSettingsViewModel(store, appCurrencyRepository)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun ScreenContent(modifier: Modifier) {
|
||||
val viewModel = hiltViewModel<AppSettingsViewModel>().apply {
|
||||
LocalLifecycleOwner.current.lifecycle.addObserver(observer = this)
|
||||
}
|
||||
val state by viewModel.uiState.collectAsStateWithLifecycle()
|
||||
|
||||
AppSettingsScreen(
|
||||
modifier = modifier,
|
||||
state = viewModel.uiState,
|
||||
state = state,
|
||||
onBackClick = {
|
||||
store.dispatch(DetailsAction.ResetCardSettingsData)
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
viewModel.checkBiometricsStatus(lifecycleScope)
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
store.subscribe(this) { state ->
|
||||
state.skipRepeats { oldState, newState ->
|
||||
oldState.detailsState == newState.detailsState
|
||||
}.select { it.detailsState }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
store.unsubscribe(this)
|
||||
}
|
||||
|
||||
override fun newState(state: DetailsState) {
|
||||
if (activity == null || view == null) return
|
||||
viewModel.updateState(state)
|
||||
}
|
||||
}
|
||||
|
|
@ -10,7 +10,7 @@ internal class AppSettingsItemsFactory {
|
|||
|
||||
fun createEnrollBiometricsCard(onClick: () -> Unit): Item.Card {
|
||||
return Item.Card(
|
||||
id = "enroll_biometrics_card",
|
||||
id = ID_ENROLL_BIOMETRICS_CARD,
|
||||
title = resourceReference(R.string.app_settings_enable_biometrics_title),
|
||||
description = resourceReference(R.string.app_settings_enable_biometrics_description),
|
||||
iconResId = R.drawable.ic_alert_circle_24,
|
||||
|
|
@ -24,7 +24,7 @@ internal class AppSettingsItemsFactory {
|
|||
onCheckedChange: (Boolean) -> Unit,
|
||||
): Item.Switch {
|
||||
return Item.Switch(
|
||||
id = "save_wallets_switch",
|
||||
id = ID_SAVE_WALLETS_SWITCH,
|
||||
title = resourceReference(R.string.app_settings_saved_wallet),
|
||||
description = resourceReference(R.string.app_settings_saved_wallet_footer),
|
||||
isEnabled = isEnabled,
|
||||
|
|
@ -39,7 +39,7 @@ internal class AppSettingsItemsFactory {
|
|||
onCheckedChange: (Boolean) -> Unit,
|
||||
): Item.Switch {
|
||||
return Item.Switch(
|
||||
id = "save_access_codes_switch",
|
||||
id = ID_SAVE_ACCESS_CODES_SWITCH,
|
||||
title = resourceReference(R.string.app_settings_saved_access_codes),
|
||||
description = resourceReference(R.string.app_settings_saved_access_codes_footer),
|
||||
isEnabled = isEnabled,
|
||||
|
|
@ -54,7 +54,7 @@ internal class AppSettingsItemsFactory {
|
|||
onCheckedChange: (Boolean) -> Unit,
|
||||
): Item.Switch {
|
||||
return Item.Switch(
|
||||
id = "flip_to_hide_balance_switch",
|
||||
id = ID_FLIP_TO_HIDE_BALANCE_SWITCH,
|
||||
title = resourceReference(R.string.details_row_title_flip_to_hide),
|
||||
description = resourceReference(R.string.details_row_description_flip_to_hide),
|
||||
isEnabled = isEnabled,
|
||||
|
|
@ -65,7 +65,7 @@ internal class AppSettingsItemsFactory {
|
|||
|
||||
fun createSelectAppCurrencyButton(currentAppCurrencyName: String, onClick: () -> Unit): Item.Button {
|
||||
return Item.Button(
|
||||
id = "select_app_currency_button",
|
||||
id = ID_SELECT_APP_CURRENCY_BUTTON,
|
||||
title = resourceReference(R.string.details_row_title_currency),
|
||||
description = stringReference(currentAppCurrencyName),
|
||||
isEnabled = true,
|
||||
|
|
@ -75,7 +75,7 @@ internal class AppSettingsItemsFactory {
|
|||
|
||||
fun createSelectThemeModeButton(currentThemeMode: AppThemeMode, onClick: () -> Unit): Item.Button {
|
||||
return Item.Button(
|
||||
id = "select_theme_mode_button",
|
||||
id = ID_SELECT_THEME_MODE_BUTTON,
|
||||
title = resourceReference(R.string.app_settings_theme_selector_title),
|
||||
description = resourceReference(
|
||||
id = when (currentThemeMode) {
|
||||
|
|
@ -88,4 +88,13 @@ internal class AppSettingsItemsFactory {
|
|||
onClick = onClick,
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val ID_ENROLL_BIOMETRICS_CARD = "enroll_biometrics_card"
|
||||
const val ID_SAVE_WALLETS_SWITCH = "save_wallets_switch"
|
||||
const val ID_SAVE_ACCESS_CODES_SWITCH = "save_access_codes_switch"
|
||||
const val ID_FLIP_TO_HIDE_BALANCE_SWITCH = "flip_to_hide_balance_switch"
|
||||
const val ID_SELECT_APP_CURRENCY_BUTTON = "select_app_currency_button"
|
||||
const val ID_SELECT_THEME_MODE_BUTTON = "select_theme_mode_button"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
package com.tangem.tap.features.details.ui.appsettings
|
||||
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.LifecycleCoroutineScope
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
|
||||
|
|
@ -13,53 +13,78 @@ import com.tangem.tap.common.analytics.events.AnalyticsParam
|
|||
import com.tangem.tap.common.analytics.events.Settings
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.dispatchWithMain
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.features.details.redux.AppSetting
|
||||
import com.tangem.tap.features.details.redux.AppSettingsState
|
||||
import com.tangem.tap.features.details.redux.DetailsAction
|
||||
import com.tangem.tap.features.details.redux.DetailsState
|
||||
import com.tangem.tap.features.details.ui.appsettings.analytics.AppSettingsItemsAnalyticsSender
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.utils.coroutines.JobHolder
|
||||
import com.tangem.utils.coroutines.saveIn
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.rekotlin.Store
|
||||
import kotlinx.coroutines.flow.*
|
||||
import org.rekotlin.StoreSubscriber
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class AppSettingsViewModel(
|
||||
private val store: Store<AppState>,
|
||||
@HiltViewModel
|
||||
internal class AppSettingsViewModel @Inject constructor(
|
||||
private val appCurrencyRepository: AppCurrencyRepository,
|
||||
) {
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val appSettingsItemsAnalyticsSender: AppSettingsItemsAnalyticsSender,
|
||||
) : ViewModel(),
|
||||
StoreSubscriber<DetailsState>,
|
||||
DefaultLifecycleObserver {
|
||||
|
||||
private val itemsFactory = AppSettingsItemsFactory()
|
||||
private val dialogsFactory = AppSettingsDialogsFactory()
|
||||
|
||||
private val appCurrencyUpdatesJobHolder = JobHolder()
|
||||
|
||||
var uiState: AppSettingsScreenState by mutableStateOf(AppSettingsScreenState.Loading)
|
||||
private set
|
||||
private val _uiState: MutableStateFlow<AppSettingsScreenState> = MutableStateFlow(
|
||||
value = AppSettingsScreenState.Loading,
|
||||
)
|
||||
val uiState: StateFlow<AppSettingsScreenState> = _uiState
|
||||
|
||||
init {
|
||||
bootstrapAppCurrencyUpdates()
|
||||
|
||||
subscribeToStoreChanges()
|
||||
sendItemsAnalytics()
|
||||
}
|
||||
|
||||
fun updateState(state: DetailsState) {
|
||||
uiState = AppSettingsScreenState.Content(
|
||||
items = buildItems(state.appSettingsState),
|
||||
dialog = (uiState as? AppSettingsScreenState.Content)?.dialog,
|
||||
)
|
||||
override fun newState(state: DetailsState) {
|
||||
val items = buildItems(state.appSettingsState)
|
||||
|
||||
_uiState.update { prevState ->
|
||||
when (prevState) {
|
||||
is AppSettingsScreenState.Content -> prevState.copy(
|
||||
items = items,
|
||||
)
|
||||
is AppSettingsScreenState.Loading -> AppSettingsScreenState.Content(
|
||||
items = items,
|
||||
dialog = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun checkBiometricsStatus(lifecycleScope: LifecycleCoroutineScope) {
|
||||
store.dispatch(DetailsAction.AppSettings.CheckBiometricsStatus(lifecycleScope))
|
||||
override fun onResume(owner: LifecycleOwner) {
|
||||
store.dispatch(DetailsAction.AppSettings.CheckBiometricsStatus(owner.lifecycleScope))
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
store.unsubscribe(subscriber = this)
|
||||
}
|
||||
|
||||
private fun buildItems(state: AppSettingsState): ImmutableList<AppSettingsScreenState.Item> {
|
||||
val items = buildList {
|
||||
if (state.needEnrollBiometrics) {
|
||||
Analytics.send(Settings.AppSettings.EnableBiometrics)
|
||||
itemsFactory.createEnrollBiometricsCard(onClick = ::enrollBiometrics).let(::add)
|
||||
itemsFactory.createEnrollBiometricsCard(
|
||||
onClick = ::enrollBiometrics,
|
||||
).let(::add)
|
||||
}
|
||||
|
||||
itemsFactory.createSelectAppCurrencyButton(
|
||||
|
|
@ -89,9 +114,10 @@ internal class AppSettingsViewModel(
|
|||
onCheckedChange = ::onFlipToHideBalanceToggled,
|
||||
).let(::add)
|
||||
|
||||
itemsFactory.createSelectThemeModeButton(state.selectedThemeMode) {
|
||||
showThemeModeSelector(state.selectedThemeMode)
|
||||
}.let(::add)
|
||||
itemsFactory.createSelectThemeModeButton(
|
||||
currentThemeMode = state.selectedThemeMode,
|
||||
onClick = { showThemeModeSelector(state.selectedThemeMode) },
|
||||
).let(::add)
|
||||
}
|
||||
|
||||
return items.toImmutableList()
|
||||
|
|
@ -111,7 +137,7 @@ internal class AppSettingsViewModel(
|
|||
dialog = dialogsFactory.createThemeModeSelectorDialog(
|
||||
selectedModeIndex = selectedMode.ordinal,
|
||||
onSelect = { mode ->
|
||||
Analytics.send(
|
||||
analyticsEventHandler.send(
|
||||
event = Settings.AppSettings.ThemeSwitched(
|
||||
theme = AnalyticsParam.AppTheme.fromAppThemeMode(mode),
|
||||
),
|
||||
|
|
@ -167,7 +193,7 @@ internal class AppSettingsViewModel(
|
|||
|
||||
private fun onFlipToHideBalanceToggled(enable: Boolean) {
|
||||
val param = AnalyticsParam.OnOffState(enable)
|
||||
Analytics.send(Settings.AppSettings.HideBalanceChanged(param))
|
||||
analyticsEventHandler.send(Settings.AppSettings.HideBalanceChanged(param))
|
||||
|
||||
store.dispatch(DetailsAction.AppSettings.ChangeBalanceHiding(hideBalance = enable))
|
||||
}
|
||||
|
|
@ -188,10 +214,28 @@ internal class AppSettingsViewModel(
|
|||
.saveIn(appCurrencyUpdatesJobHolder)
|
||||
}
|
||||
|
||||
private fun subscribeToStoreChanges() {
|
||||
store.subscribe(subscriber = this) { state ->
|
||||
state.skipRepeats { oldState, newState ->
|
||||
oldState.detailsState == newState.detailsState
|
||||
}.select { it.detailsState }
|
||||
}
|
||||
}
|
||||
|
||||
private fun sendItemsAnalytics() {
|
||||
uiState
|
||||
.filterIsInstance<AppSettingsScreenState.Content>()
|
||||
.distinctUntilChangedBy(AppSettingsScreenState.Content::items)
|
||||
.onEach { appSettingsItemsAnalyticsSender.send(it.items) }
|
||||
.launchIn(scope)
|
||||
}
|
||||
|
||||
private fun updateContentState(block: AppSettingsScreenState.Content.() -> AppSettingsScreenState.Content) {
|
||||
uiState = when (val state = uiState) {
|
||||
is AppSettingsScreenState.Content -> block(state)
|
||||
is AppSettingsScreenState.Loading -> state
|
||||
_uiState.update { prevState ->
|
||||
when (prevState) {
|
||||
is AppSettingsScreenState.Content -> block(prevState)
|
||||
is AppSettingsScreenState.Loading -> prevState
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package com.tangem.tap.features.details.ui.appsettings.analytics
|
||||
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
import com.tangem.tap.common.analytics.events.Settings
|
||||
import com.tangem.tap.features.details.ui.appsettings.AppSettingsItemsFactory
|
||||
import com.tangem.tap.features.details.ui.appsettings.AppSettingsScreenState
|
||||
import dagger.hilt.android.scopes.ViewModelScoped
|
||||
import javax.inject.Inject
|
||||
|
||||
@ViewModelScoped
|
||||
internal class AppSettingsItemsAnalyticsSender @Inject constructor(
|
||||
private val analyticsHandler: AnalyticsEventHandler,
|
||||
) {
|
||||
|
||||
fun send(items: List<AppSettingsScreenState.Item>) {
|
||||
val events = getEvents(items)
|
||||
|
||||
events.forEach { analyticsHandler.send(it) }
|
||||
}
|
||||
|
||||
private fun getEvents(items: List<AppSettingsScreenState.Item>): Set<AnalyticsEvent> {
|
||||
return items.mapNotNullTo(mutableSetOf(), ::getEvent)
|
||||
}
|
||||
|
||||
private fun getEvent(item: AppSettingsScreenState.Item): AnalyticsEvent? {
|
||||
return when (item.id) {
|
||||
AppSettingsItemsFactory.ID_ENROLL_BIOMETRICS_CARD -> Settings.AppSettings.EnableBiometrics
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import com.tangem.common.doOnFailure
|
|||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.models.Basic
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
|
|
@ -14,7 +15,6 @@ import com.tangem.domain.userwallets.UserWalletBuilder
|
|||
import com.tangem.domain.userwallets.UserWalletIdBuilder
|
||||
import com.tangem.tap.*
|
||||
import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter
|
||||
import com.tangem.core.analytics.models.Basic
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.onUserWalletSelected
|
||||
import com.tangem.tap.common.extensions.removeContext
|
||||
|
|
@ -106,14 +106,17 @@ object OnboardingHelper {
|
|||
backupCardsIds = backupCardsIds?.toSet(),
|
||||
),
|
||||
)
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet))
|
||||
delay(timeMillis = 1_800)
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.SaveWallet))
|
||||
}
|
||||
// If device has no biometry and save wallet screen has been shown, then go through old scenario
|
||||
else -> proceedWithScanResponse(scanResponse, backupCardsIds)
|
||||
else -> {
|
||||
proceedWithScanResponse(scanResponse, backupCardsIds)
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet))
|
||||
}
|
||||
|
||||
fun onInterrupted() {
|
||||
|
|
@ -143,7 +146,7 @@ object OnboardingHelper {
|
|||
Timber.e(error, "Unable to save user wallet")
|
||||
}
|
||||
.doOnSuccess {
|
||||
scope.launch { store.onUserWalletSelected(userWallet) }
|
||||
mainScope.launch { store.onUserWalletSelected(userWallet) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -39,7 +39,7 @@ import javax.inject.Inject
|
|||
|
||||
@Suppress("LargeClass")
|
||||
@AndroidEntryPoint
|
||||
class TwinsCardsFragment : BaseOnboardingFragment<TwinCardsState>() {
|
||||
internal class OnboardingTwinsFragment : BaseOnboardingFragment<TwinCardsState>() {
|
||||
|
||||
@Inject
|
||||
lateinit var assetReader: AssetReader
|
||||
|
|
@ -11,8 +11,8 @@ internal object AttestationFailedDialog {
|
|||
|
||||
fun create(context: Context): AlertDialog {
|
||||
return MaterialAlertDialogBuilder(context, R.style.CustomMaterialDialog).apply {
|
||||
setTitle(R.string.attestation_online_failed_title)
|
||||
setMessage(R.string.attestation_online_failed_body)
|
||||
setTitle(R.string.common_error)
|
||||
setMessage(R.string.issuer_signature_loading_failed)
|
||||
setPositiveButton(R.string.ok) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@ internal object CoinsResponseConverter : Converter<CoinsResponse, List<Token>> {
|
|||
networks = token.networks.mapNotNull { network ->
|
||||
val blockchain = Blockchain.fromNetworkId(network.networkId) ?: return@mapNotNull null
|
||||
|
||||
if (!blockchain.canHandleTokens()) return@mapNotNull null
|
||||
|
||||
Token.Network(
|
||||
id = network.networkId,
|
||||
blockchain = blockchain,
|
||||
|
|
|
|||
|
|
@ -124,7 +124,6 @@ internal class ConfigManagerImpl @Inject constructor() : ConfigManager {
|
|||
gnosis = GetBlockAccessToken(jsonRpc = accessTokens.gnosis?.jsonRPC),
|
||||
cronos = GetBlockAccessToken(jsonRpc = accessTokens.cronos?.jsonRPC),
|
||||
solana = GetBlockAccessToken(jsonRpc = accessTokens.solana?.jsonRPC),
|
||||
stellar = GetBlockAccessToken(rest = accessTokens.stellar?.rest),
|
||||
ton = GetBlockAccessToken(jsonRpc = accessTokens.ton?.jsonRPC),
|
||||
tron = GetBlockAccessToken(rest = accessTokens.tron?.rest),
|
||||
cosmos = GetBlockAccessToken(rest = accessTokens.cosmos?.rest),
|
||||
|
|
|
|||
|
|
@ -57,7 +57,6 @@ data class GetBlockAccessTokens(
|
|||
@Json(name = "xdai") val gnosis: GetBlockToken?,
|
||||
@Json(name = "cronos") val cronos: GetBlockToken?,
|
||||
@Json(name = "solana") val solana: GetBlockToken?,
|
||||
@Json(name = "stellar") val stellar: GetBlockToken?,
|
||||
@Json(name = "ton") val ton: GetBlockToken?,
|
||||
@Json(name = "tron") val tron: GetBlockToken?,
|
||||
@Json(name = "cosmos-hub") val cosmos: GetBlockToken?,
|
||||
|
|
|
|||
|
|
@ -55,7 +55,6 @@ internal class DefaultSwapRepository @Inject constructor(
|
|||
moshi: Moshi,
|
||||
) : SwapRepository {
|
||||
|
||||
private val tokensConverter = TokensConverter()
|
||||
private val expressDataConverter = ExpressDataConverter()
|
||||
private val leastTokenInfoConverter = LeastTokenInfoConverter()
|
||||
private val swapPairInfoConverter = SwapPairInfoConverter()
|
||||
|
|
@ -213,18 +212,6 @@ internal class DefaultSwapRepository @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun getExchangeableTokens(networkId: String): List<Currency> {
|
||||
return withContext(coroutineDispatcher.io) {
|
||||
tokensConverter.convertList(
|
||||
tangemTechApi.getCoins(
|
||||
exchangeable = true,
|
||||
active = true,
|
||||
networkIds = networkId,
|
||||
).getOrThrow().coins,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun findBestQuote(
|
||||
fromContractAddress: String,
|
||||
fromNetwork: String,
|
||||
|
|
|
|||
|
|
@ -1,40 +0,0 @@
|
|||
package com.tangem.feature.swap.converters
|
||||
|
||||
import com.tangem.datasource.api.tangemTech.models.CoinsResponse
|
||||
import com.tangem.feature.swap.domain.models.domain.Currency
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
class TokensConverter : Converter<CoinsResponse.Coin, Currency> {
|
||||
|
||||
override fun convert(value: CoinsResponse.Coin): Currency {
|
||||
val network = value.networks.first()
|
||||
return if (network.contractAddress != null && network.decimalCount != null) {
|
||||
Currency.NonNativeToken(
|
||||
id = value.id,
|
||||
name = value.name,
|
||||
symbol = value.symbol,
|
||||
networkId = network.networkId,
|
||||
contractAddress = network.contractAddress!!,
|
||||
decimalCount = network.decimalCount!!.intValueExact(),
|
||||
logoUrl = getSmallIconUrl(value.id),
|
||||
)
|
||||
} else {
|
||||
Currency.NativeToken(
|
||||
id = value.id,
|
||||
name = value.name,
|
||||
symbol = value.symbol,
|
||||
networkId = network.networkId,
|
||||
logoUrl = getSmallIconUrl(value.id),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSmallIconUrl(coin: String): String {
|
||||
return "$DEFAULT_IMAGE_HOST$LARGE_ICON_PATH/$coin.png"
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val DEFAULT_IMAGE_HOST = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/"
|
||||
private const val LARGE_ICON_PATH = "large"
|
||||
}
|
||||
}
|
||||
|
|
@ -17,11 +17,6 @@ interface SwapRepository {
|
|||
|
||||
suspend fun getRates(currencyId: String, tokenIds: List<String>): Map<String, Double>
|
||||
|
||||
/**
|
||||
* @throws com.tangem.datasource.api.common.response.ApiResponseError
|
||||
* */
|
||||
suspend fun getExchangeableTokens(networkId: String): List<Currency>
|
||||
|
||||
suspend fun getExchangeStatus(txId: String): Either<UnknownError, ExchangeStatusModel>
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
|
|
|
|||
|
|
@ -919,7 +919,7 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
|
||||
return when {
|
||||
fromToken is CryptoCurrency.Token -> {
|
||||
if (feeValue > tokenForFeeBalance.value) {
|
||||
if (feeValue > tokenForFeeBalance.value || tokenForFeeBalance.value.signum() == 0) {
|
||||
IncludeFeeInAmount.BalanceNotEnough
|
||||
} else {
|
||||
IncludeFeeInAmount.Excluded
|
||||
|
|
|
|||
|
|
@ -1,18 +1,38 @@
|
|||
package com.tangem.feature.swap.router
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import androidx.browser.customtabs.CustomTabColorSchemeParams
|
||||
import androidx.browser.customtabs.CustomTabsIntent
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
class CustomTabsManager(private val context: WeakReference<Context>) {
|
||||
|
||||
fun openUrl(url: String) {
|
||||
val customTabsIntent = CustomTabsIntent.Builder()
|
||||
.setDefaultColorSchemeParams(
|
||||
CustomTabColorSchemeParams.Builder().build(),
|
||||
)
|
||||
.build()
|
||||
context.get()?.let { customTabsIntent.launchUrl(it, Uri.parse(url)) }
|
||||
val mContext = context.get() ?: return
|
||||
val browserIntent = Intent()
|
||||
.setAction(Intent.ACTION_VIEW)
|
||||
.addCategory(Intent.CATEGORY_BROWSABLE)
|
||||
.setDataAndType(Uri.fromParts("http", "", null), "text/plain")
|
||||
var possibleBrowsers =
|
||||
mContext.packageManager.queryIntentActivities(browserIntent, PackageManager.MATCH_DEFAULT_ONLY)
|
||||
if (possibleBrowsers.isEmpty()) {
|
||||
possibleBrowsers =
|
||||
mContext.packageManager.queryIntentActivities(browserIntent, PackageManager.MATCH_ALL)
|
||||
}
|
||||
if (possibleBrowsers.isNotEmpty()) {
|
||||
val customTabsIntent = CustomTabsIntent.Builder()
|
||||
.setDefaultColorSchemeParams(
|
||||
CustomTabColorSchemeParams.Builder().build(),
|
||||
)
|
||||
.build()
|
||||
customTabsIntent.intent.setPackage(possibleBrowsers[0].activityInfo.packageName)
|
||||
context.get()?.let { customTabsIntent.launchUrl(it, Uri.parse(url)) }
|
||||
} else {
|
||||
val browserIntent2 = Intent(Intent.ACTION_VIEW, Uri.parse(url))
|
||||
mContext.startActivity(browserIntent2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.analytics.utils
|
||||
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class SelectedWalletAnalyticsSender @Inject constructor(
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
) {
|
||||
|
||||
fun send(userWallet: UserWallet) {
|
||||
val event = getEvent(userWallet)
|
||||
|
||||
if (event != null) {
|
||||
analyticsEventHandler.send(event)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
* that cannot be processed in [WalletWarningsAnalyticsSender].
|
||||
* */
|
||||
private fun getEvent(userWallet: UserWallet): AnalyticsEvent? = when {
|
||||
userWallet.isLocked -> WalletScreenAnalyticsEvent.MainScreen.WalletUnlock
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
|
@ -43,7 +43,7 @@ internal class WalletWarningsAnalyticsSender @Inject constructor(
|
|||
is WalletNotification.Informational.DemoCard -> MainScreen.DemoCard
|
||||
is WalletNotification.Informational.MissingAddresses -> MainScreen.MissingAddresses
|
||||
is WalletNotification.RateApp -> MainScreen.HowDoYouLikeTangem
|
||||
is WalletNotification.UnlockWallets -> MainScreen.WalletUnlock
|
||||
is WalletNotification.UnlockWallets -> null // See [SelectedWalletAnalyticsSender]
|
||||
is WalletNotification.Informational.NoAccount,
|
||||
is WalletNotification.Warning.LowSignatures,
|
||||
is WalletNotification.Warning.SomeNetworksUnreachable,
|
||||
|
|
|
|||
|
|
@ -21,7 +21,9 @@ internal class WalletContentLoaderFactory @Inject constructor(
|
|||
isRefresh: Boolean = false,
|
||||
): WalletContentLoader? {
|
||||
return when {
|
||||
userWallet.isMultiCurrency -> multiWalletContentLoaderFactory.create(userWallet, clickIntents)
|
||||
userWallet.isMultiCurrency -> {
|
||||
multiWalletContentLoaderFactory.create(userWallet, clickIntents)
|
||||
}
|
||||
userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken() -> {
|
||||
singleWalletWithTokenContentLoaderFactory.create(userWallet, clickIntents)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
|||
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase
|
||||
import com.tangem.feature.wallet.presentation.router.InnerWalletRouter
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.SelectedWalletAnalyticsSender
|
||||
import com.tangem.feature.wallet.presentation.wallet.loaders.WalletScreenContentLoader
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletEvent
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletEvent.DemonstrateWalletsScrollPreview.Direction
|
||||
|
|
@ -55,6 +56,7 @@ internal class WalletViewModelV2 @Inject constructor(
|
|||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val reduxStateHolder: ReduxStateHolder,
|
||||
private val screenLifecycleProvider: ScreenLifecycleProvider,
|
||||
private val selectedWalletAnalyticsSender: SelectedWalletAnalyticsSender,
|
||||
) : ViewModel() {
|
||||
|
||||
val uiState: StateFlow<WalletScreenState> = stateHolder.uiState
|
||||
|
|
@ -110,8 +112,8 @@ internal class WalletViewModelV2 @Inject constructor(
|
|||
|
||||
private fun subscribeToUserWalletsUpdates(shouldSaveUserWallet: Boolean) {
|
||||
getWalletsUseCase()
|
||||
.distinctUntilChanged()
|
||||
.conflate()
|
||||
.distinctUntilChanged()
|
||||
.map {
|
||||
walletsUpdateActionResolver.resolve(
|
||||
wallets = it,
|
||||
|
|
@ -152,6 +154,8 @@ internal class WalletViewModelV2 @Inject constructor(
|
|||
reduxStateHolder.dispatch(
|
||||
action = WalletConnectActions.New.SetupUserChains(userWallet = selectedWallet),
|
||||
)
|
||||
|
||||
selectedWalletAnalyticsSender.send(selectedWallet)
|
||||
}
|
||||
}
|
||||
.flowOn(dispatchers.main)
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ spr-client = "3.6.2"
|
|||
# endregion Other libraries
|
||||
|
||||
# region Tangem
|
||||
tangemBlockchainSdk = "develop-458"
|
||||
tangemBlockchainSdk = "develop-463"
|
||||
#tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds
|
||||
tangemCardSdk = "develop-319"
|
||||
#tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue