diff --git a/app/src/main/java/org/p2p/wallet/common/feature_toggles/di/FeatureTogglesModule.kt b/app/src/main/java/org/p2p/wallet/common/feature_toggles/di/FeatureTogglesModule.kt index b2c1862a26..5f30dd6628 100644 --- a/app/src/main/java/org/p2p/wallet/common/feature_toggles/di/FeatureTogglesModule.kt +++ b/app/src/main/java/org/p2p/wallet/common/feature_toggles/di/FeatureTogglesModule.kt @@ -17,6 +17,7 @@ import org.p2p.wallet.common.feature_toggles.toggles.remote.NetworkObservationDe import org.p2p.wallet.common.feature_toggles.toggles.remote.NetworkObservationFeatureToggle import org.p2p.wallet.common.feature_toggles.toggles.remote.NetworkObservationFrequencyFeatureToggle import org.p2p.wallet.common.feature_toggles.toggles.remote.NetworkObservationPercentFeatureToggle +import org.p2p.wallet.common.feature_toggles.toggles.remote.PnlEnabledFeatureToggle import org.p2p.wallet.common.feature_toggles.toggles.remote.ReferralProgramEnabledFeatureToggle import org.p2p.wallet.common.feature_toggles.toggles.remote.RegisterUsernameEnabledFeatureToggle import org.p2p.wallet.common.feature_toggles.toggles.remote.RegisterUsernameSkipEnabledFeatureToggle @@ -38,7 +39,7 @@ object FeatureTogglesModule : InjectionModule { singleOf(::FeatureTogglesValuesSource) bind RemoteConfigValuesProvider::class factory { - setOf( + listOf( get(), get(), get(), @@ -54,7 +55,8 @@ object FeatureTogglesModule : InjectionModule { get(), get(), get(), - ).toList() + get(), + ) } factoryOf(::SslPinningFeatureToggle) @@ -75,6 +77,7 @@ object FeatureTogglesModule : InjectionModule { factoryOf(::SwapRoutesValidationEnabledFeatureToggle) factoryOf(::StrigaSignupEnabledFeatureToggle) factoryOf(::ReferralProgramEnabledFeatureToggle) + factoryOf(::PnlEnabledFeatureToggle) singleOf(::FeatureToggleProvider) } diff --git a/app/src/main/java/org/p2p/wallet/common/feature_toggles/toggles/remote/PnlEnabledFeatureToggle.kt b/app/src/main/java/org/p2p/wallet/common/feature_toggles/toggles/remote/PnlEnabledFeatureToggle.kt new file mode 100644 index 0000000000..5f1b5b312d --- /dev/null +++ b/app/src/main/java/org/p2p/wallet/common/feature_toggles/toggles/remote/PnlEnabledFeatureToggle.kt @@ -0,0 +1,11 @@ +package org.p2p.wallet.common.feature_toggles.toggles.remote + +import org.p2p.wallet.common.feature_toggles.remote_config.RemoteConfigValuesProvider + +class PnlEnabledFeatureToggle( + valuesProvider: RemoteConfigValuesProvider +) : BooleanFeatureToggle(valuesProvider) { + override val featureKey: String = "pnl_enabled" + override val featureDescription: String = "Enables PNL calculation" + override val defaultValue: Boolean = false +} diff --git a/app/src/main/java/org/p2p/wallet/history/ui/token/TokenHistoryPresenter.kt b/app/src/main/java/org/p2p/wallet/history/ui/token/TokenHistoryPresenter.kt index d0086aa839..0bb2928c6a 100644 --- a/app/src/main/java/org/p2p/wallet/history/ui/token/TokenHistoryPresenter.kt +++ b/app/src/main/java/org/p2p/wallet/history/ui/token/TokenHistoryPresenter.kt @@ -55,7 +55,7 @@ class TokenHistoryPresenter( } private fun observePnlData() { - pnlInteractor.state + pnlInteractor.pnlState .onEach { updateTokenAmounts(this.token) } .launchIn(this) } @@ -87,14 +87,14 @@ class TokenHistoryPresenter( view?.renderTokenPnl( pnlUiMapper.mapTokenBalancePnl( tokenMint = token.mintAddressB58, - pnlDataState = pnlInteractor.state.value + pnlDataState = pnlInteractor.pnlState.value ) ) } override fun onTokenPnlClicked() { - if (pnlInteractor.state.value.isResult()) { - pnlInteractor.state.value.findForToken(token.mintAddressB58)?.let { + if (pnlInteractor.pnlState.value.isLoaded()) { + pnlInteractor.pnlState.value.findForToken(token.mintAddressB58)?.let { view?.showPnlDetails(it.percent) } } diff --git a/app/src/main/java/org/p2p/wallet/home/ui/crypto/MyCryptoPresenter.kt b/app/src/main/java/org/p2p/wallet/home/ui/crypto/MyCryptoPresenter.kt index 1346f66be7..0ca88ec0de 100644 --- a/app/src/main/java/org/p2p/wallet/home/ui/crypto/MyCryptoPresenter.kt +++ b/app/src/main/java/org/p2p/wallet/home/ui/crypto/MyCryptoPresenter.kt @@ -49,7 +49,7 @@ class MyCryptoPresenter( private val usernameInteractor: UsernameInteractor, private val tokenKeyProvider: TokenKeyProvider, private val mainScreenAnalytics: MainScreenAnalytics, - private val pnlInteractor: PnlDataObserver, + private val pnlDataObserver: PnlDataObserver, private val pnlUiMapper: PnlUiMapper, ) : BasePresenter(), MyCryptoContract.Presenter { @@ -87,8 +87,8 @@ class MyCryptoPresenter( // pnl must restart it's 5 minutes timer after force refresh // and we should restart it only if it was initially started // other cases might indicate that we didn't start observer due to empty state - if (pnlInteractor.isStarted()) { - pnlInteractor.restartAndRefresh() + if (pnlDataObserver.isStarted()) { + pnlDataObserver.restartAndRefresh() } } catch (cancelled: CancellationException) { Timber.i("Loading tokens job cancelled") @@ -119,7 +119,7 @@ class MyCryptoPresenter( private fun observePnlData() { pnlDataSubscription?.cancel() pnlDataSubscription = launch { - pnlInteractor.state + pnlDataObserver.pnlState .onEach { handleTokenState(tokenServiceCoordinator.observeLastState().value) } @@ -142,12 +142,12 @@ class MyCryptoPresenter( is UserTokensState.Loaded -> { view?.showEmptyState(isEmpty = false) - if (!pnlInteractor.isStarted()) { - pnlInteractor.start() + if (!pnlDataObserver.isStarted()) { + pnlDataObserver.start() } showTokensAndBalance( - pnlDataState = pnlInteractor.state.value, + pnlDataState = pnlDataObserver.pnlState.value, // separated screens logic: solTokens = filterCryptoTokens(newState.solTokens), solTokens = newState.solTokens, ethTokens = newState.ethTokens @@ -186,8 +186,8 @@ class MyCryptoPresenter( } override fun onBalancePnlClicked() { - if (pnlInteractor.state.value.isResult()) { - view?.showPnlDetails(pnlInteractor.state.value.toResultOrNull()!!.total.percent) + if (pnlDataObserver.pnlState.value.isLoaded()) { + view?.showPnlDetails(pnlDataObserver.pnlState.value.toLoadedOrNull()!!.total.percent) } } diff --git a/app/src/main/java/org/p2p/wallet/home/ui/crypto/mapper/MyCryptoMapper.kt b/app/src/main/java/org/p2p/wallet/home/ui/crypto/mapper/MyCryptoMapper.kt index 0fa45b9093..2448c81350 100644 --- a/app/src/main/java/org/p2p/wallet/home/ui/crypto/mapper/MyCryptoMapper.kt +++ b/app/src/main/java/org/p2p/wallet/home/ui/crypto/mapper/MyCryptoMapper.kt @@ -71,7 +71,7 @@ class MyCryptoMapper( result += visibleTokens.map { it.mapToCellModel( isZerosHidden = isZerosHidden, - pnlTokenData = pnlDataState.toResultOrNull()?.findForToken(it.mintAddressB58) + pnlTokenData = pnlDataState.toLoadedOrNull()?.findForToken(it.mintAddressB58) ) } @@ -85,7 +85,7 @@ class MyCryptoMapper( result += hiddenTokens.map { it.mapToCellModel( isZerosHidden = isZerosHidden, - pnlTokenData = pnlDataState.toResultOrNull()?.findForToken(it.mintAddressB58) + pnlTokenData = pnlDataState.toLoadedOrNull()?.findForToken(it.mintAddressB58) ) } } diff --git a/app/src/main/java/org/p2p/wallet/pnl/PnlModule.kt b/app/src/main/java/org/p2p/wallet/pnl/PnlModule.kt index c0fec65bfb..90f7496ebc 100644 --- a/app/src/main/java/org/p2p/wallet/pnl/PnlModule.kt +++ b/app/src/main/java/org/p2p/wallet/pnl/PnlModule.kt @@ -33,7 +33,8 @@ object PnlModule : InjectionModule { factory { val api = get(named(RETROFIT_QUALIFIER)).create(PnlServiceApi::class.java) PnlRemoteRepository( - api = api + api = api, + dispatchers = get() ) } bind PnlRepository::class diff --git a/app/src/main/java/org/p2p/wallet/pnl/interactor/PnlDataObserver.kt b/app/src/main/java/org/p2p/wallet/pnl/interactor/PnlDataObserver.kt index a7da3f03ea..72b935534e 100644 --- a/app/src/main/java/org/p2p/wallet/pnl/interactor/PnlDataObserver.kt +++ b/app/src/main/java/org/p2p/wallet/pnl/interactor/PnlDataObserver.kt @@ -15,6 +15,7 @@ import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import org.p2p.core.common.di.AppScope import org.p2p.core.crypto.Base58String +import org.p2p.wallet.common.feature_toggles.toggles.remote.PnlEnabledFeatureToggle import org.p2p.wallet.infrastructure.network.provider.TokenKeyProvider import org.p2p.wallet.pnl.repository.PnlRepository import org.p2p.wallet.tokenservice.TokenServiceCoordinator @@ -43,6 +44,7 @@ class PnlDataObserver( private val pnlRepository: PnlRepository, private val tokenKeyProvider: TokenKeyProvider, private val tokenServiceCoordinator: TokenServiceCoordinator, + private val pnlEnabledFt: PnlEnabledFeatureToggle ) { private companion object { @@ -52,14 +54,14 @@ class PnlDataObserver( private var updaterJob: Job? = null private val tokenMints = mutableListOf() private val tokenMintsMutex = Mutex() - private val _state = MutableStateFlow(PnlDataState.Idle) - val state = _state.asStateFlow() + private val _pnlState = MutableStateFlow(PnlDataState.Idle) + val pnlState = _pnlState.asStateFlow() fun isStarted(): Boolean = updaterJob != null fun start() { - if (updaterJob != null) return - _state.value = PnlDataState.Loading + if (!pnlEnabledFt.isFeatureEnabled || isStarted()) return + _pnlState.value = PnlDataState.Loading launchJob() } @@ -74,6 +76,8 @@ class PnlDataObserver( } private fun launchJob() { + if (!pnlEnabledFt.isFeatureEnabled) return + updaterJob = appScope.launch { // firstly, get current state, because observer may return actual tokens later tokenServiceCoordinator.observeLastState().value.extractTokenMints() @@ -85,14 +89,11 @@ class PnlDataObserver( while (isActive) { try { - _state.emit( - PnlDataState.Result( - pnlRepository.getPnlData(tokenKeyProvider.publicKeyBase58, tokenMints) - ) - ) + val pnlData = pnlRepository.getPnlData(tokenKeyProvider.publicKeyBase58, tokenMints) + _pnlState.emit(PnlDataState.Loaded(pnlData)) } catch (e: Throwable) { Timber.e(e, "Unable to get pnl data") - _state.emit( + _pnlState.emit( PnlDataState.Error(e) ) } diff --git a/app/src/main/java/org/p2p/wallet/pnl/interactor/PnlDataState.kt b/app/src/main/java/org/p2p/wallet/pnl/interactor/PnlDataState.kt index 71d9110e23..8c064ff83e 100644 --- a/app/src/main/java/org/p2p/wallet/pnl/interactor/PnlDataState.kt +++ b/app/src/main/java/org/p2p/wallet/pnl/interactor/PnlDataState.kt @@ -7,11 +7,11 @@ import org.p2p.wallet.pnl.models.PnlTokenData sealed class PnlDataState { object Idle : PnlDataState() object Loading : PnlDataState() - data class Result(val data: PnlData) : PnlDataState() + data class Loaded(val data: PnlData) : PnlDataState() data class Error(val error: Throwable) : PnlDataState() - fun toResultOrNull(): PnlData? = (this as? Result)?.data - fun isResult(): Boolean = this is Result + fun toLoadedOrNull(): PnlData? = (this as? Loaded)?.data + fun isLoaded(): Boolean = this is Loaded - fun findForToken(tokenMint: Base58String): PnlTokenData? = toResultOrNull()?.findForToken(tokenMint) + fun findForToken(tokenMint: Base58String): PnlTokenData? = toLoadedOrNull()?.findForToken(tokenMint) } diff --git a/app/src/main/java/org/p2p/wallet/pnl/repository/PnlRemoteRepository.kt b/app/src/main/java/org/p2p/wallet/pnl/repository/PnlRemoteRepository.kt index d5b56386e7..340b57bac1 100644 --- a/app/src/main/java/org/p2p/wallet/pnl/repository/PnlRemoteRepository.kt +++ b/app/src/main/java/org/p2p/wallet/pnl/repository/PnlRemoteRepository.kt @@ -1,8 +1,8 @@ package org.p2p.wallet.pnl.repository -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.p2p.core.crypto.Base58String +import org.p2p.core.dispatchers.CoroutineDispatchers import org.p2p.core.network.gson.GsonProvider import org.p2p.solanaj.model.types.RpcMapRequest import org.p2p.wallet.pnl.api.PnlDataTimeSpan @@ -12,25 +12,27 @@ import org.p2p.wallet.pnl.models.PnlData class PnlRemoteRepository( private val api: PnlServiceApi, + private val dispatchers: CoroutineDispatchers ) : PnlRepository { override suspend fun getPnlData( userWallet: Base58String, tokenMints: List, timeSpan: PnlDataTimeSpan, - ): PnlData = withContext(Dispatchers.IO) { + ): PnlData = withContext(dispatchers.io) { + val requestedTokenMints: List? = tokenMints + .map { it.base58Value } + .ifEmpty { null } + val params = buildMap { - put("user_wallet", userWallet.base58Value) + this["user_wallet"] = userWallet.base58Value // todo: backend decided to use null for a default duration // currently it's 24 hours, something may change in the future - put("since", timeSpan.sinceEpochSeconds) - val requestedTokenMints: List? = tokenMints - .map { it.base58Value } - .ifEmpty { null } - put("token_mints", requestedTokenMints) + this["since"] = timeSpan.sinceEpochSeconds + this["token_mints"] = requestedTokenMints } - val rpcRequest = RpcMapRequest("get_pnl", params) + val rpcRequest = RpcMapRequest(method = "get_pnl", params = params) val response = api.getAccountPnl(rpcRequest) val gson = GsonProvider() .withBuilder { diff --git a/app/src/main/java/org/p2p/wallet/pnl/ui/PnlUiMapper.kt b/app/src/main/java/org/p2p/wallet/pnl/ui/PnlUiMapper.kt index 5df6f56697..2b1004d175 100644 --- a/app/src/main/java/org/p2p/wallet/pnl/ui/PnlUiMapper.kt +++ b/app/src/main/java/org/p2p/wallet/pnl/ui/PnlUiMapper.kt @@ -23,7 +23,7 @@ class PnlUiMapper { fun mapBalancePnl(pnlDataState: PnlDataState): TextViewCellModel? { return when (pnlDataState) { - is PnlDataState.Result -> { + is PnlDataState.Loaded -> { TextViewCellModel.Raw( TextContainer(R.string.home_pnl_format, pnlDataState.data.total.percent) ) @@ -40,7 +40,7 @@ class PnlUiMapper { fun mapTokenBalancePnl(tokenMint: Base58String, pnlDataState: PnlDataState): TextViewCellModel? { return when (pnlDataState) { - is PnlDataState.Result -> { + is PnlDataState.Loaded -> { pnlDataState.findForToken(tokenMint)?.let { pnlTokenData -> TextViewCellModel.Raw( TextContainer(R.string.home_pnl_format, pnlTokenData.percent)