Skip to content

Commit

Permalink
Add validation to pool search (#1110)
Browse files Browse the repository at this point in the history
  • Loading branch information
antonijzelinskij authored Sep 12, 2023
1 parent 5e46ef0 commit 01afd37
Show file tree
Hide file tree
Showing 11 changed files with 125 additions and 7 deletions.
5 changes: 5 additions & 0 deletions common/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="TypographyEllipsis">
<string name="pool_full_failure_title">Pool is full</string>
<string name="pool_full_failure_message">You cannot join pool since it reached maximum number of members</string>

<string name="pool_inactive_failure_title">Pool is not open</string>
<string name="pool_inactive_failure_message">You cannot join pool that is not open. Please, contact the pool owner.</string>

<!--<string name="dapp_browser_close_warning_message">Are you sure you want to close this screen?\nYour changes will not be applied.</string>-->
<string name="common_close_confirmation_message">Are you sure you want to close this screen?\nYour changes will not be applied.</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,12 +223,14 @@ class StartMultiStakingModule {
fun provideSelectNominationPoolInteractor(
nominationPoolProvider: NominationPoolProvider,
knownNovaPools: KnownNovaPools,
nominationPoolRecommenderFactory: NominationPoolRecommenderFactory
nominationPoolRecommenderFactory: NominationPoolRecommenderFactory,
nominationPoolGlobalsRepository: NominationPoolGlobalsRepository
): SearchNominationPoolInteractor {
return SearchNominationPoolInteractor(
nominationPoolProvider,
knownNovaPools,
nominationPoolRecommenderFactory
nominationPoolRecommenderFactory,
nominationPoolGlobalsRepository
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package io.novafoundation.nova.feature_staking_impl.domain.nominationPools.selecting

import io.novafoundation.nova.common.validation.ValidationSystem
import io.novafoundation.nova.feature_staking_impl.domain.nominationPools.model.NominationPool
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain

typealias PoolAvailabilityValidationSystem = ValidationSystem<PoolAvailabilityPayload, PoolAvailabilityFailure>

class PoolAvailabilityPayload(val nominationPool: NominationPool, val chain: Chain)

sealed interface PoolAvailabilityFailure {

object PoolIsFull : PoolAvailabilityFailure

object PoolIsClosed : PoolAvailabilityFailure
}
Original file line number Diff line number Diff line change
@@ -1,24 +1,28 @@
package io.novafoundation.nova.feature_staking_impl.domain.nominationPools.selecting

import io.novafoundation.nova.common.utils.orFalse
import io.novafoundation.nova.common.validation.ValidationSystem
import io.novafoundation.nova.feature_staking_impl.data.StakingOption
import io.novafoundation.nova.feature_staking_impl.data.chain
import io.novafoundation.nova.feature_staking_impl.data.nominationPools.pool.KnownNovaPools
import io.novafoundation.nova.feature_staking_impl.data.nominationPools.repository.NominationPoolGlobalsRepository
import io.novafoundation.nova.feature_staking_impl.domain.nominationPools.common.getPoolComparator
import io.novafoundation.nova.feature_staking_impl.domain.nominationPools.model.NominationPool
import io.novafoundation.nova.feature_staking_impl.domain.nominationPools.model.address
import io.novafoundation.nova.feature_staking_impl.domain.nominationPools.model.name
import io.novafoundation.nova.feature_staking_impl.domain.nominationPools.model.nameOrAddress
import io.novafoundation.nova.feature_staking_impl.domain.nominationPools.pools.NominationPoolProvider
import io.novafoundation.nova.feature_staking_impl.domain.nominationPools.pools.recommendation.NominationPoolRecommenderFactory
import io.novafoundation.nova.feature_staking_impl.domain.validations.setup.poolAvailable
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map

class SearchNominationPoolInteractor(
private val nominationPoolProvider: NominationPoolProvider,
private val knownNovaPools: KnownNovaPools,
private val nominationPoolRecommenderFactory: NominationPoolRecommenderFactory
private val nominationPoolRecommenderFactory: NominationPoolRecommenderFactory,
private val nominationPoolGlobalsRepository: NominationPoolGlobalsRepository
) {

suspend fun getSortedNominationPools(stakingOption: StakingOption, coroutineScope: CoroutineScope): List<NominationPool> {
Expand Down Expand Up @@ -48,4 +52,10 @@ class SearchNominationPoolInteractor(
.sortedWith(comparator)
}
}

fun getValidationSystem(): PoolAvailabilityValidationSystem {
return ValidationSystem {
poolAvailable(nominationPoolGlobalsRepository)
}
}
}
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package io.novafoundation.nova.feature_staking_impl.domain.validations.setup

import io.novafoundation.nova.common.validation.Validation
import io.novafoundation.nova.common.validation.ValidationStatus
import io.novafoundation.nova.common.validation.ValidationSystemBuilder
import io.novafoundation.nova.common.validation.valid
import io.novafoundation.nova.common.validation.validationError
import io.novafoundation.nova.feature_staking_impl.data.nominationPools.network.blockhain.models.isOpen
import io.novafoundation.nova.feature_staking_impl.data.nominationPools.repository.NominationPoolGlobalsRepository
import io.novafoundation.nova.feature_staking_impl.domain.nominationPools.model.isActive
import io.novafoundation.nova.feature_staking_impl.domain.nominationPools.selecting.PoolAvailabilityFailure
import io.novafoundation.nova.feature_staking_impl.domain.nominationPools.selecting.PoolAvailabilityPayload

class PoolAvailabilityValidation(
private val nominationPoolGlobalsRepository: NominationPoolGlobalsRepository
) : Validation<PoolAvailabilityPayload, PoolAvailabilityFailure> {

override suspend fun validate(value: PoolAvailabilityPayload): ValidationStatus<PoolAvailabilityFailure> {
val pool = value.nominationPool
return when {
!pool.status.isActive || !pool.state.isOpen -> validationError(PoolAvailabilityFailure.PoolIsClosed)
isPoolFull(value) -> validationError(PoolAvailabilityFailure.PoolIsFull)
else -> valid()
}
}

private suspend fun isPoolFull(value: PoolAvailabilityPayload): Boolean {
val pool = value.nominationPool
val maxPoolMembers = nominationPoolGlobalsRepository.maxPoolMembersPerPool(value.chain.id)
return maxPoolMembers != null && maxPoolMembers <= pool.membersCount
}
}

fun ValidationSystemBuilder<PoolAvailabilityPayload, PoolAvailabilityFailure>.poolAvailable(
nominationPoolGlobalsRepository: NominationPoolGlobalsRepository
) {
val validation = PoolAvailabilityValidation(nominationPoolGlobalsRepository = nominationPoolGlobalsRepository)
validate(validation)
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import androidx.lifecycle.lifecycleScope
import coil.ImageLoader
import io.novafoundation.nova.common.base.BaseFragment
import io.novafoundation.nova.common.di.FeatureUtils
import io.novafoundation.nova.common.mixin.impl.observeValidations
import io.novafoundation.nova.common.utils.applyStatusBarInsets
import io.novafoundation.nova.common.utils.bindTo
import io.novafoundation.nova.common.utils.keyboard.hideSoftKeyboard
Expand Down Expand Up @@ -82,6 +83,8 @@ class SearchPoolFragment : BaseFragment<SearchPoolViewModel>(), PoolAdapter.Item

override fun subscribe(viewModel: SearchPoolViewModel) {
setupExternalActions(viewModel)
observeValidations(viewModel)

searchPoolToolbar.searchField.content.bindTo(viewModel.query, lifecycleScope)

viewModel.poolModelsFlow.observe {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,19 @@ package io.novafoundation.nova.feature_staking_impl.presentation.pools.searchPoo
import android.text.TextUtils
import androidx.lifecycle.viewModelScope
import io.novafoundation.nova.common.base.BaseViewModel
import io.novafoundation.nova.common.mixin.api.Validatable
import io.novafoundation.nova.common.resources.ResourceManager
import io.novafoundation.nova.common.utils.flowOfAll
import io.novafoundation.nova.common.utils.inBackground
import io.novafoundation.nova.common.utils.invoke
import io.novafoundation.nova.common.validation.ValidationExecutor
import io.novafoundation.nova.common.view.PlaceholderModel
import io.novafoundation.nova.feature_account_api.presenatation.actions.ExternalActions
import io.novafoundation.nova.feature_staking_impl.R
import io.novafoundation.nova.feature_staking_impl.data.chain
import io.novafoundation.nova.feature_staking_impl.data.createStakingOption
import io.novafoundation.nova.feature_staking_impl.domain.nominationPools.model.NominationPool
import io.novafoundation.nova.feature_staking_impl.domain.nominationPools.selecting.PoolAvailabilityPayload
import io.novafoundation.nova.feature_staking_impl.domain.nominationPools.selecting.SearchNominationPoolInteractor
import io.novafoundation.nova.feature_staking_impl.domain.staking.start.setupAmount.pools.asPoolSelection
import io.novafoundation.nova.feature_staking_impl.domain.staking.start.setupStakingType.SetupStakingTypeSelectionMixinFactory
Expand Down Expand Up @@ -41,7 +44,8 @@ class SearchPoolViewModel(
private val chainRegistry: ChainRegistry,
private val externalActions: ExternalActions.Presentation,
private val poolDisplayFormatter: PoolDisplayFormatter,
) : BaseViewModel(), ExternalActions by externalActions {
private val validationExecutor: ValidationExecutor,
) : BaseViewModel(), ExternalActions by externalActions, Validatable by validationExecutor {

val query = MutableStateFlow("")

Expand Down Expand Up @@ -88,6 +92,22 @@ class SearchPoolViewModel(
fun poolClicked(poolItem: PoolRvItem) {
launch {
val pool = getPoolById(poolItem.id) ?: return@launch

val validationSystem = selectNominationPoolInteractor.getValidationSystem()
val payload = PoolAvailabilityPayload(pool, stakingOption().chain)

validationExecutor.requireValid(
validationSystem = validationSystem,
payload = payload,
validationFailureTransformer = { handleSelectPoolValidationFailure(it, resourceManager) },
) {
finishSetupPoolFlow(pool)
}
}
}

private fun finishSetupPoolFlow(pool: NominationPool) {
launch {
setupStakingTypeSelectionMixin.selectNominationPoolAndApply(pool, stakingOption())
router.finishSetupPoolFlow()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.novafoundation.nova.feature_staking_impl.presentation.pools.searchPool

import io.novafoundation.nova.common.base.TitleAndMessage
import io.novafoundation.nova.common.resources.ResourceManager
import io.novafoundation.nova.feature_staking_impl.R
import io.novafoundation.nova.feature_staking_impl.domain.nominationPools.selecting.PoolAvailabilityFailure

fun handleSelectPoolValidationFailure(error: PoolAvailabilityFailure, resourceManager: ResourceManager): TitleAndMessage {
return when (error) {
PoolAvailabilityFailure.PoolIsFull -> TitleAndMessage(
resourceManager.getString(R.string.pool_full_failure_title),
resourceManager.getString(R.string.pool_full_failure_message)
)

PoolAvailabilityFailure.PoolIsClosed -> TitleAndMessage(
resourceManager.getString(R.string.pool_inactive_failure_title),
resourceManager.getString(R.string.pool_inactive_failure_message)
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import dagger.multibindings.IntoMap
import io.novafoundation.nova.common.di.viewmodel.ViewModelKey
import io.novafoundation.nova.common.di.viewmodel.ViewModelModule
import io.novafoundation.nova.common.resources.ResourceManager
import io.novafoundation.nova.common.validation.ValidationExecutor
import io.novafoundation.nova.feature_account_api.presenatation.actions.ExternalActions
import io.novafoundation.nova.feature_staking_impl.domain.nominationPools.selecting.SearchNominationPoolInteractor
import io.novafoundation.nova.feature_staking_impl.domain.staking.start.setupStakingType.SetupStakingTypeSelectionMixinFactory
Expand All @@ -33,6 +34,7 @@ class SearchPoolModule {
chainRegistry: ChainRegistry,
externalActions: ExternalActions.Presentation,
poolDisplayFormatter: PoolDisplayFormatter,
validationExecutor: ValidationExecutor
): ViewModel {
return SearchPoolViewModel(
router,
Expand All @@ -42,7 +44,8 @@ class SearchPoolModule {
selectNominationPoolInteractor,
chainRegistry,
externalActions,
poolDisplayFormatter
poolDisplayFormatter,
validationExecutor
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ fun handleSetupStakingTypeValidationFailure(chainAsset: Chain.Asset, error: Edit
is EditingStakingTypeFailure.AmountIsLessThanMinStake -> {
val amount = error.minStake.formatPlanks(chainAsset)
val stakingTypeFormat = resourceManager.formatStakingTypeLabel(error.stakingType)
return TitleAndMessage(
TitleAndMessage(
resourceManager.getString(R.string.setup_staking_type_staking_amount_is_less_than_min_amount_title),
resourceManager.getString(R.string.setup_staking_type_direct_staking_amount_is_less_than_min_amount_message, amount, stakingTypeFormat)
)
}

is EditingStakingTypeFailure.StakingTypeIsAlreadyUsing -> {
return when (error.stakingType.group()) {
when (error.stakingType.group()) {
StakingTypeGroup.PARACHAIN,
StakingTypeGroup.RELAYCHAIN -> TitleAndMessage(
resourceManager.getString(R.string.setup_staking_type_already_used_title),
Expand Down

0 comments on commit 01afd37

Please sign in to comment.