From ec7491c5e3f8ab45bbae50cbeecfda2bc16fcbfb Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Fri, 4 Oct 2024 13:35:30 +0200 Subject: [PATCH 01/23] Referendum Summary Backend --- .../di/modules/shared/MarkdownFullModule.kt | 4 +- .../di/modules/shared/MarkdownShortModule.kt | 4 +- .../nova/common/utils/SpannableExt.kt | 2 +- .../common/utils/markdown/BoldStylePlugin.kt | 36 ++++ .../{StylePlugin.kt => LinkStylePlugin.kt} | 2 +- common/src/main/res/values/colors.xml | 1 + .../summary/ReferendaSummaryInteractor.kt | 9 + .../tindergov/TinderGovBasketInteractor.kt | 28 +++ .../domain/tindergov/TinderGovInteractor.kt | 22 +- feature-governance-impl/build.gradle | 4 +- .../summary/v2/ReferendumSummaryApi.kt | 22 +- .../summary/v2/ReferendumSummaryDataSource.kt | 38 +++- .../summary/v2/ReferendumSummaryResponse.kt | 3 - .../v2/request/ReferendumSummariesRequest.kt | 7 + .../v2/request/ReferendumSummaryRequest.kt | 7 + .../v2/response/ReferendumSummaryResponse.kt | 3 + .../di/modules/screens/TinderGovModule.kt | 60 +++++- .../details/ReferendumDetailsRepository.kt | 13 +- .../RealTinderGovBasketInteractor.kt | 125 ++++++++++++ .../tindergov/RealTinderGovInteractor.kt | 85 +------- .../ReferendaSummarySharedComputation.kt | 30 +++ .../summary/ReferendumSummaryInteractor.kt | 17 ++ .../common/info/di/DescriptionComponent.kt | 1 + .../common/info/di/DescriptionModule.kt | 2 +- .../referenda/list/ReferendaListViewModel.kt | 25 ++- .../referenda/list/di/ReferendaListModule.kt | 7 +- .../basket/TinderGovBasketViewModel.kt | 12 +- .../basket/di/TinderGovBasketModule.kt | 7 +- .../cards/TinderGovCardDetailsLoader.kt | 131 ------------ .../cards/TinderGovCardsViewModel.kt | 191 +++++------------- .../cards/adapter/TinderGovCardRvItem.kt | 5 +- .../cards/adapter/TinderGovCardsAdapter.kt | 14 +- .../cards/di/TinderGovCardsModule.kt | 48 +++-- .../tindergov/cards/model/CardWithDetails.kt | 25 +++ .../cards/model/ReferendaCounterModel.kt | 14 ++ .../cards/model/ReferendaWithBasket.kt | 9 + .../confirm/ConfirmTinderGovVoteViewModel.kt | 6 +- .../confirm/di/ConfirmTinderGovVoteModule.kt | 3 + .../main/res/layout/item_tinder_gov_card.xml | 77 +------ 39 files changed, 558 insertions(+), 541 deletions(-) create mode 100644 common/src/main/java/io/novafoundation/nova/common/utils/markdown/BoldStylePlugin.kt rename common/src/main/java/io/novafoundation/nova/common/utils/markdown/{StylePlugin.kt => LinkStylePlugin.kt} (83%) create mode 100644 feature-governance-api/src/main/java/io/novafoundation/nova/feature_governance_api/domain/referendum/summary/ReferendaSummaryInteractor.kt create mode 100644 feature-governance-api/src/main/java/io/novafoundation/nova/feature_governance_api/domain/tindergov/TinderGovBasketInteractor.kt delete mode 100644 feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/referendum/summary/v2/ReferendumSummaryResponse.kt create mode 100644 feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/referendum/summary/v2/request/ReferendumSummariesRequest.kt create mode 100644 feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/referendum/summary/v2/request/ReferendumSummaryRequest.kt create mode 100644 feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/referendum/summary/v2/response/ReferendumSummaryResponse.kt create mode 100644 feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/tindergov/RealTinderGovBasketInteractor.kt create mode 100644 feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/summary/ReferendaSummarySharedComputation.kt create mode 100644 feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/summary/ReferendumSummaryInteractor.kt delete mode 100644 feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/cards/TinderGovCardDetailsLoader.kt create mode 100644 feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/cards/model/CardWithDetails.kt create mode 100644 feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/cards/model/ReferendaCounterModel.kt create mode 100644 feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/cards/model/ReferendaWithBasket.kt diff --git a/common/src/main/java/io/novafoundation/nova/common/di/modules/shared/MarkdownFullModule.kt b/common/src/main/java/io/novafoundation/nova/common/di/modules/shared/MarkdownFullModule.kt index a7ff47a32c..021e90d6a1 100644 --- a/common/src/main/java/io/novafoundation/nova/common/di/modules/shared/MarkdownFullModule.kt +++ b/common/src/main/java/io/novafoundation/nova/common/di/modules/shared/MarkdownFullModule.kt @@ -12,7 +12,7 @@ import io.noties.markwon.html.HtmlPlugin import io.noties.markwon.image.coil.CoilImagesPlugin import io.noties.markwon.linkify.LinkifyPlugin import io.novafoundation.nova.common.di.scope.ScreenScope -import io.novafoundation.nova.common.utils.markdown.StylePlugin +import io.novafoundation.nova.common.utils.markdown.LinkStylePlugin @Module class MarkdownFullModule { @@ -22,7 +22,7 @@ class MarkdownFullModule { fun provideMarkwon(context: Context, imageLoader: ImageLoader): Markwon { return Markwon.builder(context) .usePlugin(LinkifyPlugin.create(Linkify.EMAIL_ADDRESSES or Linkify.WEB_URLS)) - .usePlugin(StylePlugin(context)) + .usePlugin(LinkStylePlugin(context)) .usePlugin(CoilImagesPlugin.create(context, imageLoader)) .usePlugin(StrikethroughPlugin.create()) .usePlugin(TablePlugin.create(context)) diff --git a/common/src/main/java/io/novafoundation/nova/common/di/modules/shared/MarkdownShortModule.kt b/common/src/main/java/io/novafoundation/nova/common/di/modules/shared/MarkdownShortModule.kt index 1e99ea3d23..8b7c46127d 100644 --- a/common/src/main/java/io/novafoundation/nova/common/di/modules/shared/MarkdownShortModule.kt +++ b/common/src/main/java/io/novafoundation/nova/common/di/modules/shared/MarkdownShortModule.kt @@ -10,7 +10,7 @@ import io.noties.markwon.html.HtmlPlugin import io.noties.markwon.linkify.LinkifyPlugin import io.novafoundation.nova.common.di.scope.ScreenScope import io.novafoundation.nova.common.utils.markdown.RemoveHtmlTagsPlugin -import io.novafoundation.nova.common.utils.markdown.StylePlugin +import io.novafoundation.nova.common.utils.markdown.LinkStylePlugin private const val IMG_HTML_TAG = "img" private const val TABLE_HTML_TAG = "table" @@ -24,7 +24,7 @@ class MarkdownShortModule { return Markwon.builder(context) .usePlugin(RemoveHtmlTagsPlugin(IMG_HTML_TAG, TABLE_HTML_TAG)) .usePlugin(LinkifyPlugin.create(Linkify.EMAIL_ADDRESSES or Linkify.WEB_URLS)) - .usePlugin(StylePlugin(context)) + .usePlugin(LinkStylePlugin(context)) .usePlugin(StrikethroughPlugin.create()) .usePlugin(HtmlPlugin.create()) .build() diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/SpannableExt.kt b/common/src/main/java/io/novafoundation/nova/common/utils/SpannableExt.kt index cd6628b004..2e07f471bd 100644 --- a/common/src/main/java/io/novafoundation/nova/common/utils/SpannableExt.kt +++ b/common/src/main/java/io/novafoundation/nova/common/utils/SpannableExt.kt @@ -91,7 +91,7 @@ fun CharSequence.formatAsSpannable(vararg args: Any): SpannedString { private fun typefaceSpanCompatV28(typeface: Typeface) = TypefaceSpan(typeface) -private class CustomTypefaceSpan(private val typeface: Typeface?) : MetricAffectingSpan() { +class CustomTypefaceSpan(private val typeface: Typeface?) : MetricAffectingSpan() { override fun updateDrawState(paint: TextPaint) { paint.typeface = typeface } diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/markdown/BoldStylePlugin.kt b/common/src/main/java/io/novafoundation/nova/common/utils/markdown/BoldStylePlugin.kt new file mode 100644 index 0000000000..175aa58839 --- /dev/null +++ b/common/src/main/java/io/novafoundation/nova/common/utils/markdown/BoldStylePlugin.kt @@ -0,0 +1,36 @@ +package io.novafoundation.nova.common.utils.markdown + +import android.content.Context +import androidx.annotation.ColorRes +import androidx.annotation.FontRes +import androidx.core.content.res.ResourcesCompat +import io.noties.markwon.AbstractMarkwonPlugin +import io.noties.markwon.MarkwonConfiguration +import io.noties.markwon.MarkwonSpansFactory +import io.noties.markwon.RenderProps +import io.noties.markwon.SpanFactory +import io.novafoundation.nova.common.utils.colorSpan +import io.novafoundation.nova.common.utils.fontSpan +import org.commonmark.node.StrongEmphasis + +class BoldStylePlugin( + private val context: Context, + @FontRes private val typefaceRes: Int, + @ColorRes private val colorRes: Int +) : AbstractMarkwonPlugin() { + + override fun configureSpansFactory(builder: MarkwonSpansFactory.Builder) { + builder.setFactory(StrongEmphasis::class.java, BoldSpanFactory(context, typefaceRes, colorRes)) + } +} + +private class BoldSpanFactory(private val context: Context, private val typefaceRes: Int, private val colorRes: Int) : SpanFactory { + + override fun getSpans(configuration: MarkwonConfiguration, props: RenderProps): Any { + val font = ResourcesCompat.getFont(context, typefaceRes) + return arrayOf( + fontSpan(font), + colorSpan(context.getColor(colorRes)) + ) + } +} diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/markdown/StylePlugin.kt b/common/src/main/java/io/novafoundation/nova/common/utils/markdown/LinkStylePlugin.kt similarity index 83% rename from common/src/main/java/io/novafoundation/nova/common/utils/markdown/StylePlugin.kt rename to common/src/main/java/io/novafoundation/nova/common/utils/markdown/LinkStylePlugin.kt index 35342a35aa..20dd2afce4 100644 --- a/common/src/main/java/io/novafoundation/nova/common/utils/markdown/StylePlugin.kt +++ b/common/src/main/java/io/novafoundation/nova/common/utils/markdown/LinkStylePlugin.kt @@ -5,7 +5,7 @@ import io.noties.markwon.AbstractMarkwonPlugin import io.noties.markwon.core.MarkwonTheme import io.novafoundation.nova.common.R -class StylePlugin(private val context: Context) : AbstractMarkwonPlugin() { +class LinkStylePlugin(private val context: Context) : AbstractMarkwonPlugin() { override fun configureTheme(builder: MarkwonTheme.Builder) { builder.isLinkUnderlined(false) diff --git a/common/src/main/res/values/colors.xml b/common/src/main/res/values/colors.xml index d4a61eff3a..65457f8835 100644 --- a/common/src/main/res/values/colors.xml +++ b/common/src/main/res/values/colors.xml @@ -36,6 +36,7 @@ #A3FFFFFF #A3FFFFFF #52FFFFFF + #A3FFFFFF #BD387F #FF7A00 diff --git a/feature-governance-api/src/main/java/io/novafoundation/nova/feature_governance_api/domain/referendum/summary/ReferendaSummaryInteractor.kt b/feature-governance-api/src/main/java/io/novafoundation/nova/feature_governance_api/domain/referendum/summary/ReferendaSummaryInteractor.kt new file mode 100644 index 0000000000..f48bd44c41 --- /dev/null +++ b/feature-governance-api/src/main/java/io/novafoundation/nova/feature_governance_api/domain/referendum/summary/ReferendaSummaryInteractor.kt @@ -0,0 +1,9 @@ +package io.novafoundation.nova.feature_governance_api.domain.referendum.summary + +import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.ReferendumId +import kotlinx.coroutines.CoroutineScope + +interface ReferendaSummaryInteractor { + + suspend fun getReferendaSummaries(ids: List, coroutineScope: CoroutineScope): Map +} diff --git a/feature-governance-api/src/main/java/io/novafoundation/nova/feature_governance_api/domain/tindergov/TinderGovBasketInteractor.kt b/feature-governance-api/src/main/java/io/novafoundation/nova/feature_governance_api/domain/tindergov/TinderGovBasketInteractor.kt new file mode 100644 index 0000000000..5b50b879b4 --- /dev/null +++ b/feature-governance-api/src/main/java/io/novafoundation/nova/feature_governance_api/domain/tindergov/TinderGovBasketInteractor.kt @@ -0,0 +1,28 @@ +package io.novafoundation.nova.feature_governance_api.domain.tindergov + +import io.novafoundation.nova.feature_governance_api.data.model.TinderGovBasketItem +import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.ReferendumId +import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.VoteType +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow + +interface TinderGovBasketInteractor { + + fun observeTinderGovBasket(): Flow> + + suspend fun getTinderGovBasket(): List + + suspend fun addItemToBasket(referendumId: ReferendumId, voteType: VoteType) + + suspend fun removeReferendumFromBasket(item: TinderGovBasketItem) + + suspend fun removeBasketItems(items: Collection) + + suspend fun isBasketEmpty(): Boolean + + suspend fun clearBasket() + + suspend fun getBasketItemsToRemove(coroutineScope: CoroutineScope): List + + suspend fun awaitAllItemsVoted(coroutineScope: CoroutineScope, basket: List) +} diff --git a/feature-governance-api/src/main/java/io/novafoundation/nova/feature_governance_api/domain/tindergov/TinderGovInteractor.kt b/feature-governance-api/src/main/java/io/novafoundation/nova/feature_governance_api/domain/tindergov/TinderGovInteractor.kt index e5b0590e07..b39ff415cd 100644 --- a/feature-governance-api/src/main/java/io/novafoundation/nova/feature_governance_api/domain/tindergov/TinderGovInteractor.kt +++ b/feature-governance-api/src/main/java/io/novafoundation/nova/feature_governance_api/domain/tindergov/TinderGovInteractor.kt @@ -2,8 +2,6 @@ package io.novafoundation.nova.feature_governance_api.domain.tindergov import io.novafoundation.nova.feature_governance_api.data.model.TinderGovBasketItem import io.novafoundation.nova.feature_governance_api.data.model.VotingPower -import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.ReferendumId -import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.VoteType import io.novafoundation.nova.feature_governance_api.domain.referendum.details.ReferendumCall import io.novafoundation.nova.feature_governance_api.domain.referendum.list.ReferendaState import io.novafoundation.nova.feature_governance_api.domain.referendum.list.ReferendumPreview @@ -26,15 +24,7 @@ interface TinderGovInteractor { fun observeReferendaAvailableToVote(coroutineScope: CoroutineScope): Flow> - fun observeTinderGovBasket(): Flow> - - suspend fun getTinderGovBasket(): List - - suspend fun addItemToBasket(referendumId: ReferendumId, voteType: VoteType) - - suspend fun loadReferendumSummary(id: ReferendumId): String? - - suspend fun loadReferendumAmount(referendumPreview: ReferendumPreview): ReferendumCall.TreasuryRequest? + suspend fun getReferendumAmount(referendumPreview: ReferendumPreview): ReferendumCall.TreasuryRequest? suspend fun setVotingPower(votingPower: VotingPower) @@ -42,15 +32,5 @@ interface TinderGovInteractor { suspend fun getVotingPowerState(): VotingPowerState - suspend fun removeReferendumFromBasket(item: TinderGovBasketItem) - - suspend fun removeBasketItems(items: Collection) - - suspend fun isBasketEmpty(): Boolean - - suspend fun clearBasket() - - suspend fun getBasketItemsToRemove(coroutineScope: CoroutineScope): List - suspend fun awaitAllItemsVoted(coroutineScope: CoroutineScope, basket: List) } diff --git a/feature-governance-impl/build.gradle b/feature-governance-impl/build.gradle index 732507ae42..1762eb3543 100644 --- a/feature-governance-impl/build.gradle +++ b/feature-governance-impl/build.gradle @@ -19,7 +19,7 @@ android { buildConfigField "String", "DELEGATION_TUTORIAL_URL", "\"https://docs.novawallet.io/nova-wallet-wiki/governance/add-delegate-information\"" - buildConfigField "String", "SUMMARY_API_KEY", readStringSecret("POLKASSEMBLY_SUMMARY_API_KEY") + buildConfigField "String", "OPENGOV_API_URL", "\"https://opengov-backend-dev.novasama-tech.org\"" } buildTypes { @@ -28,6 +28,8 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' buildConfigField "String", "GOVERNANCE_DAPPS_URL", "\"https://raw.githubusercontent.com/novasamatech/nova-utils/master/governance/v2/dapps.json\"" + + buildConfigField "String", "OPENGOV_API_URL", "\"https://opengov-backend.novasama-tech.org\"" } } diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/referendum/summary/v2/ReferendumSummaryApi.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/referendum/summary/v2/ReferendumSummaryApi.kt index 27762de251..82ebd4da05 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/referendum/summary/v2/ReferendumSummaryApi.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/referendum/summary/v2/ReferendumSummaryApi.kt @@ -1,18 +1,20 @@ package io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.summary.v2 -import retrofit2.http.GET -import retrofit2.http.Header -import retrofit2.http.Query -import retrofit2.http.Url +import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.summary.v2.request.ReferendumSummariesRequest +import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.summary.v2.request.ReferendumSummaryRequest +import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.summary.v2.response.ReferendumSummaryResponse +import retrofit2.http.Body +import retrofit2.http.POST interface ReferendumSummaryApi { - @GET + @POST("/not-secure/api/v1/referendum-summaries/single") suspend fun getReferendumSummary( - @Url url: String, - @Header("x-network") networkHeader: String?, - @Header("x-ai-summary-api-key") summaryApiKey: String, - @Query("postId") postId: Int, - @Query("proposalType") proposalType: String = "referendums_v2" + @Body body: ReferendumSummaryRequest ): ReferendumSummaryResponse + + @POST("/not-secure/api/v1/referendum-summaries/list") + suspend fun getReferendumSummaries( + @Body body: ReferendumSummariesRequest + ): List } diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/referendum/summary/v2/ReferendumSummaryDataSource.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/referendum/summary/v2/ReferendumSummaryDataSource.kt index f54d2ec555..6296f902eb 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/referendum/summary/v2/ReferendumSummaryDataSource.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/referendum/summary/v2/ReferendumSummaryDataSource.kt @@ -1,27 +1,43 @@ package io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.summary.v2 import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.ReferendumId -import io.novafoundation.nova.feature_governance_impl.BuildConfig -import io.novafoundation.nova.runtime.ext.summaryApiOrNull +import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.summary.v2.request.ReferendumSummariesRequest +import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.summary.v2.request.ReferendumSummaryRequest import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain interface ReferendumSummaryDataSource { - suspend fun loadSummary(chain: Chain, id: ReferendumId, baseUrl: String): String + suspend fun loadSummary(chain: Chain, id: ReferendumId, languageCode: String): String + + suspend fun loadSummaries(chain: Chain, ids: List, languageCode: String): Map } class RealReferendumSummaryDataSource( val api: ReferendumSummaryApi ) : ReferendumSummaryDataSource { - override suspend fun loadSummary(chain: Chain, id: ReferendumId, baseUrl: String): String { - val externalApi = chain.summaryApiOrNull()!! + override suspend fun loadSummary(chain: Chain, id: ReferendumId, languageCode: String): String { + val response = api.getReferendumSummary( + ReferendumSummaryRequest( + chainId = chain.id, + languageIsoCode = languageCode, + referendumId = id.value.toString() + ) + ) + + return response.summary + } + + override suspend fun loadSummaries(chain: Chain, ids: List, languageCode: String): Map { + val response = api.getReferendumSummaries( + ReferendumSummariesRequest( + chainId = chain.id, + languageIsoCode = languageCode, + referendumIds = ids.map { it.value.toString() } + ) + ) - return api.getReferendumSummary( - baseUrl, - networkHeader = externalApi.network, - summaryApiKey = BuildConfig.SUMMARY_API_KEY, - postId = id.value.toInt() - ).summary + return response.associateBy { ReferendumId(it.referendumId.toBigInteger()) } + .mapValues { it.value.summary } } } diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/referendum/summary/v2/ReferendumSummaryResponse.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/referendum/summary/v2/ReferendumSummaryResponse.kt deleted file mode 100644 index 1e5beed9e8..0000000000 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/referendum/summary/v2/ReferendumSummaryResponse.kt +++ /dev/null @@ -1,3 +0,0 @@ -package io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.summary.v2 - -class ReferendumSummaryResponse(val summary: String) diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/referendum/summary/v2/request/ReferendumSummariesRequest.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/referendum/summary/v2/request/ReferendumSummariesRequest.kt new file mode 100644 index 0000000000..ac7acfa234 --- /dev/null +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/referendum/summary/v2/request/ReferendumSummariesRequest.kt @@ -0,0 +1,7 @@ +package io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.summary.v2.request + +class ReferendumSummariesRequest( + val chainId: String, + val languageIsoCode: String, + val referendumIds: List +) diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/referendum/summary/v2/request/ReferendumSummaryRequest.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/referendum/summary/v2/request/ReferendumSummaryRequest.kt new file mode 100644 index 0000000000..a25e2113fb --- /dev/null +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/referendum/summary/v2/request/ReferendumSummaryRequest.kt @@ -0,0 +1,7 @@ +package io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.summary.v2.request + +class ReferendumSummaryRequest( + val chainId: String, + val languageIsoCode: String, + val referendumId: String +) diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/referendum/summary/v2/response/ReferendumSummaryResponse.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/referendum/summary/v2/response/ReferendumSummaryResponse.kt new file mode 100644 index 0000000000..d1fcb1ec92 --- /dev/null +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/referendum/summary/v2/response/ReferendumSummaryResponse.kt @@ -0,0 +1,3 @@ +package io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.summary.v2.response + +class ReferendumSummaryResponse(val referendumId: Int, val summary: String) diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/di/modules/screens/TinderGovModule.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/di/modules/screens/TinderGovModule.kt index 1027cd974c..f30467b97c 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/di/modules/screens/TinderGovModule.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/di/modules/screens/TinderGovModule.kt @@ -2,11 +2,15 @@ package io.novafoundation.nova.feature_governance_impl.di.modules.screens import dagger.Module import dagger.Provides +import io.novafoundation.nova.common.data.memory.ComputationalCache import io.novafoundation.nova.common.data.network.NetworkApiCreator import io.novafoundation.nova.common.di.scope.FeatureScope import io.novafoundation.nova.core_db.dao.TinderGovDao import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository +import io.novafoundation.nova.feature_governance_api.domain.referendum.summary.ReferendaSummaryInteractor +import io.novafoundation.nova.feature_governance_api.domain.tindergov.TinderGovBasketInteractor import io.novafoundation.nova.feature_governance_api.domain.tindergov.TinderGovInteractor +import io.novafoundation.nova.feature_governance_impl.BuildConfig import io.novafoundation.nova.feature_governance_impl.data.GovernanceSharedState import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.summary.v2.RealReferendumSummaryDataSource import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.summary.v2.ReferendumSummaryApi @@ -20,9 +24,11 @@ import io.novafoundation.nova.feature_governance_impl.domain.referendum.details. import io.novafoundation.nova.feature_governance_impl.domain.referendum.details.call.ReferendumPreImageParser import io.novafoundation.nova.feature_governance_impl.domain.referendum.list.ReferendaSharedComputation import io.novafoundation.nova.feature_governance_impl.domain.referendum.list.filtering.ReferendaFilteringProvider +import io.novafoundation.nova.feature_governance_impl.domain.referendum.tindergov.RealTinderGovBasketInteractor import io.novafoundation.nova.feature_governance_impl.domain.referendum.tindergov.RealTinderGovInteractor +import io.novafoundation.nova.feature_governance_impl.domain.summary.RealReferendaSummaryInteractor +import io.novafoundation.nova.feature_governance_impl.domain.summary.ReferendaSummarySharedComputation import io.novafoundation.nova.feature_wallet_api.domain.AssetUseCase -import io.novafoundation.nova.feature_wallet_api.domain.interfaces.WalletRepository @Module class TinderGovModule { @@ -30,7 +36,7 @@ class TinderGovModule { @Provides @FeatureScope fun provideReferendumSummaryApi(apiCreator: NetworkApiCreator): ReferendumSummaryApi { - return apiCreator.create(ReferendumSummaryApi::class.java) + return apiCreator.create(ReferendumSummaryApi::class.java, customBaseUrl = BuildConfig.OPENGOV_API_URL) } @Provides @@ -69,10 +75,7 @@ class TinderGovModule { governanceSharedState: GovernanceSharedState, referendaSharedComputation: ReferendaSharedComputation, accountRepository: AccountRepository, - referendumDetailsRepository: ReferendumDetailsRepository, preImageParser: ReferendumPreImageParser, - tinderGovBasketRepository: TinderGovBasketRepository, - walletRepository: WalletRepository, tinderGovVotingPowerRepository: TinderGovVotingPowerRepository, referendaFilteringProvider: ReferendaFilteringProvider, assetUseCase: AssetUseCase @@ -80,12 +83,53 @@ class TinderGovModule { governanceSharedState, referendaSharedComputation, accountRepository, - referendumDetailsRepository, preImageParser, - tinderGovBasketRepository, - walletRepository, tinderGovVotingPowerRepository, referendaFilteringProvider, assetUseCase ) + + @Provides + @FeatureScope + fun provideReferendaSummarySharedComputation( + computationalCache: ComputationalCache, + referendumDetailsRepository: ReferendumDetailsRepository, + accountRepository: AccountRepository + ) = ReferendaSummarySharedComputation( + computationalCache, + referendumDetailsRepository, + accountRepository + ) + + @Provides + @FeatureScope + fun provideReferendaSummaryInteractor( + governanceSharedState: GovernanceSharedState, + referendaSummarySharedComputation: ReferendaSummarySharedComputation + ): ReferendaSummaryInteractor = RealReferendaSummaryInteractor( + governanceSharedState, + referendaSummarySharedComputation + ) + + @Provides + @FeatureScope + fun provideTinderGovBasketInteractor( + governanceSharedState: GovernanceSharedState, + referendaSharedComputation: ReferendaSharedComputation, + accountRepository: AccountRepository, + tinderGovBasketRepository: TinderGovBasketRepository, + tinderGovVotingPowerRepository: TinderGovVotingPowerRepository, + referendaFilteringProvider: ReferendaFilteringProvider, + assetUseCase: AssetUseCase, + tinderGovInteractor: TinderGovInteractor + ): TinderGovBasketInteractor = RealTinderGovBasketInteractor( + governanceSharedState = governanceSharedState, + referendaSharedComputation = referendaSharedComputation, + accountRepository = accountRepository, + tinderGovBasketRepository = tinderGovBasketRepository, + tinderGovVotingPowerRepository = tinderGovVotingPowerRepository, + referendaFilteringProvider = referendaFilteringProvider, + assetUseCase = assetUseCase, + tinderGovInteractor = tinderGovInteractor + ) } diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/details/ReferendumDetailsRepository.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/details/ReferendumDetailsRepository.kt index e3aaf1dcd4..2aa19932e3 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/details/ReferendumDetailsRepository.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/details/ReferendumDetailsRepository.kt @@ -1,19 +1,26 @@ package io.novafoundation.nova.feature_governance_impl.domain.referendum.details +import io.novafoundation.nova.core.model.Language import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.ReferendumId import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.summary.v2.ReferendumSummaryDataSource import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain interface ReferendumDetailsRepository { - suspend fun loadSummary(chain: Chain, id: ReferendumId, baseUrl: String): String + suspend fun loadSummary(chain: Chain, id: ReferendumId, selectedLanguage: Language): String + + suspend fun loadSummaries(chain: Chain, ids: List, selectedLanguage: Language): Map } class RealReferendumDetailsRepository( private val referendumSummaryDataSource: ReferendumSummaryDataSource ) : ReferendumDetailsRepository { - override suspend fun loadSummary(chain: Chain, id: ReferendumId, baseUrl: String): String { - return referendumSummaryDataSource.loadSummary(chain, id, baseUrl) + override suspend fun loadSummary(chain: Chain, id: ReferendumId, selectedLanguage: Language): String { + return referendumSummaryDataSource.loadSummary(chain, id, selectedLanguage.iso639Code) + } + + override suspend fun loadSummaries(chain: Chain, ids: List, selectedLanguage: Language): Map { + return referendumSummaryDataSource.loadSummaries(chain, ids, selectedLanguage.iso639Code) } } diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/tindergov/RealTinderGovBasketInteractor.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/tindergov/RealTinderGovBasketInteractor.kt new file mode 100644 index 0000000000..6fc41f2cac --- /dev/null +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/tindergov/RealTinderGovBasketInteractor.kt @@ -0,0 +1,125 @@ +package io.novafoundation.nova.feature_governance_impl.domain.referendum.tindergov + +import io.novafoundation.nova.common.utils.flowOfAll +import io.novafoundation.nova.common.utils.mapToSet +import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository +import io.novafoundation.nova.feature_governance_api.data.model.TinderGovBasketItem +import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.ReferendumId +import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.VoteType +import io.novafoundation.nova.feature_governance_api.domain.tindergov.TinderGovBasketInteractor +import io.novafoundation.nova.feature_governance_api.domain.tindergov.TinderGovInteractor +import io.novafoundation.nova.feature_governance_impl.data.GovernanceSharedState +import io.novafoundation.nova.feature_governance_impl.data.repository.tindergov.TinderGovBasketRepository +import io.novafoundation.nova.feature_governance_impl.data.repository.tindergov.TinderGovVotingPowerRepository +import io.novafoundation.nova.feature_governance_impl.domain.referendum.list.ReferendaSharedComputation +import io.novafoundation.nova.feature_governance_impl.domain.referendum.list.filtering.ReferendaFilteringProvider +import io.novafoundation.nova.feature_wallet_api.domain.AssetUseCase +import io.novafoundation.nova.feature_wallet_api.domain.getCurrentAsset +import io.novafoundation.nova.feature_wallet_api.domain.model.Asset +import io.novafoundation.nova.feature_wallet_api.domain.model.availableToVote +import io.novafoundation.nova.runtime.state.chain +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.first + +class RealTinderGovBasketInteractor( + private val governanceSharedState: GovernanceSharedState, + private val referendaSharedComputation: ReferendaSharedComputation, + private val accountRepository: AccountRepository, + private val tinderGovBasketRepository: TinderGovBasketRepository, + private val tinderGovVotingPowerRepository: TinderGovVotingPowerRepository, + private val referendaFilteringProvider: ReferendaFilteringProvider, + private val assetUseCase: AssetUseCase, + private val tinderGovInteractor: TinderGovInteractor +) : TinderGovBasketInteractor { + + override fun observeTinderGovBasket(): Flow> { + return flowOfAll { + val metaAccount = accountRepository.getSelectedMetaAccount() + val chain = governanceSharedState.chain() + + tinderGovBasketRepository.observeBasket(metaAccount.id, chain.id) + } + } + + override suspend fun getTinderGovBasket(): List { + val metaAccount = accountRepository.getSelectedMetaAccount() + val chain = governanceSharedState.chain() + + return tinderGovBasketRepository.getBasket(metaAccount.id, chain.id) + } + + override suspend fun addItemToBasket(referendumId: ReferendumId, voteType: VoteType) { + val metaAccount = accountRepository.getSelectedMetaAccount() + val chain = governanceSharedState.chain() + + val votingPower = tinderGovVotingPowerRepository.getVotingPower(metaAccount.id, chain.id)!! + + tinderGovBasketRepository.add( + TinderGovBasketItem( + metaId = metaAccount.id, + chainId = chain.id, + referendumId = referendumId, + voteType = voteType, + conviction = votingPower.conviction, + amount = votingPower.amount + ) + ) + } + + override suspend fun removeReferendumFromBasket(item: TinderGovBasketItem) { + tinderGovBasketRepository.remove(item) + } + + override suspend fun removeBasketItems(items: Collection) { + tinderGovBasketRepository.remove(items) + } + + override suspend fun isBasketEmpty(): Boolean { + val metaAccount = accountRepository.getSelectedMetaAccount() + val chain = governanceSharedState.chain() + + return tinderGovBasketRepository.isBasketEmpty(metaAccount.id, chain.id) + } + + override suspend fun clearBasket() { + val metaAccount = accountRepository.getSelectedMetaAccount() + val chain = governanceSharedState.chain() + + tinderGovBasketRepository.clearBasket(metaAccount.id, chain.id) + } + + override suspend fun getBasketItemsToRemove(coroutineScope: CoroutineScope): List { + val metaAccount = accountRepository.getSelectedMetaAccount() + val chain = governanceSharedState.chain() + val asset = assetUseCase.getCurrentAsset() + val basket = tinderGovBasketRepository.getBasket(metaAccount.id, chain.id) + + val availableToVoteReferenda = tinderGovInteractor.observeReferendaAvailableToVote(coroutineScope).first() + .mapToSet { it.id } + + return basket.filter { it.isItemNotAvailableToVote(availableToVoteReferenda, asset) } + } + + override suspend fun awaitAllItemsVoted(coroutineScope: CoroutineScope, basket: List) { + tinderGovInteractor.observeReferendaState(coroutineScope) + .filter { referendaState -> + val referenda = referendaState.referenda.associateBy { it.id } + val allBasketItemsVoted = basket.all { + val referendum = referenda[it.referendumId] + referendum?.referendumVote != null + } + + allBasketItemsVoted + }.first() + } + + private fun TinderGovBasketItem.isItemNotAvailableToVote( + availableToVoteReferenda: Set, + asset: Asset + ): Boolean { + val notEnoughBalance = this.amount > asset.availableToVote() + return (this.referendumId !in availableToVoteReferenda) || notEnoughBalance + } +} diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/tindergov/RealTinderGovInteractor.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/tindergov/RealTinderGovInteractor.kt index fe8a9e616b..da58e90c66 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/tindergov/RealTinderGovInteractor.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/tindergov/RealTinderGovInteractor.kt @@ -2,12 +2,10 @@ package io.novafoundation.nova.feature_governance_impl.domain.referendum.tinderg import io.novafoundation.nova.common.domain.filterLoaded import io.novafoundation.nova.common.utils.flowOfAll -import io.novafoundation.nova.common.utils.mapToSet import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository import io.novafoundation.nova.feature_governance_api.data.model.TinderGovBasketItem import io.novafoundation.nova.feature_governance_api.data.model.VotingPower import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.ReferendumId -import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.VoteType import io.novafoundation.nova.feature_governance_api.domain.referendum.details.ReferendumCall import io.novafoundation.nova.feature_governance_api.domain.referendum.list.ReferendaState import io.novafoundation.nova.feature_governance_api.domain.referendum.list.ReferendumPreview @@ -17,18 +15,14 @@ import io.novafoundation.nova.feature_governance_api.domain.referendum.list.user import io.novafoundation.nova.feature_governance_api.domain.tindergov.TinderGovInteractor import io.novafoundation.nova.feature_governance_api.domain.tindergov.VotingPowerState import io.novafoundation.nova.feature_governance_impl.data.GovernanceSharedState -import io.novafoundation.nova.feature_governance_impl.data.repository.tindergov.TinderGovBasketRepository import io.novafoundation.nova.feature_governance_impl.data.repository.tindergov.TinderGovVotingPowerRepository -import io.novafoundation.nova.feature_governance_impl.domain.referendum.details.ReferendumDetailsRepository import io.novafoundation.nova.feature_governance_impl.domain.referendum.details.call.ReferendumPreImageParser import io.novafoundation.nova.feature_governance_impl.domain.referendum.list.ReferendaSharedComputation import io.novafoundation.nova.feature_governance_impl.domain.referendum.list.filtering.ReferendaFilteringProvider import io.novafoundation.nova.feature_wallet_api.domain.AssetUseCase import io.novafoundation.nova.feature_wallet_api.domain.getCurrentAsset -import io.novafoundation.nova.feature_wallet_api.domain.interfaces.WalletRepository import io.novafoundation.nova.feature_wallet_api.domain.model.Asset import io.novafoundation.nova.feature_wallet_api.domain.model.availableToVote -import io.novafoundation.nova.runtime.ext.summaryApiOrNull import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId import io.novafoundation.nova.runtime.state.chain import io.novafoundation.nova.runtime.state.selectedOption @@ -42,10 +36,7 @@ class RealTinderGovInteractor( private val governanceSharedState: GovernanceSharedState, private val referendaSharedComputation: ReferendaSharedComputation, private val accountRepository: AccountRepository, - private val referendumDetailsRepository: ReferendumDetailsRepository, private val preImageParser: ReferendumPreImageParser, - private val tinderGovBasketRepository: TinderGovBasketRepository, - private val walletRepository: WalletRepository, private val tinderGovVotingPowerRepository: TinderGovVotingPowerRepository, private val referendaFilteringProvider: ReferendaFilteringProvider, private val assetUseCase: AssetUseCase @@ -70,47 +61,7 @@ class RealTinderGovInteractor( .map { filterAvailableToVoteReferenda(it) } } - override fun observeTinderGovBasket(): Flow> { - return flowOfAll { - val metaAccount = accountRepository.getSelectedMetaAccount() - val chain = governanceSharedState.chain() - - tinderGovBasketRepository.observeBasket(metaAccount.id, chain.id) - } - } - - override suspend fun getTinderGovBasket(): List { - val metaAccount = accountRepository.getSelectedMetaAccount() - val chain = governanceSharedState.chain() - - return tinderGovBasketRepository.getBasket(metaAccount.id, chain.id) - } - - override suspend fun addItemToBasket(referendumId: ReferendumId, voteType: VoteType) { - val metaAccount = accountRepository.getSelectedMetaAccount() - val chain = governanceSharedState.chain() - - val votingPower = getVotingPower(metaAccount.id, chain.id)!! - - tinderGovBasketRepository.add( - TinderGovBasketItem( - metaId = metaAccount.id, - chainId = chain.id, - referendumId = referendumId, - voteType = voteType, - conviction = votingPower.conviction, - amount = votingPower.amount - ) - ) - } - - override suspend fun loadReferendumSummary(id: ReferendumId): String? { - val chain = governanceSharedState.chain() - val summaryApi = chain.summaryApiOrNull() ?: return null - return referendumDetailsRepository.loadSummary(chain, id, summaryApi.url) - } - - override suspend fun loadReferendumAmount(referendumPreview: ReferendumPreview): ReferendumCall.TreasuryRequest? { + override suspend fun getReferendumAmount(referendumPreview: ReferendumPreview): ReferendumCall.TreasuryRequest? { val selectedGovernanceOption = governanceSharedState.selectedOption() val chain = selectedGovernanceOption.assetWithChain.chain @@ -142,40 +93,6 @@ class RealTinderGovInteractor( } } - override suspend fun removeReferendumFromBasket(item: TinderGovBasketItem) { - tinderGovBasketRepository.remove(item) - } - - override suspend fun removeBasketItems(items: Collection) { - tinderGovBasketRepository.remove(items) - } - - override suspend fun isBasketEmpty(): Boolean { - val metaAccount = accountRepository.getSelectedMetaAccount() - val chain = governanceSharedState.chain() - - return tinderGovBasketRepository.isBasketEmpty(metaAccount.id, chain.id) - } - - override suspend fun clearBasket() { - val metaAccount = accountRepository.getSelectedMetaAccount() - val chain = governanceSharedState.chain() - - tinderGovBasketRepository.clearBasket(metaAccount.id, chain.id) - } - - override suspend fun getBasketItemsToRemove(coroutineScope: CoroutineScope): List { - val metaAccount = accountRepository.getSelectedMetaAccount() - val chain = governanceSharedState.chain() - val asset = assetUseCase.getCurrentAsset() - val basket = tinderGovBasketRepository.getBasket(metaAccount.id, chain.id) - - val availableToVoteReferenda = observeReferendaAvailableToVote(coroutineScope).first() - .mapToSet { it.id } - - return basket.filter { it.isItemNotAvailableToVote(availableToVoteReferenda, asset) } - } - override suspend fun awaitAllItemsVoted(coroutineScope: CoroutineScope, basket: List) { observeReferendaState(coroutineScope) .filter { referendaState -> diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/summary/ReferendaSummarySharedComputation.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/summary/ReferendaSummarySharedComputation.kt new file mode 100644 index 0000000000..af77cac537 --- /dev/null +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/summary/ReferendaSummarySharedComputation.kt @@ -0,0 +1,30 @@ +package io.novafoundation.nova.feature_governance_impl.domain.summary + +import io.novafoundation.nova.common.data.memory.ComputationalCache +import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository +import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.ReferendumId +import io.novafoundation.nova.feature_governance_api.data.source.SupportedGovernanceOption +import io.novafoundation.nova.feature_governance_impl.domain.referendum.details.ReferendumDetailsRepository +import kotlinx.coroutines.CoroutineScope + +class ReferendaSummarySharedComputation( + private val computationalCache: ComputationalCache, + private val referendumDetailsRepository: ReferendumDetailsRepository, + private val accountRepository: AccountRepository +) { + + suspend fun summaries( + governanceOption: SupportedGovernanceOption, + referendaIds: List, + scope: CoroutineScope + ): Map { + val chainId = governanceOption.assetWithChain.chain.id + val referendaSet = referendaIds.toSet() + val selectedLanguage = accountRepository.selectedLanguage() + val key = "REFERENDA_SUMMARIES:$chainId:$referendaSet:${selectedLanguage.iso639Code}" + + return computationalCache.useCache(key, scope) { + referendumDetailsRepository.loadSummaries(governanceOption.assetWithChain.chain, referendaIds, selectedLanguage) + } + } +} diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/summary/ReferendumSummaryInteractor.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/summary/ReferendumSummaryInteractor.kt new file mode 100644 index 0000000000..bed3e023f0 --- /dev/null +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/summary/ReferendumSummaryInteractor.kt @@ -0,0 +1,17 @@ +package io.novafoundation.nova.feature_governance_impl.domain.summary + +import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.ReferendumId +import io.novafoundation.nova.feature_governance_api.domain.referendum.summary.ReferendaSummaryInteractor +import io.novafoundation.nova.feature_governance_impl.data.GovernanceSharedState +import io.novafoundation.nova.runtime.state.selectedOption +import kotlinx.coroutines.CoroutineScope + +class RealReferendaSummaryInteractor( + private val governanceSharedState: GovernanceSharedState, + private val referendaSummarySharedComputation: ReferendaSummarySharedComputation +) : ReferendaSummaryInteractor { + + override suspend fun getReferendaSummaries(ids: List, coroutineScope: CoroutineScope): Map { + return referendaSummarySharedComputation.summaries(governanceSharedState.selectedOption(), ids, coroutineScope) + } +} diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/common/info/di/DescriptionComponent.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/common/info/di/DescriptionComponent.kt index b0d00f142b..059aad55b5 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/common/info/di/DescriptionComponent.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/common/info/di/DescriptionComponent.kt @@ -6,6 +6,7 @@ import dagger.Subcomponent import io.novafoundation.nova.common.di.scope.ScreenScope import io.novafoundation.nova.feature_governance_impl.presentation.common.info.ReferendumInfoFragment import io.novafoundation.nova.feature_governance_impl.presentation.common.info.ReferendumInfoPayload +import io.novafoundation.nova.feature_governance_impl.presentation.common.info.di.ReferendumInfoModule @Subcomponent( modules = [ diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/common/info/di/DescriptionModule.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/common/info/di/DescriptionModule.kt index 8d153eb1a5..d880690c65 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/common/info/di/DescriptionModule.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/common/info/di/DescriptionModule.kt @@ -1,4 +1,4 @@ -package io.novafoundation.nova.feature_governance_impl.presentation.common.description.di +package io.novafoundation.nova.feature_governance_impl.presentation.common.info.di import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModel diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/list/ReferendaListViewModel.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/list/ReferendaListViewModel.kt index 4bde911e6c..3f3cd1b4e0 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/list/ReferendaListViewModel.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/list/ReferendaListViewModel.kt @@ -1,6 +1,7 @@ package io.novafoundation.nova.feature_governance_impl.presentation.referenda.list import android.util.Log +import androidx.lifecycle.viewModelScope import io.novafoundation.nova.common.base.BaseViewModel import io.novafoundation.nova.common.domain.dataOrNull import io.novafoundation.nova.common.list.toListWithHeaders @@ -15,6 +16,7 @@ import io.novafoundation.nova.common.utils.withItemScope import io.novafoundation.nova.common.view.PlaceholderModel import io.novafoundation.nova.core.updater.UpdateSystem import io.novafoundation.nova.feature_account_api.domain.interfaces.SelectedAccountUseCase +import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.ReferendumId import io.novafoundation.nova.feature_governance_api.domain.referendum.list.DelegatedState import io.novafoundation.nova.feature_governance_api.domain.referendum.list.GovernanceLocksOverview import io.novafoundation.nova.feature_governance_api.domain.referendum.list.ReferendaListInteractor @@ -26,6 +28,7 @@ import io.novafoundation.nova.feature_governance_impl.domain.filters.ReferendaFi import io.novafoundation.nova.feature_governance_api.domain.referendum.filters.ReferendumType import io.novafoundation.nova.feature_governance_api.domain.referendum.filters.ReferendumTypeFilter import io.novafoundation.nova.feature_governance_api.domain.referendum.list.ReferendaListState +import io.novafoundation.nova.feature_governance_api.domain.referendum.summary.ReferendaSummaryInteractor import io.novafoundation.nova.feature_governance_impl.presentation.GovernanceRouter import io.novafoundation.nova.feature_governance_impl.presentation.referenda.common.ReferendumFormatter import io.novafoundation.nova.feature_governance_impl.presentation.referenda.common.list.ReferendaListStateModel @@ -59,7 +62,8 @@ class ReferendaListViewModel( private val updateSystem: UpdateSystem, private val governanceRouter: GovernanceRouter, private val referendumFormatter: ReferendumFormatter, - private val governanceDAppsInteractor: GovernanceDAppsInteractor + private val governanceDAppsInteractor: GovernanceDAppsInteractor, + private val referendaSummaryInteractor: ReferendaSummaryInteractor ) : BaseViewModel(), WithAssetSelector { override val assetSelectorMixin = assetSelectorFactory.create( @@ -103,9 +107,14 @@ class ReferendaListViewModel( .inBackground() .shareWhileSubscribed() - val tinderGovBanner = referendaListStateFlow.map { referenda -> + private val referendaSummariesFlow = referendaListStateFlow.mapLoading { referenda -> + val referendaIds = referenda.availableToVoteReferenda.map { it.id } + referendaSummaryInteractor.getReferendaSummaries(referendaIds, viewModelScope) + }.shareInBackground() + + val tinderGovBanner = referendaSummariesFlow.map { summaries -> val chain = selectedAssetSharedState.chain() - mapTinderGovToUi(chain, referenda.dataOrNull) + mapTinderGovToUi(chain, summaries.dataOrNull) } .inBackground() .shareWhileSubscribed() @@ -170,17 +179,15 @@ class ReferendaListViewModel( } } - private fun mapTinderGovToUi(chain: Chain, referendaListState: ReferendaListState?): TinderGovBannerModel? { + private fun mapTinderGovToUi(chain: Chain, referendaSummaries: Map?): TinderGovBannerModel? { if (!chain.supportTinderGov()) return null - if (referendaListState == null) return null - - val availableToVote = referendaListState.availableToVoteReferenda + if (referendaSummaries == null) return null return TinderGovBannerModel( - chipText = if (availableToVote.isEmpty()) { + if (referendaSummaries.isEmpty()) { null } else { - resourceManager.getString(R.string.referenda_swipe_gov_banner_chip, availableToVote.size) + resourceManager.getString(R.string.referenda_swipe_gov_banner_chip, referendaSummaries.size) } ) } diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/list/di/ReferendaListModule.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/list/di/ReferendaListModule.kt index e355572148..05fb3b415c 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/list/di/ReferendaListModule.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/list/di/ReferendaListModule.kt @@ -12,6 +12,7 @@ import io.novafoundation.nova.common.resources.ResourceManager import io.novafoundation.nova.core.updater.UpdateSystem import io.novafoundation.nova.feature_account_api.domain.interfaces.SelectedAccountUseCase import io.novafoundation.nova.feature_governance_api.domain.referendum.list.ReferendaListInteractor +import io.novafoundation.nova.feature_governance_api.domain.referendum.summary.ReferendaSummaryInteractor import io.novafoundation.nova.feature_governance_impl.data.GovernanceSharedState import io.novafoundation.nova.feature_governance_impl.domain.dapp.GovernanceDAppsInteractor import io.novafoundation.nova.feature_governance_impl.domain.filters.ReferendaFiltersInteractor @@ -36,7 +37,8 @@ class ReferendaListModule { updateSystem: UpdateSystem, governanceRouter: GovernanceRouter, referendumFormatter: ReferendumFormatter, - governanceDAppsInteractor: GovernanceDAppsInteractor + governanceDAppsInteractor: GovernanceDAppsInteractor, + summaryInteractor: ReferendaSummaryInteractor ): ViewModel { return ReferendaListViewModel( assetSelectorFactory = assetSelectorFactory, @@ -48,7 +50,8 @@ class ReferendaListModule { updateSystem = updateSystem, governanceRouter = governanceRouter, referendumFormatter = referendumFormatter, - governanceDAppsInteractor = governanceDAppsInteractor + governanceDAppsInteractor = governanceDAppsInteractor, + referendaSummaryInteractor = summaryInteractor ) } diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/basket/TinderGovBasketViewModel.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/basket/TinderGovBasketViewModel.kt index ae7f974ab9..c31b67d5aa 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/basket/TinderGovBasketViewModel.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/basket/TinderGovBasketViewModel.kt @@ -14,6 +14,7 @@ import io.novafoundation.nova.common.utils.toggle import io.novafoundation.nova.feature_governance_api.data.model.TinderGovBasketItem import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.VoteType import io.novafoundation.nova.feature_governance_api.domain.referendum.list.ReferendumPreview +import io.novafoundation.nova.feature_governance_api.domain.tindergov.TinderGovBasketInteractor import io.novafoundation.nova.feature_governance_api.domain.tindergov.TinderGovInteractor import io.novafoundation.nova.feature_governance_impl.R import io.novafoundation.nova.feature_governance_impl.data.GovernanceSharedState @@ -38,6 +39,7 @@ class TinderGovBasketViewModel( private val governanceSharedState: GovernanceSharedState, private val router: GovernanceRouter, private val interactor: TinderGovInteractor, + private val basketInteractor: TinderGovBasketInteractor, private val votersFormatter: VotersFormatter, private val referendumFormatter: ReferendumFormatter, private val actionAwaitableMixinFactory: ActionAwaitableMixin.Factory, @@ -55,7 +57,7 @@ class TinderGovBasketViewModel( .map { it.associateBy { it.id } } .shareInBackground() - private val basketItemsFlow = interactor.observeTinderGovBasket() + private val basketItemsFlow = basketInteractor.observeTinderGovBasket() .shareInBackground() val editModeButtonText = inEditModeFlow.map { @@ -107,7 +109,7 @@ class TinderGovBasketViewModel( .firstOrNull { it.referendumId == item.id } ?: return@launch - interactor.removeReferendumFromBasket(referendum) + basketInteractor.removeReferendumFromBasket(referendum) closeScreenIfBasketIsEmpty() } @@ -151,10 +153,10 @@ class TinderGovBasketViewModel( private fun validateReferendaAndRemove() { launch { - val removedReferenda = interactor.getBasketItemsToRemove(coroutineScope) + val removedReferenda = basketInteractor.getBasketItemsToRemove(coroutineScope) if (removedReferenda.isNotEmpty()) { - interactor.removeBasketItems(removedReferenda) + basketInteractor.removeBasketItems(removedReferenda) itemsWasRemovedFromBasketAction.awaitAction(getFormattedAmountAvailableToVote()) @@ -169,7 +171,7 @@ class TinderGovBasketViewModel( } private suspend fun closeScreenIfBasketIsEmpty() { - if (interactor.isBasketEmpty()) { + if (basketInteractor.isBasketEmpty()) { router.back() } } diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/basket/di/TinderGovBasketModule.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/basket/di/TinderGovBasketModule.kt index 1e2dbbfdcd..acebdc6a49 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/basket/di/TinderGovBasketModule.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/basket/di/TinderGovBasketModule.kt @@ -10,6 +10,7 @@ import io.novafoundation.nova.common.di.viewmodel.ViewModelKey import io.novafoundation.nova.common.di.viewmodel.ViewModelModule import io.novafoundation.nova.common.mixin.actionAwaitable.ActionAwaitableMixin import io.novafoundation.nova.common.resources.ResourceManager +import io.novafoundation.nova.feature_governance_api.domain.tindergov.TinderGovBasketInteractor import io.novafoundation.nova.feature_governance_api.domain.tindergov.TinderGovInteractor import io.novafoundation.nova.feature_governance_impl.data.GovernanceSharedState import io.novafoundation.nova.feature_governance_impl.presentation.GovernanceRouter @@ -32,17 +33,19 @@ class TinderGovBasketModule { referendumFormatter: ReferendumFormatter, actionAwaitableMixinFactory: ActionAwaitableMixin.Factory, resourceManager: ResourceManager, - assetUseCase: AssetUseCase + assetUseCase: AssetUseCase, + basketInteractor: TinderGovBasketInteractor ): ViewModel { return TinderGovBasketViewModel( governanceSharedState = governanceSharedState, router = router, interactor = interactor, + basketInteractor = basketInteractor, votersFormatter = votersFormatter, referendumFormatter = referendumFormatter, actionAwaitableMixinFactory = actionAwaitableMixinFactory, resourceManager = resourceManager, - assetUseCase + assetUseCase = assetUseCase ) } diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/cards/TinderGovCardDetailsLoader.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/cards/TinderGovCardDetailsLoader.kt deleted file mode 100644 index 30f63c667f..0000000000 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/cards/TinderGovCardDetailsLoader.kt +++ /dev/null @@ -1,131 +0,0 @@ -package io.novafoundation.nova.feature_governance_impl.presentation.tindergov.cards - -import android.util.Log -import io.novafoundation.nova.common.domain.ExtendedLoadingState -import io.novafoundation.nova.common.domain.ExtendedLoadingState.Error -import io.novafoundation.nova.common.domain.ExtendedLoadingState.Loaded -import io.novafoundation.nova.common.domain.ExtendedLoadingState.Loading -import io.novafoundation.nova.common.domain.map -import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.ReferendumId -import io.novafoundation.nova.feature_governance_api.domain.referendum.details.ReferendumCall.TreasuryRequest -import io.novafoundation.nova.feature_governance_api.domain.referendum.list.ReferendumPreview -import io.novafoundation.nova.feature_governance_api.domain.tindergov.TinderGovInteractor -import io.novafoundation.nova.feature_wallet_api.domain.TokenUseCase -import io.novafoundation.nova.feature_wallet_api.presentation.model.AmountModel -import io.novafoundation.nova.feature_wallet_api.presentation.model.mapAmountToAmountModel -import io.novafoundation.nova.runtime.ext.fullId -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.launch -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock - -private typealias SummaryLoadingState = Map> -private typealias AmountLoadingState = Map> - -class TinderGovCardsDetailsLoaderFactory( - private val interactor: TinderGovInteractor, - private val tokenUseCase: TokenUseCase -) { - - fun create(coroutineScope: CoroutineScope): TinderGovCardDetailsLoader { - return TinderGovCardDetailsLoader(interactor, tokenUseCase, coroutineScope) - } -} - -class TinderGovCardDetailsLoader( - private val interactor: TinderGovInteractor, - private val tokenUseCase: TokenUseCase, - private val coroutineScope: CoroutineScope -) : CoroutineScope by coroutineScope { - - private val summaryMutex = Mutex() - private val amountMutex = Mutex() - - private val _cardsSummary = MutableStateFlow(emptyMap()) - private val _cardsAmount = MutableStateFlow(emptyMap()) - - val cardsSummaryFlow: Flow = _cardsSummary - val cardsAmountFlow: Flow = _cardsAmount - - suspend fun reloadSummary(referendumPreview: ReferendumPreview) { - summaryMutex.withLock { - _cardsSummary.update { it - referendumPreview.id } - } - - loadSummary(referendumPreview) - } - - suspend fun reloadAmount(referendumPreview: ReferendumPreview) { - amountMutex.withLock { - _cardsAmount.update { it - referendumPreview.id } - } - - loadAmount(referendumPreview) - } - - fun loadSummary(referendumPreview: ReferendumPreview) { - coroutineScope.launch { - val id = referendumPreview.id - if (containsSummary(id)) return@launch - - setSummaryLoadingState(id, Loading) - runCatching { interactor.loadReferendumSummary(id) } - .onSuccess { setSummaryLoadingState(id, Loaded(it)) } - .onFailure { - Log.e("TinderGovCardDetailsLoader", "Failed to load referendum summary", it) - - setSummaryLoadingState(id, Error(it)) - } - } - } - - fun loadAmount(referendumPreview: ReferendumPreview) { - coroutineScope.launch { - val id = referendumPreview.id - if (containsAmount(id)) return@launch - - setAmountLoadingState(id, Loading) - runCatching { interactor.loadReferendumAmount(referendumPreview) } - .onSuccess { setAmountLoadingState(id, Loaded(it)) } - .onFailure { - Log.e("TinderGovCardDetailsLoader", "Failed to load referendum amount", it) - - setAmountLoadingState(id, Error(it)) - } - } - } - - private suspend fun containsSummary(id: ReferendumId): Boolean { - return summaryMutex.withLock { _cardsSummary.first().containsKey(id) } - } - - private suspend fun containsAmount(id: ReferendumId): Boolean { - return amountMutex.withLock { _cardsAmount.first().containsKey(id) } - } - - private suspend fun setSummaryLoadingState(id: ReferendumId, summary: ExtendedLoadingState) { - summaryMutex.withLock { - _cardsSummary.update { it.plus(id to summary) } - } - } - - private suspend fun setAmountLoadingState(id: ReferendumId, treasuryRequestState: ExtendedLoadingState) { - amountMutex.withLock { - val amountModel = treasuryRequestState.map { treasuryRequest -> - treasuryRequest?.let { - val token = tokenUseCase.getToken(treasuryRequest.chainAsset.fullId) - mapAmountToAmountModel(treasuryRequest.amount, token) - } - } - - _cardsAmount.update { it.plus(id to amountModel) } - } - } -} - -private fun MutableStateFlow.update(updater: (T) -> T) { - value = updater(value) -} diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/cards/TinderGovCardsViewModel.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/cards/TinderGovCardsViewModel.kt index af2b77678c..d176dcff32 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/cards/TinderGovCardsViewModel.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/cards/TinderGovCardsViewModel.kt @@ -2,13 +2,10 @@ package io.novafoundation.nova.feature_governance_impl.presentation.tindergov.ca import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope +import io.noties.markwon.Markwon import io.novafoundation.nova.common.base.BaseViewModel import io.novafoundation.nova.common.base.TitleAndMessage -import io.novafoundation.nova.common.domain.ExtendedLoadingState -import io.novafoundation.nova.common.domain.isError -import io.novafoundation.nova.common.domain.isLoaded -import io.novafoundation.nova.common.domain.map -import io.novafoundation.nova.common.domain.orLoading import io.novafoundation.nova.common.mixin.actionAwaitable.ActionAwaitableMixin import io.novafoundation.nova.common.mixin.actionAwaitable.confirmingOrDenyingAction import io.novafoundation.nova.common.navigation.awaitResponse @@ -17,7 +14,6 @@ import io.novafoundation.nova.common.utils.Event import io.novafoundation.nova.common.utils.formatTokenAmount import io.novafoundation.nova.common.utils.formatting.format import io.novafoundation.nova.common.utils.onEachWithPrevious -import io.novafoundation.nova.common.utils.safeSubList import io.novafoundation.nova.common.utils.sendEvent import io.novafoundation.nova.feature_governance_api.data.model.TinderGovBasketItem import io.novafoundation.nova.feature_governance_api.data.model.VotingPower @@ -25,40 +21,46 @@ import io.novafoundation.nova.feature_governance_api.data.network.blockhain.mode import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.VoteType import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.amountMultiplier import io.novafoundation.nova.feature_governance_api.domain.referendum.list.ReferendumPreview +import io.novafoundation.nova.feature_governance_api.domain.referendum.summary.ReferendaSummaryInteractor +import io.novafoundation.nova.feature_governance_api.domain.tindergov.TinderGovBasketInteractor import io.novafoundation.nova.feature_governance_api.domain.tindergov.TinderGovInteractor import io.novafoundation.nova.feature_governance_api.domain.tindergov.VotingPowerState import io.novafoundation.nova.feature_governance_impl.R import io.novafoundation.nova.feature_governance_impl.presentation.GovernanceRouter import io.novafoundation.nova.feature_governance_impl.presentation.common.info.ReferendumInfoPayload -import io.novafoundation.nova.feature_governance_impl.presentation.referenda.common.ReferendumFormatter import io.novafoundation.nova.feature_governance_impl.presentation.referenda.vote.setup.tindergov.TinderGovVoteRequester import io.novafoundation.nova.feature_governance_impl.presentation.tindergov.cards.adapter.TinderGovCardRvItem +import io.novafoundation.nova.feature_governance_impl.presentation.tindergov.cards.model.CardWithDetails +import io.novafoundation.nova.feature_governance_impl.presentation.tindergov.cards.model.ReferendaCounterModel +import io.novafoundation.nova.feature_governance_impl.presentation.tindergov.cards.model.ReferendaWithBasket import io.novafoundation.nova.feature_wallet_api.domain.AssetUseCase +import io.novafoundation.nova.feature_wallet_api.domain.TokenUseCase import io.novafoundation.nova.feature_wallet_api.domain.getCurrentAsset import io.novafoundation.nova.feature_wallet_api.domain.model.amountFromPlanks import io.novafoundation.nova.feature_wallet_api.presentation.model.AmountModel +import io.novafoundation.nova.feature_wallet_api.presentation.model.mapAmountToAmountModel +import io.novafoundation.nova.runtime.ext.fullId import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.distinctUntilChangedBy import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch class TinderGovCardsViewModel( private val router: GovernanceRouter, - private val tinderGovCardDetailsLoaderFactory: TinderGovCardsDetailsLoaderFactory, private val interactor: TinderGovInteractor, - private val referendumFormatter: ReferendumFormatter, + private val basketInteractor: TinderGovBasketInteractor, private val actionAwaitableMixinFactory: ActionAwaitableMixin.Factory, private val tinderGovVoteRequester: TinderGovVoteRequester, private val assetUseCase: AssetUseCase, - private val resourceManager: ResourceManager + private val resourceManager: ResourceManager, + private val referendaSummaryInteractor: ReferendaSummaryInteractor, + private val tokenUseCase: TokenUseCase, + private val cardsMarkdown: Markwon, ) : BaseViewModel() { companion object { @@ -67,27 +69,14 @@ class TinderGovCardsViewModel( private val cardsBackground = createCardsBackground() - private val tinderGovCardDetailsLoader = tinderGovCardDetailsLoaderFactory.create(coroutineScope = this) - - private val cardsSummaryFlow = tinderGovCardDetailsLoader.cardsSummaryFlow - .shareInBackground() - - private val cardsAmountFlow = tinderGovCardDetailsLoader.cardsAmountFlow - .shareInBackground() - private val topCardIndex = MutableStateFlow(0) - private val sortedReferendaFlow = MutableStateFlow(listOf()) + private val sortedReferendaFlow = MutableStateFlow(listOf()) - private val basketFlow = interactor.observeTinderGovBasket() + private val basketFlow = basketInteractor.observeTinderGovBasket() .map { it.associateBy { it.referendumId } } .shareInBackground() - val cardsFlow = combine( - sortedReferendaFlow, - cardsSummaryFlow, - cardsAmountFlow, - ::mapCards - ).shareInBackground() + val cardsFlow = sortedReferendaFlow.map { mapCards(it) } val retryReferendumInfoLoadingAction = actionAwaitableMixinFactory.confirmingOrDenyingAction() @@ -102,12 +91,8 @@ class TinderGovCardsViewModel( private val _resetCards = MutableLiveData>() val resetCardsEvent: LiveData> = _resetCards - private val topReferendumWithDetails = combine(topCardIndex, cardsSummaryFlow, cardsAmountFlow) { topCardIndex, cardsSummary, cardsAmount -> - val referendum = sortedReferendaFlow.value.getOrNull(topCardIndex) ?: return@combine null - val cardSummary = cardsSummary.getOrDefault(referendum.id, ExtendedLoadingState.Loading) - val cardAmount = cardsAmount.getOrDefault(referendum.id, ExtendedLoadingState.Loading) - - CardWithDetails(referendum, cardSummary, cardAmount) + private val topReferendumWithDetails = combine(topCardIndex, sortedReferendaFlow) { topCardIndex, referenda -> + referenda.getOrNull(topCardIndex) ?: return@combine null } .distinctUntilChanged() .shareInBackground() @@ -116,11 +101,7 @@ class TinderGovCardsViewModel( val manageVotingPowerAvailable = topReferendumWithDetails.map { it != null } - val isCardDraggingAvailable = combine(isVotingInProgress, topReferendumWithDetails.filterNotNull()) { isVotingInProgress, cardWithDetails -> - val summaryLoaded = cardWithDetails.summary.isLoaded() - val amountLoaded = cardWithDetails.summary.isLoaded() - !isVotingInProgress && summaryLoaded && amountLoaded - } + val isCardDraggingAvailable = isVotingInProgress.map { !it } val basketModelFlow = basketFlow .map { items -> mapBasketModel(items.values.toList()) } @@ -157,8 +138,6 @@ class TinderGovCardsViewModel( init { observeReferendaAndAddToCards() - setupReferendumRetryAction() - loadFirstCards() } @@ -178,15 +157,6 @@ class TinderGovCardsViewModel( fun onCardAppeared(position: Int) { topCardIndex.value = position - - // Get 3 first referenda to load content for - val referenda = sortedReferendaFlow.value.safeSubList(position, position + CARD_STACK_SIZE) - - // Load summary and amount for each referendum - referenda.forEach { - tinderGovCardDetailsLoader.loadSummary(it) - tinderGovCardDetailsLoader.loadAmount(it) - } } fun onBasketClicked() { @@ -200,7 +170,7 @@ class TinderGovCardsViewModel( fun editVotingPowerClicked() { launch { val topReferendum = topReferendumWithDetails.first() - val topReferendumId = topReferendum?.referendum?.id?.value ?: return@launch + val topReferendumId = topReferendum?.id?.value ?: return@launch val request = TinderGovVoteRequester.Request(topReferendumId) tinderGovVoteRequester.openRequest(request) } @@ -241,7 +211,7 @@ class TinderGovCardsViewModel( } if (isSufficientAmount) { - interactor.addItemToBasket(referendum.id, voteType) + basketInteractor.addItemToBasket(referendum.id, voteType) checkAllReferendaWasVotedAndOpenBasket() } else { _rewindCardEvent.sendEvent() @@ -277,7 +247,7 @@ class TinderGovCardsViewModel( } private suspend fun checkAllReferendaWasVotedAndOpenBasket() { - val basket = interactor.getTinderGovBasket() + val basket = basketInteractor.getTinderGovBasket() .associateBy { it.referendumId } val referenda = sortedReferendaFlow.value @@ -288,67 +258,24 @@ class TinderGovCardsViewModel( } private fun mapReferendumToUi( - referendumPreview: ReferendumPreview, - summary: ExtendedLoadingState?, - amount: ExtendedLoadingState?, + referendumPreview: CardWithDetails, backgroundRes: Int ): TinderGovCardRvItem { return TinderGovCardRvItem( referendumPreview.id, - summary = summary?.map { mapSummaryToUi(it, referendumPreview) }.orLoading(), - requestedAmount = amount.orLoading(), + summary = cardsMarkdown.toMarkdown(referendumPreview.summary), + requestedAmount = referendumPreview.amount, backgroundRes = backgroundRes, ) } - private fun mapSummaryToUi(summary: String?, referendumPreview: ReferendumPreview): String { - return if (summary.isNullOrBlank()) { - referendumFormatter.formatReferendumName(referendumPreview) - } else { - summary - } - } - - private fun setupReferendumRetryAction() { - topReferendumWithDetails - .filterNotNull() - .map { - val isLoadingError = it.summary.isError() || it.amount.isError() - it.referendum to isLoadingError - } - .distinctUntilChangedBy { (referendum, isLoadingError) -> referendum.id to isLoadingError } - .onEach { (referendum, isLoadingError) -> - if (isLoadingError) { - showRetryDialog(referendum) - } - }.launchIn(this) - } - - private suspend fun showRetryDialog(referendum: ReferendumPreview) { - val retryConfirmed = retryReferendumInfoLoadingAction.awaitAction(Unit) - if (retryConfirmed) { - reloadDetailsForReferendum(referendum) - } else { - skipCard() - } - } - - private fun reloadDetailsForReferendum(referendum: ReferendumPreview) = launch { - tinderGovCardDetailsLoader.reloadSummary(referendum) - tinderGovCardDetailsLoader.reloadAmount(referendum) - } - private fun mapCards( - sortedReferenda: List, - summaries: Map>, - amounts: Map> + sortedReferenda: List ): List { return sortedReferenda.mapIndexed { index, referendum -> val backgroundRes = cardsBackground[index % cardsBackground.size] - val summary = summaries[referendum.id] - val amount = amounts[referendum.id] - mapReferendumToUi(referendum, summary, amount, backgroundRes) + mapReferendumToUi(referendum, backgroundRes) } } @@ -364,12 +291,38 @@ class TinderGovCardsViewModel( private fun observeReferendaAndAddToCards() { combine(interactor.observeReferendaAvailableToVote(this), basketFlow) { referenda, basket -> - ReferendaWithBasket(referenda, basket) + val referendaIds = referenda.map { it.id } + val summaries = referendaSummaryInteractor.getReferendaSummaries(referendaIds, viewModelScope) + val amounts = referenda.associate { it.id to it.getAmountModel() } + + val referendaCards = referenda.mapToCardWithDetails(summaries, amounts) + .filterNotNull() + + ReferendaWithBasket(referendaCards, basket) } .addNewReferendaToCards() .launchIn(this) } + private fun List.mapToCardWithDetails( + summaries: Map, + amounts: Map + ): List { + return map { + val summary = summaries[it.id] ?: return@map null + + CardWithDetails(it.id, summary, amounts[it.id]) + } + } + + private suspend fun ReferendumPreview.getAmountModel(): AmountModel? { + val treasuryRequest = interactor.getReferendumAmount(this) + return treasuryRequest?.let { + val token = tokenUseCase.getToken(treasuryRequest.chainAsset.fullId) + mapAmountToAmountModel(treasuryRequest.amount, token) + } + } + private fun Flow.addNewReferendaToCards(): Flow { return onEachWithPrevious { old, new -> val oldBasket = old?.basket.orEmpty() @@ -414,35 +367,3 @@ class TinderGovCardsViewModel( ) } } - -private class ReferendaWithBasket( - val referenda: List, - val basket: Map -) - -private class CardWithDetails( - val referendum: ReferendumPreview, - val summary: ExtendedLoadingState, - val amount: ExtendedLoadingState -) { - - override fun equals(other: Any?): Boolean { - return other is CardWithDetails && - referendum.id == other.referendum.id && - summary == other.summary && - amount == other.amount - } -} - -private class ReferendaCounterModel( - val itemsInBasket: Int, - val referendaSize: Int -) { - - val remainingReferendaToVote: Int - get() = referendaSize - itemsInBasket - - fun hasReferendaToVote(): Boolean { - return referendaSize > itemsInBasket - } -} diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/cards/adapter/TinderGovCardRvItem.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/cards/adapter/TinderGovCardRvItem.kt index a8e87121d3..c047e5f116 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/cards/adapter/TinderGovCardRvItem.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/cards/adapter/TinderGovCardRvItem.kt @@ -1,13 +1,12 @@ package io.novafoundation.nova.feature_governance_impl.presentation.tindergov.cards.adapter import androidx.annotation.DrawableRes -import io.novafoundation.nova.common.domain.ExtendedLoadingState import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.ReferendumId import io.novafoundation.nova.feature_wallet_api.presentation.model.AmountModel data class TinderGovCardRvItem( val id: ReferendumId, - val summary: ExtendedLoadingState, - val requestedAmount: ExtendedLoadingState, + val summary: CharSequence?, + val requestedAmount: AmountModel?, @DrawableRes val backgroundRes: Int ) diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/cards/adapter/TinderGovCardsAdapter.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/cards/adapter/TinderGovCardsAdapter.kt index 23e729dab4..7b48c4e342 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/cards/adapter/TinderGovCardsAdapter.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/cards/adapter/TinderGovCardsAdapter.kt @@ -2,12 +2,9 @@ package io.novafoundation.nova.feature_governance_impl.presentation.tindergov.ca import android.view.View import android.view.ViewGroup -import androidx.core.view.isVisible import androidx.lifecycle.LifecycleOwner import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter -import io.novafoundation.nova.common.domain.dataOrNull -import io.novafoundation.nova.common.domain.isLoadingOrError import io.novafoundation.nova.common.list.GroupedListHolder import io.novafoundation.nova.common.utils.inflateChild import io.novafoundation.nova.common.utils.letOrHide @@ -15,12 +12,9 @@ import io.novafoundation.nova.common.utils.setTextOrHide import io.novafoundation.nova.feature_governance_impl.R import io.novafoundation.nova.feature_governance_impl.presentation.tindergov.cards.adapter.TinderGovCardsAdapter.Handler import kotlinx.android.synthetic.main.item_tinder_gov_card.view.itemTinderGovCardAmountContainer -import kotlinx.android.synthetic.main.item_tinder_gov_card.view.itemTinderGovCardAmountShimmering -import kotlinx.android.synthetic.main.item_tinder_gov_card.view.itemTinderGovCardDivider import kotlinx.android.synthetic.main.item_tinder_gov_card.view.itemTinderGovCardRequestedAmount import kotlinx.android.synthetic.main.item_tinder_gov_card.view.itemTinderGovCardRequestedFiat import kotlinx.android.synthetic.main.item_tinder_gov_card.view.itemTinderGovCardSummary -import kotlinx.android.synthetic.main.item_tinder_gov_card.view.itemTinderGovCardSummaryShimmering import kotlinx.android.synthetic.main.item_tinder_gov_card.view.tinderGovCardContainer import kotlinx.android.synthetic.main.item_tinder_gov_card.view.tinderGovCardReadMore @@ -53,16 +47,12 @@ class TinderGovCardViewHolder( } fun bind(item: TinderGovCardRvItem) { - itemView.itemTinderGovCardSummary.setTextOrHide(item.summary.dataOrNull) - itemView.itemTinderGovCardSummaryShimmering.isVisible = item.summary.isLoadingOrError() - - itemView.itemTinderGovCardAmountContainer.letOrHide(item.requestedAmount.dataOrNull) { + itemView.itemTinderGovCardSummary.text = item.summary + itemView.itemTinderGovCardAmountContainer.letOrHide(item.requestedAmount) { itemView.itemTinderGovCardRequestedAmount.setTextOrHide(it.token) itemView.itemTinderGovCardRequestedFiat.setTextOrHide(it.fiat) } - itemView.itemTinderGovCardDivider.isVisible = item.requestedAmount.isLoadingOrError() || item.requestedAmount.dataOrNull != null - itemView.itemTinderGovCardAmountShimmering.isVisible = item.requestedAmount.isLoadingOrError() itemView.tinderGovCardContainer.setBackgroundResource(item.backgroundRes) itemView.tinderGovCardReadMore.setOnClickListener { handler.onReadMoreClicked(item) } diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/cards/di/TinderGovCardsModule.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/cards/di/TinderGovCardsModule.kt index 49814cf4f9..d38910c19a 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/cards/di/TinderGovCardsModule.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/cards/di/TinderGovCardsModule.kt @@ -1,20 +1,25 @@ package io.novafoundation.nova.feature_governance_impl.presentation.tindergov.cards.di +import android.content.Context import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import dagger.Module import dagger.Provides import dagger.multibindings.IntoMap +import io.noties.markwon.Markwon +import io.novafoundation.nova.common.R +import io.novafoundation.nova.common.di.scope.ScreenScope import io.novafoundation.nova.common.di.viewmodel.ViewModelKey import io.novafoundation.nova.common.di.viewmodel.ViewModelModule import io.novafoundation.nova.common.mixin.actionAwaitable.ActionAwaitableMixin import io.novafoundation.nova.common.resources.ResourceManager +import io.novafoundation.nova.common.utils.markdown.BoldStylePlugin +import io.novafoundation.nova.feature_governance_api.domain.referendum.summary.ReferendaSummaryInteractor +import io.novafoundation.nova.feature_governance_api.domain.tindergov.TinderGovBasketInteractor import io.novafoundation.nova.feature_governance_api.domain.tindergov.TinderGovInteractor import io.novafoundation.nova.feature_governance_impl.presentation.GovernanceRouter -import io.novafoundation.nova.feature_governance_impl.presentation.referenda.common.ReferendumFormatter import io.novafoundation.nova.feature_governance_impl.presentation.referenda.vote.setup.tindergov.TinderGovVoteCommunicator -import io.novafoundation.nova.feature_governance_impl.presentation.tindergov.cards.TinderGovCardsDetailsLoaderFactory import io.novafoundation.nova.feature_governance_impl.presentation.tindergov.cards.TinderGovCardsViewModel import io.novafoundation.nova.feature_wallet_api.domain.AssetUseCase import io.novafoundation.nova.feature_wallet_api.domain.TokenUseCase @@ -23,14 +28,11 @@ import io.novafoundation.nova.feature_wallet_api.domain.TokenUseCase class TinderGovCardsModule { @Provides - fun provideTinderGovCardsDataHelper( - interactor: TinderGovInteractor, - tokenUseCase: TokenUseCase, - ): TinderGovCardsDetailsLoaderFactory { - return TinderGovCardsDetailsLoaderFactory( - interactor, - tokenUseCase - ) + @ScreenScope + fun provideMarkwon(context: Context): Markwon { + return Markwon.builder(context) + .usePlugin(BoldStylePlugin(context, R.font.public_sans_semi_bold, R.color.text_primary)) + .build() } @Provides @@ -39,22 +41,26 @@ class TinderGovCardsModule { fun provideViewModel( router: GovernanceRouter, tinderGovInteractor: TinderGovInteractor, - tinderGovCardDetailsLoaderFactory: TinderGovCardsDetailsLoaderFactory, - referendumFormatter: ReferendumFormatter, actionAwaitableMixinFactory: ActionAwaitableMixin.Factory, tinderGovVoteCommunicator: TinderGovVoteCommunicator, resourceManager: ResourceManager, - assetUseCase: AssetUseCase + assetUseCase: AssetUseCase, + referendaSummaryInteractor: ReferendaSummaryInteractor, + tokenUseCase: TokenUseCase, + basketInteractor: TinderGovBasketInteractor, + markwon: Markwon ): ViewModel { return TinderGovCardsViewModel( - router, - tinderGovCardDetailsLoaderFactory, - tinderGovInteractor, - referendumFormatter, - actionAwaitableMixinFactory, - tinderGovVoteCommunicator, - assetUseCase, - resourceManager + router = router, + interactor = tinderGovInteractor, + basketInteractor = basketInteractor, + actionAwaitableMixinFactory = actionAwaitableMixinFactory, + tinderGovVoteRequester = tinderGovVoteCommunicator, + assetUseCase = assetUseCase, + resourceManager = resourceManager, + referendaSummaryInteractor = referendaSummaryInteractor, + tokenUseCase = tokenUseCase, + cardsMarkdown = markwon ) } diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/cards/model/CardWithDetails.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/cards/model/CardWithDetails.kt new file mode 100644 index 0000000000..d8f1e92005 --- /dev/null +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/cards/model/CardWithDetails.kt @@ -0,0 +1,25 @@ +package io.novafoundation.nova.feature_governance_impl.presentation.tindergov.cards.model + +import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.ReferendumId +import io.novafoundation.nova.feature_wallet_api.presentation.model.AmountModel + +class CardWithDetails( + val id: ReferendumId, + val summary: String, + val amount: AmountModel? +) { + + override fun equals(other: Any?): Boolean { + return other is CardWithDetails && + id == other.id && + summary == other.summary && + amount == other.amount + } + + override fun hashCode(): Int { + var result = id.hashCode() + result = 31 * result + summary.hashCode() + result = 31 * result + amount.hashCode() + return result + } +} diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/cards/model/ReferendaCounterModel.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/cards/model/ReferendaCounterModel.kt new file mode 100644 index 0000000000..d03894f261 --- /dev/null +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/cards/model/ReferendaCounterModel.kt @@ -0,0 +1,14 @@ +package io.novafoundation.nova.feature_governance_impl.presentation.tindergov.cards.model + +class ReferendaCounterModel( + val itemsInBasket: Int, + val referendaSize: Int +) { + + val remainingReferendaToVote: Int + get() = referendaSize - itemsInBasket + + fun hasReferendaToVote(): Boolean { + return referendaSize > itemsInBasket + } +} diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/cards/model/ReferendaWithBasket.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/cards/model/ReferendaWithBasket.kt new file mode 100644 index 0000000000..12dfcb7d11 --- /dev/null +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/cards/model/ReferendaWithBasket.kt @@ -0,0 +1,9 @@ +package io.novafoundation.nova.feature_governance_impl.presentation.tindergov.cards.model + +import io.novafoundation.nova.feature_governance_api.data.model.TinderGovBasketItem +import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.ReferendumId + +data class ReferendaWithBasket( + val referenda: List, + val basket: Map +) diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/confirm/ConfirmTinderGovVoteViewModel.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/confirm/ConfirmTinderGovVoteViewModel.kt index 9d9ade9730..bb6b2d48a7 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/confirm/ConfirmTinderGovVoteViewModel.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/confirm/ConfirmTinderGovVoteViewModel.kt @@ -13,6 +13,7 @@ import io.novafoundation.nova.feature_account_api.presenatation.actions.External import io.novafoundation.nova.feature_governance_api.data.model.TinderGovBasketItem import io.novafoundation.nova.feature_governance_api.data.model.accountVote import io.novafoundation.nova.feature_governance_api.domain.referendum.vote.VoteReferendumInteractor +import io.novafoundation.nova.feature_governance_api.domain.tindergov.TinderGovBasketInteractor import io.novafoundation.nova.feature_governance_api.domain.tindergov.TinderGovInteractor import io.novafoundation.nova.feature_governance_impl.R import io.novafoundation.nova.feature_governance_impl.data.GovernanceSharedState @@ -55,6 +56,7 @@ class ConfirmTinderGovVoteViewModel( private val resourceManager: ResourceManager, private val locksChangeFormatter: LocksChangeFormatter, private val tinderGovInteractor: TinderGovInteractor, + private val tinderGovBasketInteractor: TinderGovBasketInteractor, partialRetriableMixinFactory: PartialRetriableMixin.Factory, ) : ConfirmVoteViewModel( router, @@ -69,7 +71,7 @@ class ConfirmTinderGovVoteViewModel( validationExecutor ) { - private val basketFlow = tinderGovInteractor.observeTinderGovBasket() + private val basketFlow = tinderGovBasketInteractor.observeTinderGovBasket() .map { it.associateBy { it.referendumId } } .shareInBackground() @@ -157,7 +159,7 @@ class ConfirmTinderGovVoteViewModel( awaitVotedReferendaStateUpdate(basket) showMessage(resourceManager.getString(R.string.swipe_gov_convirm_votes_success_message, basket.size)) - tinderGovInteractor.clearBasket() + tinderGovBasketInteractor.clearBasket() router.backToTinderGovCards() } diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/confirm/di/ConfirmTinderGovVoteModule.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/confirm/di/ConfirmTinderGovVoteModule.kt index 33224fc264..3efb4111eb 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/confirm/di/ConfirmTinderGovVoteModule.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/confirm/di/ConfirmTinderGovVoteModule.kt @@ -18,6 +18,7 @@ import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.W import io.novafoundation.nova.feature_account_api.presenatation.actions.ExternalActions import io.novafoundation.nova.feature_governance_api.data.source.GovernanceSourceRegistry import io.novafoundation.nova.feature_governance_api.domain.referendum.vote.VoteReferendumInteractor +import io.novafoundation.nova.feature_governance_api.domain.tindergov.TinderGovBasketInteractor import io.novafoundation.nova.feature_governance_api.domain.tindergov.TinderGovInteractor import io.novafoundation.nova.feature_governance_impl.data.GovernanceSharedState import io.novafoundation.nova.feature_governance_impl.domain.referendum.vote.validations.tindergov.VoteTinderGovValidationSystem @@ -57,6 +58,7 @@ class ConfirmTinderGovVoteModule { feeLoaderMixinFactory: FeeLoaderMixin.Factory, locksChangeFormatter: LocksChangeFormatter, tinderGovInteractor: TinderGovInteractor, + tinderGovBasketInteractor: TinderGovBasketInteractor, partialRetriableMixinFactory: PartialRetriableMixin.Factory, ): ViewModel { return ConfirmTinderGovVoteViewModel( @@ -75,6 +77,7 @@ class ConfirmTinderGovVoteModule { resourceManager = resourceManager, locksChangeFormatter = locksChangeFormatter, tinderGovInteractor = tinderGovInteractor, + tinderGovBasketInteractor = tinderGovBasketInteractor, partialRetriableMixinFactory = partialRetriableMixinFactory ) } diff --git a/feature-governance-impl/src/main/res/layout/item_tinder_gov_card.xml b/feature-governance-impl/src/main/res/layout/item_tinder_gov_card.xml index d98308eb03..886ed927cd 100644 --- a/feature-governance-impl/src/main/res/layout/item_tinder_gov_card.xml +++ b/feature-governance-impl/src/main/res/layout/item_tinder_gov_card.xml @@ -25,39 +25,12 @@ android:autoSizeMinTextSize="16sp" android:autoSizeTextType="uniform" android:ellipsize="end" - android:textColor="@color/text_primary" - android:visibility="gone" + android:textColor="@color/tinder_gov_banner_secondary_text" app:layout_constraintBottom_toTopOf="@+id/guideline" app:layout_constraintTop_toTopOf="parent" tools:text="Sovereign Nature proposes DOTphin Event Multipass with WalletConnect integration and dynamic NFTs by Unique Network for Polkadot events, aiming for unified proof of attendance and immersive engagement." tools:visibility="visible" /> - - - - - - - - - - - - - + + - - - - - - - - - Date: Fri, 4 Oct 2024 13:54:17 +0200 Subject: [PATCH 02/23] Changed api to chains api --- feature-governance-impl/build.gradle | 4 ---- .../summary/v2/ReferendumSummaryApi.kt | 7 +++++-- .../summary/v2/ReferendumSummaryDataSource.kt | 16 ++++++++++++---- .../di/modules/screens/TinderGovModule.kt | 2 +- .../details/ReferendumDetailsRepository.kt | 8 ++++---- .../summary/ReferendaSummarySharedComputation.kt | 2 +- .../summary/ReferendumSummaryInteractor.kt | 9 ++++++++- .../chain/mappers/LocalToDomainChainMapper.kt | 4 +--- .../mappers/utils/SummaryReferendaParameters.kt | 3 --- .../runtime/multiNetwork/chain/model/Chain.kt | 2 +- 10 files changed, 33 insertions(+), 24 deletions(-) delete mode 100644 runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/chain/mappers/utils/SummaryReferendaParameters.kt diff --git a/feature-governance-impl/build.gradle b/feature-governance-impl/build.gradle index 1762eb3543..8786c21eaa 100644 --- a/feature-governance-impl/build.gradle +++ b/feature-governance-impl/build.gradle @@ -18,8 +18,6 @@ android { buildConfigField "String", "GOVERNANCE_DAPPS_URL", "\"https://raw.githubusercontent.com/novasamatech/nova-utils/master/governance/v2/dapps_dev.json\"" buildConfigField "String", "DELEGATION_TUTORIAL_URL", "\"https://docs.novawallet.io/nova-wallet-wiki/governance/add-delegate-information\"" - - buildConfigField "String", "OPENGOV_API_URL", "\"https://opengov-backend-dev.novasama-tech.org\"" } buildTypes { @@ -28,8 +26,6 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' buildConfigField "String", "GOVERNANCE_DAPPS_URL", "\"https://raw.githubusercontent.com/novasamatech/nova-utils/master/governance/v2/dapps.json\"" - - buildConfigField "String", "OPENGOV_API_URL", "\"https://opengov-backend.novasama-tech.org\"" } } diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/referendum/summary/v2/ReferendumSummaryApi.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/referendum/summary/v2/ReferendumSummaryApi.kt index 82ebd4da05..6581e1c2eb 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/referendum/summary/v2/ReferendumSummaryApi.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/referendum/summary/v2/ReferendumSummaryApi.kt @@ -5,16 +5,19 @@ import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.s import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.summary.v2.response.ReferendumSummaryResponse import retrofit2.http.Body import retrofit2.http.POST +import retrofit2.http.Url interface ReferendumSummaryApi { - @POST("/not-secure/api/v1/referendum-summaries/single") + @POST suspend fun getReferendumSummary( + @Url url: String, @Body body: ReferendumSummaryRequest ): ReferendumSummaryResponse - @POST("/not-secure/api/v1/referendum-summaries/list") + @POST suspend fun getReferendumSummaries( + @Url url: String, @Body body: ReferendumSummariesRequest ): List } diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/referendum/summary/v2/ReferendumSummaryDataSource.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/referendum/summary/v2/ReferendumSummaryDataSource.kt index 6296f902eb..7cf60028ff 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/referendum/summary/v2/ReferendumSummaryDataSource.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/referendum/summary/v2/ReferendumSummaryDataSource.kt @@ -3,21 +3,26 @@ package io.novafoundation.nova.feature_governance_impl.data.offchain.referendum. import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.ReferendumId import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.summary.v2.request.ReferendumSummariesRequest import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.summary.v2.request.ReferendumSummaryRequest +import io.novafoundation.nova.runtime.ext.isSwapSupported +import io.novafoundation.nova.runtime.ext.summaryApiOrNull import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain interface ReferendumSummaryDataSource { - suspend fun loadSummary(chain: Chain, id: ReferendumId, languageCode: String): String + suspend fun loadSummary(chain: Chain, id: ReferendumId, languageCode: String): String? - suspend fun loadSummaries(chain: Chain, ids: List, languageCode: String): Map + suspend fun loadSummaries(chain: Chain, ids: List, languageCode: String): Map? } class RealReferendumSummaryDataSource( val api: ReferendumSummaryApi ) : ReferendumSummaryDataSource { - override suspend fun loadSummary(chain: Chain, id: ReferendumId, languageCode: String): String { + override suspend fun loadSummary(chain: Chain, id: ReferendumId, languageCode: String): String? { + val summaryApi = chain.summaryApiOrNull() ?: return null + val response = api.getReferendumSummary( + summaryApi.url, ReferendumSummaryRequest( chainId = chain.id, languageIsoCode = languageCode, @@ -28,8 +33,11 @@ class RealReferendumSummaryDataSource( return response.summary } - override suspend fun loadSummaries(chain: Chain, ids: List, languageCode: String): Map { + override suspend fun loadSummaries(chain: Chain, ids: List, languageCode: String): Map? { + val summaryApi = chain.summaryApiOrNull() ?: return null + val response = api.getReferendumSummaries( + summaryApi.url, ReferendumSummariesRequest( chainId = chain.id, languageIsoCode = languageCode, diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/di/modules/screens/TinderGovModule.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/di/modules/screens/TinderGovModule.kt index f30467b97c..1ae9ad234a 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/di/modules/screens/TinderGovModule.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/di/modules/screens/TinderGovModule.kt @@ -36,7 +36,7 @@ class TinderGovModule { @Provides @FeatureScope fun provideReferendumSummaryApi(apiCreator: NetworkApiCreator): ReferendumSummaryApi { - return apiCreator.create(ReferendumSummaryApi::class.java, customBaseUrl = BuildConfig.OPENGOV_API_URL) + return apiCreator.create(ReferendumSummaryApi::class.java) } @Provides diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/details/ReferendumDetailsRepository.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/details/ReferendumDetailsRepository.kt index 2aa19932e3..f3f28aef4b 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/details/ReferendumDetailsRepository.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/details/ReferendumDetailsRepository.kt @@ -7,20 +7,20 @@ import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain interface ReferendumDetailsRepository { - suspend fun loadSummary(chain: Chain, id: ReferendumId, selectedLanguage: Language): String + suspend fun loadSummary(chain: Chain, id: ReferendumId, selectedLanguage: Language): String? - suspend fun loadSummaries(chain: Chain, ids: List, selectedLanguage: Language): Map + suspend fun loadSummaries(chain: Chain, ids: List, selectedLanguage: Language): Map? } class RealReferendumDetailsRepository( private val referendumSummaryDataSource: ReferendumSummaryDataSource ) : ReferendumDetailsRepository { - override suspend fun loadSummary(chain: Chain, id: ReferendumId, selectedLanguage: Language): String { + override suspend fun loadSummary(chain: Chain, id: ReferendumId, selectedLanguage: Language): String? { return referendumSummaryDataSource.loadSummary(chain, id, selectedLanguage.iso639Code) } - override suspend fun loadSummaries(chain: Chain, ids: List, selectedLanguage: Language): Map { + override suspend fun loadSummaries(chain: Chain, ids: List, selectedLanguage: Language): Map? { return referendumSummaryDataSource.loadSummaries(chain, ids, selectedLanguage.iso639Code) } } diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/summary/ReferendaSummarySharedComputation.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/summary/ReferendaSummarySharedComputation.kt index af77cac537..7d7876549f 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/summary/ReferendaSummarySharedComputation.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/summary/ReferendaSummarySharedComputation.kt @@ -17,7 +17,7 @@ class ReferendaSummarySharedComputation( governanceOption: SupportedGovernanceOption, referendaIds: List, scope: CoroutineScope - ): Map { + ): Map? { val chainId = governanceOption.assetWithChain.chain.id val referendaSet = referendaIds.toSet() val selectedLanguage = accountRepository.selectedLanguage() diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/summary/ReferendumSummaryInteractor.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/summary/ReferendumSummaryInteractor.kt index bed3e023f0..d68ec94fe9 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/summary/ReferendumSummaryInteractor.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/summary/ReferendumSummaryInteractor.kt @@ -12,6 +12,13 @@ class RealReferendaSummaryInteractor( ) : ReferendaSummaryInteractor { override suspend fun getReferendaSummaries(ids: List, coroutineScope: CoroutineScope): Map { - return referendaSummarySharedComputation.summaries(governanceSharedState.selectedOption(), ids, coroutineScope) + return runCatching { + referendaSummarySharedComputation.summaries( + governanceSharedState.selectedOption(), + ids, + coroutineScope + ) + }.getOrNull() + .orEmpty() } } diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/chain/mappers/LocalToDomainChainMapper.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/chain/mappers/LocalToDomainChainMapper.kt index a2167d4122..aa89dd769a 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/chain/mappers/LocalToDomainChainMapper.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/chain/mappers/LocalToDomainChainMapper.kt @@ -24,7 +24,6 @@ import io.novafoundation.nova.core_db.model.chain.NodeSelectionPreferencesLocal import io.novafoundation.nova.runtime.multiNetwork.chain.mappers.utils.EVM_TRANSFER_PARAMETER import io.novafoundation.nova.runtime.multiNetwork.chain.mappers.utils.GovernanceReferendaParameters import io.novafoundation.nova.runtime.multiNetwork.chain.mappers.utils.SUBSTRATE_TRANSFER_PARAMETER -import io.novafoundation.nova.runtime.multiNetwork.chain.mappers.utils.SummaryReferendaParameters import io.novafoundation.nova.runtime.multiNetwork.chain.mappers.utils.TransferParameters import io.novafoundation.nova.runtime.multiNetwork.chain.model.BuyProviderArguments import io.novafoundation.nova.runtime.multiNetwork.chain.model.BuyProviderId @@ -155,8 +154,7 @@ private fun mapExternalApiLocalToExternalApi(externalApiLocal: ChainExternalApiL } ApiType.REFERENDUM_SUMMARY -> { - val parameters = externalApiLocal.parsedParameters(gson) - ExternalApi.ReferendumSummary(externalApiLocal.url, parameters?.network) + ExternalApi.ReferendumSummary(externalApiLocal.url) } ApiType.UNKNOWN -> null diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/chain/mappers/utils/SummaryReferendaParameters.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/chain/mappers/utils/SummaryReferendaParameters.kt deleted file mode 100644 index d6a776afcc..0000000000 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/chain/mappers/utils/SummaryReferendaParameters.kt +++ /dev/null @@ -1,3 +0,0 @@ -package io.novafoundation.nova.runtime.multiNetwork.chain.mappers.utils - -class SummaryReferendaParameters(val network: String?) diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/chain/model/Chain.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/chain/model/Chain.kt index 57e4d690aa..cdaf7acf91 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/chain/model/Chain.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/chain/model/Chain.kt @@ -195,7 +195,7 @@ data class Chain( data class GovernanceDelegations(override val url: String) : ExternalApi() - data class ReferendumSummary(override val url: String, val network: String?) : ExternalApi() + data class ReferendumSummary(override val url: String) : ExternalApi() } enum class Governance { From 97dcc5cd70bbd764156630efd42cb6303ca2535f Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Fri, 4 Oct 2024 13:55:17 +0200 Subject: [PATCH 03/23] Run ktlint --- .../referendum/summary/v2/ReferendumSummaryDataSource.kt | 1 - .../di/modules/screens/TinderGovModule.kt | 1 - 2 files changed, 2 deletions(-) diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/referendum/summary/v2/ReferendumSummaryDataSource.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/referendum/summary/v2/ReferendumSummaryDataSource.kt index 7cf60028ff..8bcded9e8d 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/referendum/summary/v2/ReferendumSummaryDataSource.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/referendum/summary/v2/ReferendumSummaryDataSource.kt @@ -3,7 +3,6 @@ package io.novafoundation.nova.feature_governance_impl.data.offchain.referendum. import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.ReferendumId import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.summary.v2.request.ReferendumSummariesRequest import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.summary.v2.request.ReferendumSummaryRequest -import io.novafoundation.nova.runtime.ext.isSwapSupported import io.novafoundation.nova.runtime.ext.summaryApiOrNull import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/di/modules/screens/TinderGovModule.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/di/modules/screens/TinderGovModule.kt index 1ae9ad234a..0bb65f1ade 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/di/modules/screens/TinderGovModule.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/di/modules/screens/TinderGovModule.kt @@ -10,7 +10,6 @@ import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepos import io.novafoundation.nova.feature_governance_api.domain.referendum.summary.ReferendaSummaryInteractor import io.novafoundation.nova.feature_governance_api.domain.tindergov.TinderGovBasketInteractor import io.novafoundation.nova.feature_governance_api.domain.tindergov.TinderGovInteractor -import io.novafoundation.nova.feature_governance_impl.BuildConfig import io.novafoundation.nova.feature_governance_impl.data.GovernanceSharedState import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.summary.v2.RealReferendumSummaryDataSource import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.summary.v2.ReferendumSummaryApi From 0623d5a8a73d4671f0e56e2da4ebf8149d9a1d5b Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Fri, 4 Oct 2024 14:14:59 +0200 Subject: [PATCH 04/23] Update SpannableExt.kt --- .../java/io/novafoundation/nova/common/utils/SpannableExt.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/SpannableExt.kt b/common/src/main/java/io/novafoundation/nova/common/utils/SpannableExt.kt index 2e07f471bd..cd6628b004 100644 --- a/common/src/main/java/io/novafoundation/nova/common/utils/SpannableExt.kt +++ b/common/src/main/java/io/novafoundation/nova/common/utils/SpannableExt.kt @@ -91,7 +91,7 @@ fun CharSequence.formatAsSpannable(vararg args: Any): SpannedString { private fun typefaceSpanCompatV28(typeface: Typeface) = TypefaceSpan(typeface) -class CustomTypefaceSpan(private val typeface: Typeface?) : MetricAffectingSpan() { +private class CustomTypefaceSpan(private val typeface: Typeface?) : MetricAffectingSpan() { override fun updateDrawState(paint: TextPaint) { paint.typeface = typeface } From 143af64a595ed5f20a96c31807798a6e5f65eb66 Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Fri, 4 Oct 2024 14:29:59 +0200 Subject: [PATCH 05/23] Remove single referendum fetching --- .../summary/v2/ReferendumSummaryApi.kt | 7 ------- .../summary/v2/ReferendumSummaryDataSource.kt | 18 ------------------ .../v2/request/ReferendumSummaryRequest.kt | 7 ------- .../details/ReferendumDetailsRepository.kt | 6 ------ 4 files changed, 38 deletions(-) delete mode 100644 feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/referendum/summary/v2/request/ReferendumSummaryRequest.kt diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/referendum/summary/v2/ReferendumSummaryApi.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/referendum/summary/v2/ReferendumSummaryApi.kt index 6581e1c2eb..a2058632f0 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/referendum/summary/v2/ReferendumSummaryApi.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/referendum/summary/v2/ReferendumSummaryApi.kt @@ -1,7 +1,6 @@ package io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.summary.v2 import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.summary.v2.request.ReferendumSummariesRequest -import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.summary.v2.request.ReferendumSummaryRequest import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.summary.v2.response.ReferendumSummaryResponse import retrofit2.http.Body import retrofit2.http.POST @@ -9,12 +8,6 @@ import retrofit2.http.Url interface ReferendumSummaryApi { - @POST - suspend fun getReferendumSummary( - @Url url: String, - @Body body: ReferendumSummaryRequest - ): ReferendumSummaryResponse - @POST suspend fun getReferendumSummaries( @Url url: String, diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/referendum/summary/v2/ReferendumSummaryDataSource.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/referendum/summary/v2/ReferendumSummaryDataSource.kt index 8bcded9e8d..3db1a4fa60 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/referendum/summary/v2/ReferendumSummaryDataSource.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/referendum/summary/v2/ReferendumSummaryDataSource.kt @@ -2,14 +2,11 @@ package io.novafoundation.nova.feature_governance_impl.data.offchain.referendum. import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.ReferendumId import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.summary.v2.request.ReferendumSummariesRequest -import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.summary.v2.request.ReferendumSummaryRequest import io.novafoundation.nova.runtime.ext.summaryApiOrNull import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain interface ReferendumSummaryDataSource { - suspend fun loadSummary(chain: Chain, id: ReferendumId, languageCode: String): String? - suspend fun loadSummaries(chain: Chain, ids: List, languageCode: String): Map? } @@ -17,21 +14,6 @@ class RealReferendumSummaryDataSource( val api: ReferendumSummaryApi ) : ReferendumSummaryDataSource { - override suspend fun loadSummary(chain: Chain, id: ReferendumId, languageCode: String): String? { - val summaryApi = chain.summaryApiOrNull() ?: return null - - val response = api.getReferendumSummary( - summaryApi.url, - ReferendumSummaryRequest( - chainId = chain.id, - languageIsoCode = languageCode, - referendumId = id.value.toString() - ) - ) - - return response.summary - } - override suspend fun loadSummaries(chain: Chain, ids: List, languageCode: String): Map? { val summaryApi = chain.summaryApiOrNull() ?: return null diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/referendum/summary/v2/request/ReferendumSummaryRequest.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/referendum/summary/v2/request/ReferendumSummaryRequest.kt deleted file mode 100644 index a25e2113fb..0000000000 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/referendum/summary/v2/request/ReferendumSummaryRequest.kt +++ /dev/null @@ -1,7 +0,0 @@ -package io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.summary.v2.request - -class ReferendumSummaryRequest( - val chainId: String, - val languageIsoCode: String, - val referendumId: String -) diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/details/ReferendumDetailsRepository.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/details/ReferendumDetailsRepository.kt index f3f28aef4b..92d366a975 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/details/ReferendumDetailsRepository.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/details/ReferendumDetailsRepository.kt @@ -7,8 +7,6 @@ import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain interface ReferendumDetailsRepository { - suspend fun loadSummary(chain: Chain, id: ReferendumId, selectedLanguage: Language): String? - suspend fun loadSummaries(chain: Chain, ids: List, selectedLanguage: Language): Map? } @@ -16,10 +14,6 @@ class RealReferendumDetailsRepository( private val referendumSummaryDataSource: ReferendumSummaryDataSource ) : ReferendumDetailsRepository { - override suspend fun loadSummary(chain: Chain, id: ReferendumId, selectedLanguage: Language): String? { - return referendumSummaryDataSource.loadSummary(chain, id, selectedLanguage.iso639Code) - } - override suspend fun loadSummaries(chain: Chain, ids: List, selectedLanguage: Language): Map? { return referendumSummaryDataSource.loadSummaries(chain, ids, selectedLanguage.iso639Code) } From b1722e4c788278c977c5dcf308dd7c42b3d29612 Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Fri, 4 Oct 2024 16:36:18 +0200 Subject: [PATCH 06/23] Fixed pr notes --- .../summary/ReferendaSummaryInteractor.kt | 9 ----- .../tindergov/TinderGovBasketInteractor.kt | 28 --------------- .../domain/tindergov/TinderGovInteractor.kt | 36 ------------------- .../di/modules/screens/TinderGovModule.kt | 6 ++-- ...ractor.kt => TinderGovBasketInteractor.kt} | 23 ++++++++++-- ...ovInteractor.kt => TinderGovInteractor.kt} | 28 +++++++++++++-- ...actor.kt => ReferendaSummaryInteractor.kt} | 6 +++- .../ReferendaSummarySharedComputation.kt | 4 +-- .../referenda/list/ReferendaListViewModel.kt | 14 ++++---- .../referenda/list/di/ReferendaListModule.kt | 2 +- .../tindergov/SetupTinderGovVoteViewModel.kt | 2 +- .../tindergov/di/SetupTinderGovVoteModule.kt | 2 +- .../basket/TinderGovBasketViewModel.kt | 4 +-- .../basket/di/TinderGovBasketModule.kt | 4 +-- .../cards/TinderGovCardsViewModel.kt | 25 +++++++------ .../cards/di/TinderGovCardsModule.kt | 6 ++-- .../tindergov/cards/model/CardWithDetails.kt | 19 ++-------- .../confirm/ConfirmTinderGovVoteViewModel.kt | 4 +-- .../confirm/di/ConfirmTinderGovVoteModule.kt | 4 +-- 19 files changed, 93 insertions(+), 133 deletions(-) delete mode 100644 feature-governance-api/src/main/java/io/novafoundation/nova/feature_governance_api/domain/referendum/summary/ReferendaSummaryInteractor.kt delete mode 100644 feature-governance-api/src/main/java/io/novafoundation/nova/feature_governance_api/domain/tindergov/TinderGovBasketInteractor.kt delete mode 100644 feature-governance-api/src/main/java/io/novafoundation/nova/feature_governance_api/domain/tindergov/TinderGovInteractor.kt rename feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/tindergov/{RealTinderGovBasketInteractor.kt => TinderGovBasketInteractor.kt} (89%) rename feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/tindergov/{RealTinderGovInteractor.kt => TinderGovInteractor.kt} (87%) rename feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/summary/{ReferendumSummaryInteractor.kt => ReferendaSummaryInteractor.kt} (85%) diff --git a/feature-governance-api/src/main/java/io/novafoundation/nova/feature_governance_api/domain/referendum/summary/ReferendaSummaryInteractor.kt b/feature-governance-api/src/main/java/io/novafoundation/nova/feature_governance_api/domain/referendum/summary/ReferendaSummaryInteractor.kt deleted file mode 100644 index f48bd44c41..0000000000 --- a/feature-governance-api/src/main/java/io/novafoundation/nova/feature_governance_api/domain/referendum/summary/ReferendaSummaryInteractor.kt +++ /dev/null @@ -1,9 +0,0 @@ -package io.novafoundation.nova.feature_governance_api.domain.referendum.summary - -import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.ReferendumId -import kotlinx.coroutines.CoroutineScope - -interface ReferendaSummaryInteractor { - - suspend fun getReferendaSummaries(ids: List, coroutineScope: CoroutineScope): Map -} diff --git a/feature-governance-api/src/main/java/io/novafoundation/nova/feature_governance_api/domain/tindergov/TinderGovBasketInteractor.kt b/feature-governance-api/src/main/java/io/novafoundation/nova/feature_governance_api/domain/tindergov/TinderGovBasketInteractor.kt deleted file mode 100644 index 5b50b879b4..0000000000 --- a/feature-governance-api/src/main/java/io/novafoundation/nova/feature_governance_api/domain/tindergov/TinderGovBasketInteractor.kt +++ /dev/null @@ -1,28 +0,0 @@ -package io.novafoundation.nova.feature_governance_api.domain.tindergov - -import io.novafoundation.nova.feature_governance_api.data.model.TinderGovBasketItem -import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.ReferendumId -import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.VoteType -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.Flow - -interface TinderGovBasketInteractor { - - fun observeTinderGovBasket(): Flow> - - suspend fun getTinderGovBasket(): List - - suspend fun addItemToBasket(referendumId: ReferendumId, voteType: VoteType) - - suspend fun removeReferendumFromBasket(item: TinderGovBasketItem) - - suspend fun removeBasketItems(items: Collection) - - suspend fun isBasketEmpty(): Boolean - - suspend fun clearBasket() - - suspend fun getBasketItemsToRemove(coroutineScope: CoroutineScope): List - - suspend fun awaitAllItemsVoted(coroutineScope: CoroutineScope, basket: List) -} diff --git a/feature-governance-api/src/main/java/io/novafoundation/nova/feature_governance_api/domain/tindergov/TinderGovInteractor.kt b/feature-governance-api/src/main/java/io/novafoundation/nova/feature_governance_api/domain/tindergov/TinderGovInteractor.kt deleted file mode 100644 index b39ff415cd..0000000000 --- a/feature-governance-api/src/main/java/io/novafoundation/nova/feature_governance_api/domain/tindergov/TinderGovInteractor.kt +++ /dev/null @@ -1,36 +0,0 @@ -package io.novafoundation.nova.feature_governance_api.domain.tindergov - -import io.novafoundation.nova.feature_governance_api.data.model.TinderGovBasketItem -import io.novafoundation.nova.feature_governance_api.data.model.VotingPower -import io.novafoundation.nova.feature_governance_api.domain.referendum.details.ReferendumCall -import io.novafoundation.nova.feature_governance_api.domain.referendum.list.ReferendaState -import io.novafoundation.nova.feature_governance_api.domain.referendum.list.ReferendumPreview -import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.Flow - -sealed interface VotingPowerState { - - object Empty : VotingPowerState - - class InsufficientAmount(val votingPower: VotingPower) : VotingPowerState - - class SufficientAmount(val votingPower: VotingPower) : VotingPowerState -} - -interface TinderGovInteractor { - - fun observeReferendaState(coroutineScope: CoroutineScope): Flow - - fun observeReferendaAvailableToVote(coroutineScope: CoroutineScope): Flow> - - suspend fun getReferendumAmount(referendumPreview: ReferendumPreview): ReferendumCall.TreasuryRequest? - - suspend fun setVotingPower(votingPower: VotingPower) - - suspend fun getVotingPower(metaId: Long, chainId: ChainId): VotingPower? - - suspend fun getVotingPowerState(): VotingPowerState - - suspend fun awaitAllItemsVoted(coroutineScope: CoroutineScope, basket: List) -} diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/di/modules/screens/TinderGovModule.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/di/modules/screens/TinderGovModule.kt index 0bb65f1ade..b02d0df8b5 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/di/modules/screens/TinderGovModule.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/di/modules/screens/TinderGovModule.kt @@ -7,9 +7,9 @@ import io.novafoundation.nova.common.data.network.NetworkApiCreator import io.novafoundation.nova.common.di.scope.FeatureScope import io.novafoundation.nova.core_db.dao.TinderGovDao import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository -import io.novafoundation.nova.feature_governance_api.domain.referendum.summary.ReferendaSummaryInteractor -import io.novafoundation.nova.feature_governance_api.domain.tindergov.TinderGovBasketInteractor -import io.novafoundation.nova.feature_governance_api.domain.tindergov.TinderGovInteractor +import io.novafoundation.nova.feature_governance_impl.domain.summary.ReferendaSummaryInteractor +import io.novafoundation.nova.feature_governance_impl.domain.referendum.tindergov.TinderGovBasketInteractor +import io.novafoundation.nova.feature_governance_impl.domain.referendum.tindergov.TinderGovInteractor import io.novafoundation.nova.feature_governance_impl.data.GovernanceSharedState import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.summary.v2.RealReferendumSummaryDataSource import io.novafoundation.nova.feature_governance_impl.data.offchain.referendum.summary.v2.ReferendumSummaryApi diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/tindergov/RealTinderGovBasketInteractor.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/tindergov/TinderGovBasketInteractor.kt similarity index 89% rename from feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/tindergov/RealTinderGovBasketInteractor.kt rename to feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/tindergov/TinderGovBasketInteractor.kt index 6fc41f2cac..76dd09c339 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/tindergov/RealTinderGovBasketInteractor.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/tindergov/TinderGovBasketInteractor.kt @@ -6,8 +6,6 @@ import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepos import io.novafoundation.nova.feature_governance_api.data.model.TinderGovBasketItem import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.ReferendumId import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.VoteType -import io.novafoundation.nova.feature_governance_api.domain.tindergov.TinderGovBasketInteractor -import io.novafoundation.nova.feature_governance_api.domain.tindergov.TinderGovInteractor import io.novafoundation.nova.feature_governance_impl.data.GovernanceSharedState import io.novafoundation.nova.feature_governance_impl.data.repository.tindergov.TinderGovBasketRepository import io.novafoundation.nova.feature_governance_impl.data.repository.tindergov.TinderGovVotingPowerRepository @@ -23,6 +21,27 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first +interface TinderGovBasketInteractor { + + fun observeTinderGovBasket(): Flow> + + suspend fun getTinderGovBasket(): List + + suspend fun addItemToBasket(referendumId: ReferendumId, voteType: VoteType) + + suspend fun removeReferendumFromBasket(item: TinderGovBasketItem) + + suspend fun removeBasketItems(items: Collection) + + suspend fun isBasketEmpty(): Boolean + + suspend fun clearBasket() + + suspend fun getBasketItemsToRemove(coroutineScope: CoroutineScope): List + + suspend fun awaitAllItemsVoted(coroutineScope: CoroutineScope, basket: List) +} + class RealTinderGovBasketInteractor( private val governanceSharedState: GovernanceSharedState, private val referendaSharedComputation: ReferendaSharedComputation, diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/tindergov/RealTinderGovInteractor.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/tindergov/TinderGovInteractor.kt similarity index 87% rename from feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/tindergov/RealTinderGovInteractor.kt rename to feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/tindergov/TinderGovInteractor.kt index da58e90c66..76beae00a3 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/tindergov/RealTinderGovInteractor.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/tindergov/TinderGovInteractor.kt @@ -12,8 +12,6 @@ import io.novafoundation.nova.feature_governance_api.domain.referendum.list.Refe import io.novafoundation.nova.feature_governance_api.domain.referendum.list.Voter import io.novafoundation.nova.feature_governance_api.domain.referendum.list.toCallOrNull import io.novafoundation.nova.feature_governance_api.domain.referendum.list.user -import io.novafoundation.nova.feature_governance_api.domain.tindergov.TinderGovInteractor -import io.novafoundation.nova.feature_governance_api.domain.tindergov.VotingPowerState import io.novafoundation.nova.feature_governance_impl.data.GovernanceSharedState import io.novafoundation.nova.feature_governance_impl.data.repository.tindergov.TinderGovVotingPowerRepository import io.novafoundation.nova.feature_governance_impl.domain.referendum.details.call.ReferendumPreImageParser @@ -32,6 +30,32 @@ import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map +sealed interface VotingPowerState { + + object Empty : VotingPowerState + + class InsufficientAmount(val votingPower: VotingPower) : VotingPowerState + + class SufficientAmount(val votingPower: VotingPower) : VotingPowerState +} + +interface TinderGovInteractor { + + fun observeReferendaState(coroutineScope: CoroutineScope): Flow + + fun observeReferendaAvailableToVote(coroutineScope: CoroutineScope): Flow> + + suspend fun getReferendumAmount(referendumPreview: ReferendumPreview): ReferendumCall.TreasuryRequest? + + suspend fun setVotingPower(votingPower: VotingPower) + + suspend fun getVotingPower(metaId: Long, chainId: ChainId): VotingPower? + + suspend fun getVotingPowerState(): VotingPowerState + + suspend fun awaitAllItemsVoted(coroutineScope: CoroutineScope, basket: List) +} + class RealTinderGovInteractor( private val governanceSharedState: GovernanceSharedState, private val referendaSharedComputation: ReferendaSharedComputation, diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/summary/ReferendumSummaryInteractor.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/summary/ReferendaSummaryInteractor.kt similarity index 85% rename from feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/summary/ReferendumSummaryInteractor.kt rename to feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/summary/ReferendaSummaryInteractor.kt index d68ec94fe9..7f8bc87241 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/summary/ReferendumSummaryInteractor.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/summary/ReferendaSummaryInteractor.kt @@ -1,11 +1,15 @@ package io.novafoundation.nova.feature_governance_impl.domain.summary import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.ReferendumId -import io.novafoundation.nova.feature_governance_api.domain.referendum.summary.ReferendaSummaryInteractor import io.novafoundation.nova.feature_governance_impl.data.GovernanceSharedState import io.novafoundation.nova.runtime.state.selectedOption import kotlinx.coroutines.CoroutineScope +interface ReferendaSummaryInteractor { + + suspend fun getReferendaSummaries(ids: List, coroutineScope: CoroutineScope): Map +} + class RealReferendaSummaryInteractor( private val governanceSharedState: GovernanceSharedState, private val referendaSummarySharedComputation: ReferendaSummarySharedComputation diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/summary/ReferendaSummarySharedComputation.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/summary/ReferendaSummarySharedComputation.kt index 7d7876549f..8237e87b3b 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/summary/ReferendaSummarySharedComputation.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/summary/ReferendaSummarySharedComputation.kt @@ -19,9 +19,9 @@ class ReferendaSummarySharedComputation( scope: CoroutineScope ): Map? { val chainId = governanceOption.assetWithChain.chain.id - val referendaSet = referendaIds.toSet() + val referendaHashCode = referendaIds.toSet().hashCode() val selectedLanguage = accountRepository.selectedLanguage() - val key = "REFERENDA_SUMMARIES:$chainId:$referendaSet:${selectedLanguage.iso639Code}" + val key = "REFERENDA_SUMMARIES:$chainId:$referendaHashCode:${selectedLanguage.iso639Code}" return computationalCache.useCache(key, scope) { referendumDetailsRepository.loadSummaries(governanceOption.assetWithChain.chain, referendaIds, selectedLanguage) diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/list/ReferendaListViewModel.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/list/ReferendaListViewModel.kt index 3f3cd1b4e0..176e60a989 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/list/ReferendaListViewModel.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/list/ReferendaListViewModel.kt @@ -3,6 +3,7 @@ package io.novafoundation.nova.feature_governance_impl.presentation.referenda.li import android.util.Log import androidx.lifecycle.viewModelScope import io.novafoundation.nova.common.base.BaseViewModel +import io.novafoundation.nova.common.domain.ExtendedLoadingState import io.novafoundation.nova.common.domain.dataOrNull import io.novafoundation.nova.common.list.toListWithHeaders import io.novafoundation.nova.common.domain.mapLoading @@ -28,7 +29,7 @@ import io.novafoundation.nova.feature_governance_impl.domain.filters.ReferendaFi import io.novafoundation.nova.feature_governance_api.domain.referendum.filters.ReferendumType import io.novafoundation.nova.feature_governance_api.domain.referendum.filters.ReferendumTypeFilter import io.novafoundation.nova.feature_governance_api.domain.referendum.list.ReferendaListState -import io.novafoundation.nova.feature_governance_api.domain.referendum.summary.ReferendaSummaryInteractor +import io.novafoundation.nova.feature_governance_impl.domain.summary.ReferendaSummaryInteractor import io.novafoundation.nova.feature_governance_impl.presentation.GovernanceRouter import io.novafoundation.nova.feature_governance_impl.presentation.referenda.common.ReferendumFormatter import io.novafoundation.nova.feature_governance_impl.presentation.referenda.common.list.ReferendaListStateModel @@ -114,7 +115,7 @@ class ReferendaListViewModel( val tinderGovBanner = referendaSummariesFlow.map { summaries -> val chain = selectedAssetSharedState.chain() - mapTinderGovToUi(chain, summaries.dataOrNull) + mapTinderGovToUi(chain, summaries) } .inBackground() .shareWhileSubscribed() @@ -179,15 +180,16 @@ class ReferendaListViewModel( } } - private fun mapTinderGovToUi(chain: Chain, referendaSummaries: Map?): TinderGovBannerModel? { + private fun mapTinderGovToUi(chain: Chain, referendaSummariesLoadingState: ExtendedLoadingState>): TinderGovBannerModel? { if (!chain.supportTinderGov()) return null - if (referendaSummaries == null) return null + + val referendumSummaries = referendaSummariesLoadingState.dataOrNull ?: return null return TinderGovBannerModel( - if (referendaSummaries.isEmpty()) { + if (referendumSummaries.isEmpty()) { null } else { - resourceManager.getString(R.string.referenda_swipe_gov_banner_chip, referendaSummaries.size) + resourceManager.getString(R.string.referenda_swipe_gov_banner_chip, referendumSummaries.size) } ) } diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/list/di/ReferendaListModule.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/list/di/ReferendaListModule.kt index 05fb3b415c..38f39b5a48 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/list/di/ReferendaListModule.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/list/di/ReferendaListModule.kt @@ -12,7 +12,7 @@ import io.novafoundation.nova.common.resources.ResourceManager import io.novafoundation.nova.core.updater.UpdateSystem import io.novafoundation.nova.feature_account_api.domain.interfaces.SelectedAccountUseCase import io.novafoundation.nova.feature_governance_api.domain.referendum.list.ReferendaListInteractor -import io.novafoundation.nova.feature_governance_api.domain.referendum.summary.ReferendaSummaryInteractor +import io.novafoundation.nova.feature_governance_impl.domain.summary.ReferendaSummaryInteractor import io.novafoundation.nova.feature_governance_impl.data.GovernanceSharedState import io.novafoundation.nova.feature_governance_impl.domain.dapp.GovernanceDAppsInteractor import io.novafoundation.nova.feature_governance_impl.domain.filters.ReferendaFiltersInteractor diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/vote/setup/tindergov/SetupTinderGovVoteViewModel.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/vote/setup/tindergov/SetupTinderGovVoteViewModel.kt index ec911efca8..6778b09057 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/vote/setup/tindergov/SetupTinderGovVoteViewModel.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/vote/setup/tindergov/SetupTinderGovVoteViewModel.kt @@ -7,7 +7,7 @@ import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepos import io.novafoundation.nova.feature_governance_api.data.model.VotingPower import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.VoteType import io.novafoundation.nova.feature_governance_api.domain.referendum.vote.VoteReferendumInteractor -import io.novafoundation.nova.feature_governance_api.domain.tindergov.TinderGovInteractor +import io.novafoundation.nova.feature_governance_impl.domain.referendum.tindergov.TinderGovInteractor import io.novafoundation.nova.feature_governance_impl.R import io.novafoundation.nova.feature_governance_impl.domain.referendum.vote.validations.referendum.VoteReferendaValidationPayload import io.novafoundation.nova.feature_governance_impl.domain.referendum.vote.validations.referendum.VoteReferendumValidationSystem diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/vote/setup/tindergov/di/SetupTinderGovVoteModule.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/vote/setup/tindergov/di/SetupTinderGovVoteModule.kt index 5b012ed54f..7632a1668d 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/vote/setup/tindergov/di/SetupTinderGovVoteModule.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/vote/setup/tindergov/di/SetupTinderGovVoteModule.kt @@ -12,7 +12,7 @@ import io.novafoundation.nova.common.resources.ResourceManager import io.novafoundation.nova.common.validation.ValidationExecutor import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository import io.novafoundation.nova.feature_governance_api.domain.referendum.vote.VoteReferendumInteractor -import io.novafoundation.nova.feature_governance_api.domain.tindergov.TinderGovInteractor +import io.novafoundation.nova.feature_governance_impl.domain.referendum.tindergov.TinderGovInteractor import io.novafoundation.nova.feature_governance_impl.domain.referendum.vote.validations.referendum.VoteReferendumValidationSystem import io.novafoundation.nova.feature_governance_impl.presentation.GovernanceRouter import io.novafoundation.nova.feature_governance_impl.presentation.common.conviction.ConvictionValuesProvider diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/basket/TinderGovBasketViewModel.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/basket/TinderGovBasketViewModel.kt index c31b67d5aa..ea7f491875 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/basket/TinderGovBasketViewModel.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/basket/TinderGovBasketViewModel.kt @@ -14,8 +14,8 @@ import io.novafoundation.nova.common.utils.toggle import io.novafoundation.nova.feature_governance_api.data.model.TinderGovBasketItem import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.VoteType import io.novafoundation.nova.feature_governance_api.domain.referendum.list.ReferendumPreview -import io.novafoundation.nova.feature_governance_api.domain.tindergov.TinderGovBasketInteractor -import io.novafoundation.nova.feature_governance_api.domain.tindergov.TinderGovInteractor +import io.novafoundation.nova.feature_governance_impl.domain.referendum.tindergov.TinderGovBasketInteractor +import io.novafoundation.nova.feature_governance_impl.domain.referendum.tindergov.TinderGovInteractor import io.novafoundation.nova.feature_governance_impl.R import io.novafoundation.nova.feature_governance_impl.data.GovernanceSharedState import io.novafoundation.nova.feature_governance_impl.presentation.GovernanceRouter diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/basket/di/TinderGovBasketModule.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/basket/di/TinderGovBasketModule.kt index acebdc6a49..cdf5c28ea6 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/basket/di/TinderGovBasketModule.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/basket/di/TinderGovBasketModule.kt @@ -10,8 +10,8 @@ import io.novafoundation.nova.common.di.viewmodel.ViewModelKey import io.novafoundation.nova.common.di.viewmodel.ViewModelModule import io.novafoundation.nova.common.mixin.actionAwaitable.ActionAwaitableMixin import io.novafoundation.nova.common.resources.ResourceManager -import io.novafoundation.nova.feature_governance_api.domain.tindergov.TinderGovBasketInteractor -import io.novafoundation.nova.feature_governance_api.domain.tindergov.TinderGovInteractor +import io.novafoundation.nova.feature_governance_impl.domain.referendum.tindergov.TinderGovBasketInteractor +import io.novafoundation.nova.feature_governance_impl.domain.referendum.tindergov.TinderGovInteractor import io.novafoundation.nova.feature_governance_impl.data.GovernanceSharedState import io.novafoundation.nova.feature_governance_impl.presentation.GovernanceRouter import io.novafoundation.nova.feature_governance_impl.presentation.common.voters.VotersFormatter diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/cards/TinderGovCardsViewModel.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/cards/TinderGovCardsViewModel.kt index d176dcff32..434a2c3ded 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/cards/TinderGovCardsViewModel.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/cards/TinderGovCardsViewModel.kt @@ -21,10 +21,10 @@ import io.novafoundation.nova.feature_governance_api.data.network.blockhain.mode import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.VoteType import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.amountMultiplier import io.novafoundation.nova.feature_governance_api.domain.referendum.list.ReferendumPreview -import io.novafoundation.nova.feature_governance_api.domain.referendum.summary.ReferendaSummaryInteractor -import io.novafoundation.nova.feature_governance_api.domain.tindergov.TinderGovBasketInteractor -import io.novafoundation.nova.feature_governance_api.domain.tindergov.TinderGovInteractor -import io.novafoundation.nova.feature_governance_api.domain.tindergov.VotingPowerState +import io.novafoundation.nova.feature_governance_impl.domain.summary.ReferendaSummaryInteractor +import io.novafoundation.nova.feature_governance_impl.domain.referendum.tindergov.TinderGovBasketInteractor +import io.novafoundation.nova.feature_governance_impl.domain.referendum.tindergov.TinderGovInteractor +import io.novafoundation.nova.feature_governance_impl.domain.referendum.tindergov.VotingPowerState import io.novafoundation.nova.feature_governance_impl.R import io.novafoundation.nova.feature_governance_impl.presentation.GovernanceRouter import io.novafoundation.nova.feature_governance_impl.presentation.common.info.ReferendumInfoPayload @@ -91,7 +91,7 @@ class TinderGovCardsViewModel( private val _resetCards = MutableLiveData>() val resetCardsEvent: LiveData> = _resetCards - private val topReferendumWithDetails = combine(topCardIndex, sortedReferendaFlow) { topCardIndex, referenda -> + private val topCardFlow = combine(topCardIndex, sortedReferendaFlow) { topCardIndex, referenda -> referenda.getOrNull(topCardIndex) ?: return@combine null } .distinctUntilChanged() @@ -99,7 +99,7 @@ class TinderGovCardsViewModel( private var isVotingInProgress = MutableStateFlow(false) - val manageVotingPowerAvailable = topReferendumWithDetails.map { it != null } + val manageVotingPowerAvailable = topCardFlow.map { it != null } val isCardDraggingAvailable = isVotingInProgress.map { !it } @@ -169,7 +169,7 @@ class TinderGovCardsViewModel( fun editVotingPowerClicked() { launch { - val topReferendum = topReferendumWithDetails.first() + val topReferendum = topCardFlow.first() val topReferendumId = topReferendum?.id?.value ?: return@launch val request = TinderGovVoteRequester.Request(topReferendumId) tinderGovVoteRequester.openRequest(request) @@ -295,8 +295,7 @@ class TinderGovCardsViewModel( val summaries = referendaSummaryInteractor.getReferendaSummaries(referendaIds, viewModelScope) val amounts = referenda.associate { it.id to it.getAmountModel() } - val referendaCards = referenda.mapToCardWithDetails(summaries, amounts) - .filterNotNull() + val referendaCards = referenda.mapToCardsAndFilterBySummaries(summaries, amounts) ReferendaWithBasket(referendaCards, basket) } @@ -304,12 +303,12 @@ class TinderGovCardsViewModel( .launchIn(this) } - private fun List.mapToCardWithDetails( + private fun List.mapToCardsAndFilterBySummaries( summaries: Map, amounts: Map - ): List { - return map { - val summary = summaries[it.id] ?: return@map null + ): List { + return mapNotNull { + val summary = summaries[it.id] ?: return@mapNotNull null CardWithDetails(it.id, summary, amounts[it.id]) } diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/cards/di/TinderGovCardsModule.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/cards/di/TinderGovCardsModule.kt index d38910c19a..72c2f53c88 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/cards/di/TinderGovCardsModule.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/cards/di/TinderGovCardsModule.kt @@ -15,9 +15,9 @@ import io.novafoundation.nova.common.di.viewmodel.ViewModelModule import io.novafoundation.nova.common.mixin.actionAwaitable.ActionAwaitableMixin import io.novafoundation.nova.common.resources.ResourceManager import io.novafoundation.nova.common.utils.markdown.BoldStylePlugin -import io.novafoundation.nova.feature_governance_api.domain.referendum.summary.ReferendaSummaryInteractor -import io.novafoundation.nova.feature_governance_api.domain.tindergov.TinderGovBasketInteractor -import io.novafoundation.nova.feature_governance_api.domain.tindergov.TinderGovInteractor +import io.novafoundation.nova.feature_governance_impl.domain.summary.ReferendaSummaryInteractor +import io.novafoundation.nova.feature_governance_impl.domain.referendum.tindergov.TinderGovBasketInteractor +import io.novafoundation.nova.feature_governance_impl.domain.referendum.tindergov.TinderGovInteractor import io.novafoundation.nova.feature_governance_impl.presentation.GovernanceRouter import io.novafoundation.nova.feature_governance_impl.presentation.referenda.vote.setup.tindergov.TinderGovVoteCommunicator import io.novafoundation.nova.feature_governance_impl.presentation.tindergov.cards.TinderGovCardsViewModel diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/cards/model/CardWithDetails.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/cards/model/CardWithDetails.kt index d8f1e92005..8f0607f756 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/cards/model/CardWithDetails.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/cards/model/CardWithDetails.kt @@ -3,23 +3,8 @@ package io.novafoundation.nova.feature_governance_impl.presentation.tindergov.ca import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.ReferendumId import io.novafoundation.nova.feature_wallet_api.presentation.model.AmountModel -class CardWithDetails( +data class CardWithDetails( val id: ReferendumId, val summary: String, val amount: AmountModel? -) { - - override fun equals(other: Any?): Boolean { - return other is CardWithDetails && - id == other.id && - summary == other.summary && - amount == other.amount - } - - override fun hashCode(): Int { - var result = id.hashCode() - result = 31 * result + summary.hashCode() - result = 31 * result + amount.hashCode() - return result - } -} +) diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/confirm/ConfirmTinderGovVoteViewModel.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/confirm/ConfirmTinderGovVoteViewModel.kt index bb6b2d48a7..e54116bfd4 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/confirm/ConfirmTinderGovVoteViewModel.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/confirm/ConfirmTinderGovVoteViewModel.kt @@ -13,8 +13,8 @@ import io.novafoundation.nova.feature_account_api.presenatation.actions.External import io.novafoundation.nova.feature_governance_api.data.model.TinderGovBasketItem import io.novafoundation.nova.feature_governance_api.data.model.accountVote import io.novafoundation.nova.feature_governance_api.domain.referendum.vote.VoteReferendumInteractor -import io.novafoundation.nova.feature_governance_api.domain.tindergov.TinderGovBasketInteractor -import io.novafoundation.nova.feature_governance_api.domain.tindergov.TinderGovInteractor +import io.novafoundation.nova.feature_governance_impl.domain.referendum.tindergov.TinderGovBasketInteractor +import io.novafoundation.nova.feature_governance_impl.domain.referendum.tindergov.TinderGovInteractor import io.novafoundation.nova.feature_governance_impl.R import io.novafoundation.nova.feature_governance_impl.data.GovernanceSharedState import io.novafoundation.nova.feature_governance_impl.domain.referendum.vote.validations.tindergov.VoteTinderGovValidationPayload diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/confirm/di/ConfirmTinderGovVoteModule.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/confirm/di/ConfirmTinderGovVoteModule.kt index 3efb4111eb..51052adcb1 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/confirm/di/ConfirmTinderGovVoteModule.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/confirm/di/ConfirmTinderGovVoteModule.kt @@ -18,8 +18,8 @@ import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.W import io.novafoundation.nova.feature_account_api.presenatation.actions.ExternalActions import io.novafoundation.nova.feature_governance_api.data.source.GovernanceSourceRegistry import io.novafoundation.nova.feature_governance_api.domain.referendum.vote.VoteReferendumInteractor -import io.novafoundation.nova.feature_governance_api.domain.tindergov.TinderGovBasketInteractor -import io.novafoundation.nova.feature_governance_api.domain.tindergov.TinderGovInteractor +import io.novafoundation.nova.feature_governance_impl.domain.referendum.tindergov.TinderGovBasketInteractor +import io.novafoundation.nova.feature_governance_impl.domain.referendum.tindergov.TinderGovInteractor import io.novafoundation.nova.feature_governance_impl.data.GovernanceSharedState import io.novafoundation.nova.feature_governance_impl.domain.referendum.vote.validations.tindergov.VoteTinderGovValidationSystem import io.novafoundation.nova.feature_governance_impl.domain.referendum.vote.validations.tindergov.voteTinderGovValidationSystem From 16dd145cf9a656c61ea0ca1f0322c073654cdcfd Mon Sep 17 00:00:00 2001 From: valentunn <70131744+valentunn@users.noreply.github.com> Date: Mon, 7 Oct 2024 09:59:07 +0300 Subject: [PATCH 07/23] Fix - no historical delegations shown for finished delegations (#1680) --- .../delegation/v2/stats/request/AllHistoricalVotesRequest.kt | 4 +--- .../offchain/delegation/v2/stats/response/AllVotesResponse.kt | 4 +--- .../data/repository/v2/Gov2DelegationsRepository.kt | 2 +- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/delegation/v2/stats/request/AllHistoricalVotesRequest.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/delegation/v2/stats/request/AllHistoricalVotesRequest.kt index 341aed2b23..312bb2d51c 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/delegation/v2/stats/request/AllHistoricalVotesRequest.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/delegation/v2/stats/request/AllHistoricalVotesRequest.kt @@ -20,9 +20,7 @@ class AllHistoricalVotesRequest(address: String) { vote parent { referendumId - delegate { - accountId - } + delegateId standardVote } } diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/delegation/v2/stats/response/AllVotesResponse.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/delegation/v2/stats/response/AllVotesResponse.kt index c9892d131d..29e1b7626a 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/delegation/v2/stats/response/AllVotesResponse.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/offchain/delegation/v2/stats/response/AllVotesResponse.kt @@ -27,9 +27,7 @@ class DelegatedVoteRemote( class Parent( val referendumId: BigInteger, - val delegate: Delegate, + val delegateId: String, val standardVote: StandardVoteRemote? ) - - class Delegate(@SerializedName("accountId") val address: String) } diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/repository/v2/Gov2DelegationsRepository.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/repository/v2/Gov2DelegationsRepository.kt index 0c8e5130b0..625366550a 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/repository/v2/Gov2DelegationsRepository.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/repository/v2/Gov2DelegationsRepository.kt @@ -184,7 +184,7 @@ class Gov2DelegationsRepository( val standardVote = delegatedVoteRemote.vote UserVote.Delegated( - delegate = chain.accountIdOf(delegatedVoteRemote.parent.delegate.address), + delegate = chain.accountIdOf(delegatedVoteRemote.parent.delegateId), vote = AccountVote.Standard( balance = standardVote.amount, vote = Vote( From 702649a3881f72ba258c5a3947aac3a5615b71b3 Mon Sep 17 00:00:00 2001 From: valentunn <70131744+valentunn@users.noreply.github.com> Date: Mon, 7 Oct 2024 10:21:56 +0300 Subject: [PATCH 08/23] Chore/refactor network managment (#1681) * Refactor network managment * Consider all prefilled nodes as custom * Remove comment dublication * Fixes * Code style * Remove unused test * Fix tests --- .../nova/core_db/dao/Helpers.kt | 2 +- .../core_db/converters/ChainConverters.kt | 8 +- .../nova/core_db/model/chain/ChainLocal.kt | 9 +- .../chain/NodeSelectionPreferencesLocal.kt | 5 ++ .../NetworkManagementChainInteractor.kt | 34 ++++--- .../domain/utils/CustomChainFactory.kt | 51 ++++++++--- .../chain/ChainNetworkManagementViewModel.kt | 10 +-- .../ChainNetworkManagementNodesAdapter.kt | 2 +- .../nodeAdapter/items/NetworkNodeRvItem.kt | 2 +- .../nova/runtime/di/ChainRegistryModule.kt | 14 ++- .../ethereum/BalancingHttpWeb3jService.kt | 42 +++------ .../nova/runtime/ethereum/Web3Api.kt | 14 +-- .../nova/runtime/ext/ChainExt.kt | 19 ++-- .../runtime/multiNetwork/ChainRegistry.kt | 19 ++-- .../chain/mappers/DomainToLocalChainMapper.kt | 17 ++-- .../chain/mappers/LocalToDomainChainMapper.kt | 28 +++--- .../mappers/RemoteToLocalChainMappers.kt | 10 +-- .../runtime/multiNetwork/chain/model/Chain.kt | 15 ++-- .../connection/ChainConnection.kt | 42 ++++----- .../multiNetwork/connection/Web3ApiPool.kt | 10 +-- .../autobalance/NodeAutobalancer.kt | 42 +++++---- .../strategy/NodeSelectionSequenceStrategy.kt | 12 --- .../strategy/NodeSelectionStrategyProvider.kt | 69 ++++++++++---- .../strategy/NodeSequenceGenerator.kt | 12 +++ ...obinStrategy.kt => RoundRobinGenerator.kt} | 8 +- ...rmStrategy.kt => SelectedNodeGenerator.kt} | 8 +- .../strategy/SelectedNodeStrategy.kt | 23 ----- .../autobalance/strategy/UniformGenerator.kt | 13 +++ .../autobalance/NodeAutobalancerTest.kt | 90 ------------------- .../strategy/RoundRobinStrategyTest.kt | 7 +- 30 files changed, 310 insertions(+), 327 deletions(-) delete mode 100644 runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/connection/autobalance/strategy/NodeSelectionSequenceStrategy.kt create mode 100644 runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/connection/autobalance/strategy/NodeSequenceGenerator.kt rename runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/connection/autobalance/strategy/{RoundRobinStrategy.kt => RoundRobinGenerator.kt} (50%) rename runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/connection/autobalance/strategy/{UniformStrategy.kt => SelectedNodeGenerator.kt} (50%) delete mode 100644 runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/connection/autobalance/strategy/SelectedNodeStrategy.kt create mode 100644 runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/connection/autobalance/strategy/UniformGenerator.kt delete mode 100644 runtime/src/test/java/io/novafoundation/nova/runtime/multiNetwork/connection/autobalance/NodeAutobalancerTest.kt diff --git a/core-db/src/androidTest/java/io/novafoundation/nova/core_db/dao/Helpers.kt b/core-db/src/androidTest/java/io/novafoundation/nova/core_db/dao/Helpers.kt index 52daa4215f..e9085c3b47 100644 --- a/core-db/src/androidTest/java/io/novafoundation/nova/core_db/dao/Helpers.kt +++ b/core-db/src/androidTest/java/io/novafoundation/nova/core_db/dao/Helpers.kt @@ -64,7 +64,7 @@ fun chainOf( supportProxy = false, swap = "", hasSubstrateRuntime = true, - nodeSelectionStrategy = ChainLocal.NodeSelectionStrategyLocal.ROUND_ROBIN, + nodeSelectionStrategy = ChainLocal.AutoBalanceStrategyLocal.ROUND_ROBIN, source = ChainLocal.Source.CUSTOM, customFee = "" ) diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/converters/ChainConverters.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/converters/ChainConverters.kt index 32e52b7fe3..0a6caa3e36 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/converters/ChainConverters.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/converters/ChainConverters.kt @@ -3,16 +3,16 @@ package io.novafoundation.nova.core_db.converters import androidx.room.TypeConverter import io.novafoundation.nova.common.utils.enumValueOfOrNull import io.novafoundation.nova.core_db.model.chain.ChainLocal.ConnectionStateLocal -import io.novafoundation.nova.core_db.model.chain.ChainLocal.NodeSelectionStrategyLocal +import io.novafoundation.nova.core_db.model.chain.ChainLocal.AutoBalanceStrategyLocal class ChainConverters { @TypeConverter - fun fromNodeStrategy(strategy: NodeSelectionStrategyLocal): String = strategy.name + fun fromNodeStrategy(strategy: AutoBalanceStrategyLocal): String = strategy.name @TypeConverter - fun toNodeStrategy(name: String): NodeSelectionStrategyLocal { - return enumValueOfOrNull(name) ?: NodeSelectionStrategyLocal.UNKNOWN + fun toNodeStrategy(name: String): AutoBalanceStrategyLocal { + return enumValueOfOrNull(name) ?: AutoBalanceStrategyLocal.UNKNOWN } @TypeConverter diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/ChainLocal.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/ChainLocal.kt index 76ea6e964a..53bfaf03f3 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/ChainLocal.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/ChainLocal.kt @@ -33,12 +33,17 @@ data class ChainLocal( val governance: String, val additional: String?, val connectionState: ConnectionStateLocal, + @Deprecated("Use autoBalanceStrategy") @ColumnInfo(defaultValue = NODE_SELECTION_STRATEGY_DEFAULT) - val nodeSelectionStrategy: NodeSelectionStrategyLocal, + val nodeSelectionStrategy: AutoBalanceStrategyLocal, @ColumnInfo(defaultValue = DEFAULT_NETWORK_SOURCE_STR) val source: Source ) : Identifiable { + @Suppress("DEPRECATION") + val autoBalanceStrategy: AutoBalanceStrategyLocal + get() = nodeSelectionStrategy + companion object { const val EMPTY_CHAIN_ICON = "" @@ -46,7 +51,7 @@ data class ChainLocal( const val DEFAULT_NETWORK_SOURCE_STR = "DEFAULT" } - enum class NodeSelectionStrategyLocal { + enum class AutoBalanceStrategyLocal { ROUND_ROBIN, UNIFORM, UNKNOWN } diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/NodeSelectionPreferencesLocal.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/NodeSelectionPreferencesLocal.kt index 21cff260f8..39df5546a3 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/NodeSelectionPreferencesLocal.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/NodeSelectionPreferencesLocal.kt @@ -26,9 +26,14 @@ data class NodeSelectionPreferencesLocal( val chainId: String, @ColumnInfo(defaultValue = DEFAULT_AUTO_BALANCE_DEFAULT_STR) val autoBalanceEnabled: Boolean, + @Deprecated("Use [selectedUnformattedWssNodeUrl]") val selectedNodeUrl: String? ) : Identifiable { + @Suppress("DEPRECATION") + val selectedUnformattedWssNodeUrl: String? + get() = selectedNodeUrl + companion object { const val DEFAULT_AUTO_BALANCE_DEFAULT_STR = "1" diff --git a/feature-settings-impl/src/main/java/io/novafoundation/nova/feature_settings_impl/domain/NetworkManagementChainInteractor.kt b/feature-settings-impl/src/main/java/io/novafoundation/nova/feature_settings_impl/domain/NetworkManagementChainInteractor.kt index 57664ff4d8..df828da919 100644 --- a/feature-settings-impl/src/main/java/io/novafoundation/nova/feature_settings_impl/domain/NetworkManagementChainInteractor.kt +++ b/feature-settings-impl/src/main/java/io/novafoundation/nova/feature_settings_impl/domain/NetworkManagementChainInteractor.kt @@ -3,15 +3,15 @@ package io.novafoundation.nova.feature_settings_impl.domain import io.novafoundation.nova.common.utils.combine import io.novafoundation.nova.common.utils.flowOf import io.novafoundation.nova.runtime.ext.Geneses -import io.novafoundation.nova.runtime.ext.autoBalanceDisabled import io.novafoundation.nova.runtime.ext.genesisHash import io.novafoundation.nova.runtime.ext.isCustomNetwork import io.novafoundation.nova.runtime.ext.isDisabled import io.novafoundation.nova.runtime.ext.isEnabled -import io.novafoundation.nova.runtime.ext.selectedNodeUrlOrNull +import io.novafoundation.nova.runtime.ext.selectedUnformattedWssNodeUrlOrNull import io.novafoundation.nova.runtime.ext.wssNodes import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain +import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain.Nodes.NodeSelectionStrategy import io.novafoundation.nova.runtime.multiNetwork.connection.node.healthState.NodeHealthStateTesterFactory import io.novafoundation.nova.runtime.repository.ChainRepository import kotlinx.coroutines.CoroutineScope @@ -50,13 +50,13 @@ interface NetworkManagementChainInteractor { suspend fun toggleAutoBalance(chainId: String) - suspend fun selectNode(chainId: String, nodeUrl: String) + suspend fun selectNode(chainId: String, unformattedNodeUrl: String) suspend fun toggleChainEnableState(chainId: String) suspend fun deleteNetwork(chainId: String) - suspend fun deleteNode(chainId: String, nodeUrl: String) + suspend fun deleteNode(chainId: String, unformattedNodeUrl: String) } class RealNetworkManagementChainInteractor( @@ -77,11 +77,23 @@ class RealNetworkManagementChainInteractor( override suspend fun toggleAutoBalance(chainId: String) { val chain = chainRegistry.getChain(chainId) - chainRegistry.setAutoBalanceEnabled(chainId, chain.autoBalanceDisabled) + chainRegistry.setWssNodeSelectionStrategy(chainId, chain.nodes.strategyForToggledWssAutoBalance()) } - override suspend fun selectNode(chainId: String, nodeUrl: String) { - chainRegistry.setDefaultNode(chainId, nodeUrl) + private fun Chain.Nodes.strategyForToggledWssAutoBalance(): NodeSelectionStrategy { + return when (wssNodeSelectionStrategy) { + NodeSelectionStrategy.AutoBalance -> { + val firstNode = wssNodes().first() + NodeSelectionStrategy.SelectedNode(firstNode.unformattedUrl) + } + + is NodeSelectionStrategy.SelectedNode -> NodeSelectionStrategy.AutoBalance + } + } + + override suspend fun selectNode(chainId: String, unformattedNodeUrl: String) { + val strategy = NodeSelectionStrategy.SelectedNode(unformattedNodeUrl) + chainRegistry.setWssNodeSelectionStrategy(chainId, strategy) } override suspend fun toggleChainEnableState(chainId: String) { @@ -97,15 +109,15 @@ class RealNetworkManagementChainInteractor( chainRepository.deleteNetwork(chainId) } - override suspend fun deleteNode(chainId: String, nodeUrl: String) { + override suspend fun deleteNode(chainId: String, unformattedNodeUrl: String) { val chain = chainRegistry.getChain(chainId) require(chain.nodes.nodes.size > 1) - chainRepository.deleteNode(chainId, nodeUrl) + chainRepository.deleteNode(chainId, unformattedNodeUrl) - if (chain.selectedNodeUrlOrNull == nodeUrl) { - chainRegistry.setAutoBalanceEnabled(chain.id, true) + if (chain.selectedUnformattedWssNodeUrlOrNull == unformattedNodeUrl) { + chainRegistry.setWssNodeSelectionStrategy(chainId, NodeSelectionStrategy.AutoBalance) } } diff --git a/feature-settings-impl/src/main/java/io/novafoundation/nova/feature_settings_impl/domain/utils/CustomChainFactory.kt b/feature-settings-impl/src/main/java/io/novafoundation/nova/feature_settings_impl/domain/utils/CustomChainFactory.kt index 1ec3eaa953..2a6ec8f606 100644 --- a/feature-settings-impl/src/main/java/io/novafoundation/nova/feature_settings_impl/domain/utils/CustomChainFactory.kt +++ b/feature-settings-impl/src/main/java/io/novafoundation/nova/feature_settings_impl/domain/utils/CustomChainFactory.kt @@ -15,7 +15,7 @@ import io.novafoundation.nova.runtime.ext.EVM_DEFAULT_TOKEN_DECIMALS import io.novafoundation.nova.runtime.ext.evmChainIdFrom import io.novafoundation.nova.runtime.ext.utilityAsset import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain -import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain.Nodes.NodeSelectionStrategy.AutoBalance +import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain.Nodes.NodeSelectionStrategy import io.novafoundation.nova.runtime.multiNetwork.connection.node.connection.NodeConnection import io.novafoundation.nova.runtime.multiNetwork.connection.node.connection.NodeConnectionFactory import io.novafoundation.nova.runtime.network.rpc.systemProperties @@ -102,23 +102,21 @@ class CustomChainFactory( source = Chain.Asset.Source.MANUAL, ) - val node = Chain.Node( - chainId = chainId, - unformattedUrl = payload.nodeUrl, - name = payload.nodeName, - orderId = 0, - isCustom = true, - ) - val explorer = getChainExplorer(payload.blockExplorer, chainId) + val nodes = Chain.Nodes( + autoBalanceStrategy = prefilledChain?.nodes?.autoBalanceStrategy ?: Chain.Nodes.AutoBalanceStrategy.ROUND_ROBIN, + wssNodeSelectionStrategy = NodeSelectionStrategy.AutoBalance, + nodes = createNodeList(chainId, prefilledChain, payload) + ) + return Chain( id = chainId, parentId = prefilledChain?.parentId, name = payload.chainName, assets = listOf(asset), - nodes = Chain.Nodes(prefilledChain?.nodes?.nodeSelectionStrategy ?: AutoBalance.ROUND_ROBIN, listOf(node)), - explorers = explorer?.let { listOf(it) } ?: prefilledChain?.explorers.orEmpty(), + nodes = nodes, + explorers = explorer?.let(::listOf) ?: prefilledChain?.explorers.orEmpty(), externalApis = prefilledChain?.externalApis.orEmpty(), icon = prefilledChain?.icon, addressPrefix = addressPrefix, @@ -138,6 +136,37 @@ class CustomChainFactory( ) } + private fun createNodeList( + chainId: String, + prefilledChain: Chain?, + input: CustomNetworkPayload + ): List { + val inputNode = Chain.Node( + chainId = chainId, + unformattedUrl = input.nodeUrl, + name = input.nodeName, + orderId = 0, + isCustom = true, + ) + + val prefilledNodes = prefilledChain?.nodes?.nodes.orEmpty() + val prefilledExceptInput = prefilledNodes.mapNotNull { + val differentFromInput = it.unformattedUrl != inputNode.unformattedUrl + + if (differentFromInput) { + // Consider prefilled nodes as custom + it.copy(isCustom = true) + } else { + null + } + } + + return buildList { + add(inputNode) + addAll(prefilledExceptInput) + } + } + fun getChainExplorer(blockExplorer: CustomNetworkPayload.BlockExplorer?, chainId: String): Chain.Explorer? { return blockExplorer?.let { val links = blockExplorerLinkFormatter.format(it.url) diff --git a/feature-settings-impl/src/main/java/io/novafoundation/nova/feature_settings_impl/presentation/networkManagement/chain/ChainNetworkManagementViewModel.kt b/feature-settings-impl/src/main/java/io/novafoundation/nova/feature_settings_impl/presentation/networkManagement/chain/ChainNetworkManagementViewModel.kt index 807bbdc41c..61ae27ee64 100644 --- a/feature-settings-impl/src/main/java/io/novafoundation/nova/feature_settings_impl/presentation/networkManagement/chain/ChainNetworkManagementViewModel.kt +++ b/feature-settings-impl/src/main/java/io/novafoundation/nova/feature_settings_impl/presentation/networkManagement/chain/ChainNetworkManagementViewModel.kt @@ -83,7 +83,7 @@ class ChainNetworkManagementViewModel( fun selectNode(item: NetworkNodeRvItem) { launch { - networkManagementChainInteractor.selectNode(payload.chainId, item.url) + networkManagementChainInteractor.selectNode(payload.chainId, item.unformattedUrl) } } @@ -96,12 +96,12 @@ class ChainNetworkManagementViewModel( R.string.manage_node_actions_title, subtitle = item.name, listOf( - editItem(R.string.manage_node_action_edit) { editNode(item.url) }, + editItem(R.string.manage_node_action_edit) { editNode(item.unformattedUrl) }, deleteItem(R.string.manage_node_action_delete) { deleteNode(item) } ) ) } else { - editNode(item.url) + editNode(item.unformattedUrl) } } } @@ -151,7 +151,7 @@ class ChainNetworkManagementViewModel( ) ) - networkManagementChainInteractor.deleteNode(chainId = payload.chainId, item.url) + networkManagementChainInteractor.deleteNode(chainId = payload.chainId, unformattedNodeUrl = item.unformattedUrl) } } @@ -181,7 +181,7 @@ class ChainNetworkManagementViewModel( return NetworkNodeRvItem( id = nodeHealthState.node.unformattedUrl, name = nodeHealthState.node.name, - url = nodeHealthState.node.unformattedUrl, + unformattedUrl = nodeHealthState.node.unformattedUrl, isEditable = nodeHealthState.node.isCustom, isDeletable = networkState.nodeHealthStates.size > 1 && nodeHealthState.node.isCustom, isSelected = nodeHealthState.node.unformattedUrl == networkState.connectingNode?.unformattedUrl, diff --git a/feature-settings-impl/src/main/java/io/novafoundation/nova/feature_settings_impl/presentation/networkManagement/chain/nodeAdapter/ChainNetworkManagementNodesAdapter.kt b/feature-settings-impl/src/main/java/io/novafoundation/nova/feature_settings_impl/presentation/networkManagement/chain/nodeAdapter/ChainNetworkManagementNodesAdapter.kt index 8e568d358a..a2bf7dfef1 100644 --- a/feature-settings-impl/src/main/java/io/novafoundation/nova/feature_settings_impl/presentation/networkManagement/chain/nodeAdapter/ChainNetworkManagementNodesAdapter.kt +++ b/feature-settings-impl/src/main/java/io/novafoundation/nova/feature_settings_impl/presentation/networkManagement/chain/nodeAdapter/ChainNetworkManagementNodesAdapter.kt @@ -100,7 +100,7 @@ class ChainNetworkManagementNodeViewHolder( chainNodeRadioButton.isEnabled = item.isSelectable chainNodeName.text = item.name chainNodeName.setTextColorRes(item.nameColorRes) - chainNodeSocketAddress.text = item.url + chainNodeSocketAddress.text = item.unformattedUrl chainNodeConnectionStatusShimmering.setShimmerShown(item.connectionState.showShimmering) chainNodeConnectionState.setText(item.connectionState.name) item.connectionState.chainStatusColor?.let { chainNodeConnectionState.setTextColor(it) } diff --git a/feature-settings-impl/src/main/java/io/novafoundation/nova/feature_settings_impl/presentation/networkManagement/chain/nodeAdapter/items/NetworkNodeRvItem.kt b/feature-settings-impl/src/main/java/io/novafoundation/nova/feature_settings_impl/presentation/networkManagement/chain/nodeAdapter/items/NetworkNodeRvItem.kt index dbefb08349..e07fa0cd5c 100644 --- a/feature-settings-impl/src/main/java/io/novafoundation/nova/feature_settings_impl/presentation/networkManagement/chain/nodeAdapter/items/NetworkNodeRvItem.kt +++ b/feature-settings-impl/src/main/java/io/novafoundation/nova/feature_settings_impl/presentation/networkManagement/chain/nodeAdapter/items/NetworkNodeRvItem.kt @@ -7,7 +7,7 @@ data class NetworkNodeRvItem( val id: String, val name: String, @ColorRes val nameColorRes: Int, - val url: String, + val unformattedUrl: String, val isEditable: Boolean, val isDeletable: Boolean, val isSelected: Boolean, diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/di/ChainRegistryModule.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/di/ChainRegistryModule.kt index 8e51d23ecc..97e74a266d 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/di/ChainRegistryModule.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/di/ChainRegistryModule.kt @@ -5,8 +5,8 @@ import dagger.Module import dagger.Provides import io.novafoundation.nova.common.BuildConfig import io.novafoundation.nova.common.data.network.NetworkApiCreator -import io.novafoundation.nova.common.data.storage.Preferences import io.novafoundation.nova.common.data.network.rpc.BulkRetriever +import io.novafoundation.nova.common.data.storage.Preferences import io.novafoundation.nova.common.di.scope.ApplicationScope import io.novafoundation.nova.common.interfaces.FileProvider import io.novafoundation.nova.core_db.dao.ChainAssetDao @@ -140,14 +140,15 @@ class ChainRegistryModule { @Provides @ApplicationScope - fun provideAutoBalanceProvider() = NodeSelectionStrategyProvider() + fun provideAutoBalanceProvider( + connectionSecrets: ConnectionSecrets + ) = NodeSelectionStrategyProvider(connectionSecrets) @Provides @ApplicationScope fun provideNodeAutoBalancer( nodeSelectionStrategyProvider: NodeSelectionStrategyProvider, - connectionSecrets: ConnectionSecrets - ) = NodeAutobalancer(nodeSelectionStrategyProvider, connectionSecrets) + ) = NodeAutobalancer(nodeSelectionStrategyProvider) @Provides @ApplicationScope @@ -173,12 +174,10 @@ class ChainRegistryModule { socketProvider: Provider, externalRequirementsFlow: MutableStateFlow, nodeAutobalancer: NodeAutobalancer, - connectionSecrets: ConnectionSecrets ) = ChainConnectionFactory( externalRequirementsFlow, nodeAutobalancer, socketProvider, - connectionSecrets ) @Provides @@ -195,7 +194,6 @@ class ChainRegistryModule { @Provides @ApplicationScope fun provideWeb3ApiFactory( - connectionSecrets: ConnectionSecrets, strategyProvider: NodeSelectionStrategyProvider, ): Web3ApiFactory { val builder = HttpService.getOkHttpClientBuilder() @@ -207,7 +205,7 @@ class ChainRegistryModule { val okHttpClient = builder.build() - return Web3ApiFactory(connectionSecrets = connectionSecrets, strategyProvider = strategyProvider, httpClient = okHttpClient) + return Web3ApiFactory(strategyProvider = strategyProvider, httpClient = okHttpClient) } @Provides diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/ethereum/BalancingHttpWeb3jService.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/ethereum/BalancingHttpWeb3jService.kt index 4a8cab4569..eb62359037 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/ethereum/BalancingHttpWeb3jService.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/ethereum/BalancingHttpWeb3jService.kt @@ -6,15 +6,13 @@ import com.fasterxml.jackson.databind.node.ArrayNode import com.fasterxml.jackson.databind.node.ObjectNode import io.novafoundation.nova.common.utils.requireException import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain -import io.novafoundation.nova.runtime.multiNetwork.connection.ConnectionSecrets import io.novafoundation.nova.runtime.multiNetwork.connection.NodeWithSaturatedUrl import io.novafoundation.nova.runtime.multiNetwork.connection.UpdatableNodes -import io.novafoundation.nova.runtime.multiNetwork.connection.autobalance.strategy.NodeSelectionSequenceStrategy import io.novafoundation.nova.runtime.multiNetwork.connection.autobalance.strategy.NodeSelectionStrategyProvider +import io.novafoundation.nova.runtime.multiNetwork.connection.autobalance.strategy.NodeSequenceGenerator import io.novafoundation.nova.runtime.multiNetwork.connection.autobalance.strategy.generateNodeIterator -import io.novafoundation.nova.runtime.multiNetwork.connection.saturateNodeUrls -import io.reactivex.Flowable import io.novasama.substrate_sdk_android.extensions.tryFindNonNull +import io.reactivex.Flowable import okhttp3.Call import okhttp3.Callback import okhttp3.OkHttpClient @@ -31,27 +29,21 @@ import org.web3j.protocol.http.HttpService import org.web3j.protocol.websocket.events.Notification import java.io.IOException import java.util.concurrent.CompletableFuture -import java.util.concurrent.ExecutorService class BalancingHttpWeb3jService( initialNodes: Chain.Nodes, - connectionSecrets: ConnectionSecrets, private val httpClient: OkHttpClient, private val strategyProvider: NodeSelectionStrategyProvider, private val objectMapper: ObjectMapper = ObjectMapperFactory.getObjectMapper(), - private val executorService: ExecutorService, ) : Web3jService, UpdatableNodes { private val nodeSwitcher = NodeSwitcher( - initialNodes = initialNodes.nodes, - initialStrategy = strategyProvider.strategyFor(initialNodes.nodeSelectionStrategy), - connectionSecrets = connectionSecrets + initialStrategy = strategyProvider.createHttp(initialNodes), ) override fun updateNodes(nodes: Chain.Nodes) { - val autoBalanceStrategy = strategyProvider.strategyFor(nodes.nodeSelectionStrategy) - - nodeSwitcher.updateNodes(nodes.nodes, autoBalanceStrategy) + val strategy = strategyProvider.createHttp(nodes) + nodeSwitcher.updateNodes(strategy) } override fun > send(request: Request<*, out Response<*>>, responseType: Class): T { @@ -245,16 +237,11 @@ class BalancingHttpWeb3jService( } private class NodeSwitcher( - initialNodes: List, - initialStrategy: NodeSelectionSequenceStrategy, - private val connectionSecrets: ConnectionSecrets, + initialStrategy: NodeSequenceGenerator, ) { @Volatile - private var availableNodes: List = initialNodes - - @Volatile - private var balanceStrategy: NodeSelectionSequenceStrategy = initialStrategy + private var balanceStrategy: NodeSequenceGenerator = initialStrategy @Volatile private var nodeIterator: Iterator? = null @@ -263,18 +250,14 @@ private class NodeSwitcher( private var currentNodeUrl: String? = null init { - updateNodes(initialNodes, initialStrategy) + updateNodes(initialStrategy) } @Synchronized - fun updateNodes(nodes: List, strategy: NodeSelectionSequenceStrategy) { - val saturatedNodes = nodes.saturateNodeUrls(connectionSecrets) - if (saturatedNodes.isEmpty()) return - - availableNodes = nodes + fun updateNodes(strategy: NodeSequenceGenerator) { balanceStrategy = strategy - nodeIterator = balanceStrategy.generateNodeIterator(saturatedNodes) + nodeIterator = balanceStrategy.generateNodeIterator() selectNextNode() } @@ -289,7 +272,10 @@ private class NodeSwitcher( } private fun selectNextNode() { - currentNodeUrl = nodeIterator?.next()?.saturatedUrl + val iterator = nodeIterator ?: return + if (iterator.hasNext()) { + currentNodeUrl = iterator.next().saturatedUrl + } } } diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/ethereum/Web3Api.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/ethereum/Web3Api.kt index 3b4c2ae6b1..9d646e3f22 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/ethereum/Web3Api.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/ethereum/Web3Api.kt @@ -3,7 +3,6 @@ package io.novafoundation.nova.runtime.ethereum import io.novafoundation.nova.core.ethereum.Web3Api import io.novafoundation.nova.core.ethereum.log.Topic import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain -import io.novafoundation.nova.runtime.multiNetwork.connection.ConnectionSecrets import io.novafoundation.nova.runtime.multiNetwork.connection.UpdatableNodes import io.novafoundation.nova.runtime.multiNetwork.connection.autobalance.strategy.NodeSelectionStrategyProvider import io.novasama.substrate_sdk_android.extensions.requireHexPrefix @@ -23,7 +22,6 @@ import java.util.concurrent.ScheduledExecutorService class Web3ApiFactory( private val requestExecutorService: ScheduledExecutorService = Async.defaultExecutorService(), - private val connectionSecrets: ConnectionSecrets, private val httpClient: OkHttpClient, private val strategyProvider: NodeSelectionStrategyProvider, ) { @@ -37,17 +35,21 @@ class Web3ApiFactory( ) } - fun createHttps(chainNodes: Chain.Node): Pair { - return createHttps(Chain.Nodes(Chain.Nodes.NodeSelectionStrategy.AutoBalance.ROUND_ROBIN, listOf(chainNodes))) + fun createHttps(chainNode: Chain.Node): Pair { + val nodes = Chain.Nodes( + autoBalanceStrategy = Chain.Nodes.AutoBalanceStrategy.ROUND_ROBIN, + wssNodeSelectionStrategy = Chain.Nodes.NodeSelectionStrategy.AutoBalance, + nodes = listOf(chainNode) + ) + + return createHttps(nodes) } fun createHttps(chainNodes: Chain.Nodes): Pair { val service = BalancingHttpWeb3jService( initialNodes = chainNodes, - connectionSecrets = connectionSecrets, httpClient = httpClient, strategyProvider = strategyProvider, - executorService = requestExecutorService ) val api = RealWeb3Api( diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/ext/ChainExt.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/ext/ChainExt.kt index a9fbd253ec..8d084be570 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/ext/ChainExt.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/ext/ChainExt.kt @@ -49,14 +49,11 @@ const val EVM_DEFAULT_TOKEN_DECIMALS = 18 private const val EIP_155_PREFIX = "eip155" val Chain.autoBalanceEnabled: Boolean - get() = nodes.nodeSelectionStrategy is Chain.Nodes.NodeSelectionStrategy.AutoBalance + get() = nodes.wssNodeSelectionStrategy is Chain.Nodes.NodeSelectionStrategy.AutoBalance -val Chain.autoBalanceDisabled: Boolean - get() = !autoBalanceEnabled - -val Chain.selectedNodeUrlOrNull: String? - get() = if (nodes.nodeSelectionStrategy is Chain.Nodes.NodeSelectionStrategy.SelectedNode) { - nodes.nodeSelectionStrategy.nodeUrl +val Chain.selectedUnformattedWssNodeUrlOrNull: String? + get() = if (nodes.wssNodeSelectionStrategy is Chain.Nodes.NodeSelectionStrategy.SelectedNode) { + nodes.wssNodeSelectionStrategy.unformattedNodeUrl } else { null } @@ -215,8 +212,12 @@ fun Chain.Nodes.wssNodes(): List { return nodes.filter { it.isWss } } -fun Chain.Nodes.httpNodes(): Chain.Nodes { - return copy(nodes = nodes.filter { it.isHttps }) +fun Chain.Nodes.httpNodes(): List { + return nodes.filter { it.isHttps } +} + +fun Chain.Nodes.hasHttpNodes(): Boolean { + return nodes.any { it.isHttps } } val Chain.Asset.disabled: Boolean diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/ChainRegistry.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/ChainRegistry.kt index 8329e72331..f8c0879a0a 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/ChainRegistry.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/ChainRegistry.kt @@ -134,17 +134,24 @@ class ChainRegistry( chainDao.setConnectionState(chainId, connectionState) } - suspend fun setAutoBalanceEnabled(chainId: ChainId, enabled: Boolean) { - chainDao.setNodePreferences(NodeSelectionPreferencesLocal(chainId, enabled, null)) + suspend fun setWssNodeSelectionStrategy(chainId: String, strategy: Chain.Nodes.NodeSelectionStrategy) { + return when (strategy) { + Chain.Nodes.NodeSelectionStrategy.AutoBalance -> enableAutoBalance(chainId) + is Chain.Nodes.NodeSelectionStrategy.SelectedNode -> setSelectedNode(chainId, strategy.unformattedNodeUrl) + } + } + + private suspend fun enableAutoBalance(chainId: ChainId) { + chainDao.setNodePreferences(NodeSelectionPreferencesLocal(chainId, autoBalanceEnabled = false, null)) } - suspend fun setDefaultNode(chainId: ChainId, nodeUrl: String) { + private suspend fun setSelectedNode(chainId: ChainId, unformattedNodeUrl: String) { val chain = getChain(chainId) - val chainSupportsNode = chain.nodes.nodes.any { it.unformattedUrl == nodeUrl } - require(chainSupportsNode) { "Node with url $nodeUrl is not found for chain $chainId" } + val chainSupportsNode = chain.nodes.nodes.any { it.unformattedUrl == unformattedNodeUrl } + require(chainSupportsNode) { "Node with url $unformattedNodeUrl is not found for chain $chainId" } - chainDao.setNodePreferences(NodeSelectionPreferencesLocal(chainId, false, nodeUrl)) + chainDao.setNodePreferences(NodeSelectionPreferencesLocal(chainId, false, unformattedNodeUrl)) } private suspend fun requireConnectionStateAtLeast(chainId: ChainId, state: ConnectionState) { diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/chain/mappers/DomainToLocalChainMapper.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/chain/mappers/DomainToLocalChainMapper.kt index 8048c72cf3..fb90529a77 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/chain/mappers/DomainToLocalChainMapper.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/chain/mappers/DomainToLocalChainMapper.kt @@ -13,7 +13,7 @@ import io.novafoundation.nova.core_db.model.chain.ChainLocal.ConnectionStateLoca import io.novafoundation.nova.core_db.model.chain.ChainNodeLocal import io.novafoundation.nova.core_db.model.chain.NodeSelectionPreferencesLocal import io.novafoundation.nova.runtime.ext.autoBalanceEnabled -import io.novafoundation.nova.runtime.ext.selectedNodeUrlOrNull +import io.novafoundation.nova.runtime.ext.selectedUnformattedWssNodeUrlOrNull import io.novafoundation.nova.runtime.multiNetwork.chain.mappers.utils.EVM_TRANSFER_PARAMETER import io.novafoundation.nova.runtime.multiNetwork.chain.mappers.utils.GovernanceReferendaParameters import io.novafoundation.nova.runtime.multiNetwork.chain.mappers.utils.SUBSTRATE_TRANSFER_PARAMETER @@ -157,19 +157,14 @@ fun mapNodeSelectionPreferencesToLocal(chain: Chain): NodeSelectionPreferencesLo return NodeSelectionPreferencesLocal( chainId = chain.id, autoBalanceEnabled = chain.autoBalanceEnabled, - selectedNodeUrl = chain.selectedNodeUrlOrNull + selectedNodeUrl = chain.selectedUnformattedWssNodeUrlOrNull ) } -fun mapNodeSelectionStrategyToLocal(domain: Chain): ChainLocal.NodeSelectionStrategyLocal { - val autobalanceStrategy = when (val strategy = domain.nodes.nodeSelectionStrategy) { - is Chain.Nodes.NodeSelectionStrategy.SelectedNode -> strategy.autoBalanceStrategy - is Chain.Nodes.NodeSelectionStrategy.AutoBalance -> strategy - } - - return when (autobalanceStrategy) { - Chain.Nodes.NodeSelectionStrategy.AutoBalance.ROUND_ROBIN -> ChainLocal.NodeSelectionStrategyLocal.ROUND_ROBIN - Chain.Nodes.NodeSelectionStrategy.AutoBalance.UNIFORM -> ChainLocal.NodeSelectionStrategyLocal.UNIFORM +fun mapNodeSelectionStrategyToLocal(domain: Chain): ChainLocal.AutoBalanceStrategyLocal { + return when (domain.nodes.autoBalanceStrategy) { + Chain.Nodes.AutoBalanceStrategy.ROUND_ROBIN -> ChainLocal.AutoBalanceStrategyLocal.ROUND_ROBIN + Chain.Nodes.AutoBalanceStrategy.UNIFORM -> ChainLocal.AutoBalanceStrategyLocal.UNIFORM } } diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/chain/mappers/LocalToDomainChainMapper.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/chain/mappers/LocalToDomainChainMapper.kt index aa89dd769a..81ce937317 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/chain/mappers/LocalToDomainChainMapper.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/chain/mappers/LocalToDomainChainMapper.kt @@ -16,8 +16,8 @@ import io.novafoundation.nova.core_db.model.chain.ChainExternalApiLocal import io.novafoundation.nova.core_db.model.chain.ChainExternalApiLocal.ApiType import io.novafoundation.nova.core_db.model.chain.ChainExternalApiLocal.SourceType import io.novafoundation.nova.core_db.model.chain.ChainLocal +import io.novafoundation.nova.core_db.model.chain.ChainLocal.AutoBalanceStrategyLocal import io.novafoundation.nova.core_db.model.chain.ChainLocal.ConnectionStateLocal -import io.novafoundation.nova.core_db.model.chain.ChainLocal.NodeSelectionStrategyLocal import io.novafoundation.nova.core_db.model.chain.ChainNodeLocal import io.novafoundation.nova.core_db.model.chain.JoinedChainInfo import io.novafoundation.nova.core_db.model.chain.NodeSelectionPreferencesLocal @@ -30,6 +30,7 @@ import io.novafoundation.nova.runtime.multiNetwork.chain.model.BuyProviderId import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain.ConnectionState import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain.ExternalApi +import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain.Nodes.AutoBalanceStrategy import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain.Nodes.NodeSelectionStrategy import io.novafoundation.nova.runtime.multiNetwork.chain.model.StatemineAssetId @@ -161,17 +162,23 @@ private fun mapExternalApiLocalToExternalApi(externalApiLocal: ChainExternalApiL } }.getOrNull() -private fun mapNodeSelectionFromLocal(chainLocal: ChainLocal, nodeSelectionPreferencesLocal: NodeSelectionPreferencesLocal?): NodeSelectionStrategy { - val autoBalanceStrategy = when (chainLocal.nodeSelectionStrategy) { - NodeSelectionStrategyLocal.ROUND_ROBIN -> NodeSelectionStrategy.AutoBalance.ROUND_ROBIN - NodeSelectionStrategyLocal.UNIFORM -> NodeSelectionStrategy.AutoBalance.UNIFORM - NodeSelectionStrategyLocal.UNKNOWN -> NodeSelectionStrategy.AutoBalance.ROUND_ROBIN +private fun mapAutoBalanceStrategyFromLocal(local: AutoBalanceStrategyLocal): AutoBalanceStrategy { + return when (local) { + AutoBalanceStrategyLocal.ROUND_ROBIN -> AutoBalanceStrategy.ROUND_ROBIN + AutoBalanceStrategyLocal.UNIFORM -> AutoBalanceStrategy.UNIFORM + AutoBalanceStrategyLocal.UNKNOWN -> AutoBalanceStrategy.ROUND_ROBIN } +} + +private fun mapNodeSelectionFromLocal(nodeSelectionPreferencesLocal: NodeSelectionPreferencesLocal?): NodeSelectionStrategy { + if (nodeSelectionPreferencesLocal == null) return NodeSelectionStrategy.AutoBalance + + val selectedUnformattedWssUrl = nodeSelectionPreferencesLocal.selectedUnformattedWssNodeUrl - return if (nodeSelectionPreferencesLocal?.autoBalanceEnabled == true) { - autoBalanceStrategy + return if (selectedUnformattedWssUrl != null && !nodeSelectionPreferencesLocal.autoBalanceEnabled) { + NodeSelectionStrategy.SelectedNode(selectedUnformattedWssUrl) } else { - NodeSelectionStrategy.SelectedNode(nodeSelectionPreferencesLocal?.selectedNodeUrl, autoBalanceStrategy) + NodeSelectionStrategy.AutoBalance } } @@ -207,7 +214,8 @@ fun mapChainLocalToChain( } val nodesConfig = Chain.Nodes( - nodeSelectionStrategy = mapNodeSelectionFromLocal(chainLocal, nodeSelectionPreferences), + autoBalanceStrategy = mapAutoBalanceStrategyFromLocal(chainLocal.autoBalanceStrategy), + wssNodeSelectionStrategy = mapNodeSelectionFromLocal(nodeSelectionPreferences), nodes = nodes ) diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/chain/mappers/RemoteToLocalChainMappers.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/chain/mappers/RemoteToLocalChainMappers.kt index eb4fa5cd0e..0652fef8bd 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/chain/mappers/RemoteToLocalChainMappers.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/chain/mappers/RemoteToLocalChainMappers.kt @@ -12,7 +12,7 @@ import io.novafoundation.nova.core_db.model.chain.ChainExternalApiLocal.SourceTy import io.novafoundation.nova.core_db.model.chain.ChainLocal import io.novafoundation.nova.core_db.model.chain.ChainLocal.Companion.EMPTY_CHAIN_ICON import io.novafoundation.nova.core_db.model.chain.ChainLocal.ConnectionStateLocal -import io.novafoundation.nova.core_db.model.chain.ChainLocal.NodeSelectionStrategyLocal +import io.novafoundation.nova.core_db.model.chain.ChainLocal.AutoBalanceStrategyLocal import io.novafoundation.nova.core_db.model.chain.ChainNodeLocal import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId @@ -99,11 +99,11 @@ fun mapRemoteChainToLocal( return chainLocal } -private fun mapNodeSelectionStrategyToLocal(remote: String?): NodeSelectionStrategyLocal { +private fun mapNodeSelectionStrategyToLocal(remote: String?): AutoBalanceStrategyLocal { return when (remote) { - null, "roundRobin" -> NodeSelectionStrategyLocal.ROUND_ROBIN - "uniform" -> NodeSelectionStrategyLocal.UNIFORM - else -> NodeSelectionStrategyLocal.UNKNOWN + null, "roundRobin" -> AutoBalanceStrategyLocal.ROUND_ROBIN + "uniform" -> AutoBalanceStrategyLocal.UNIFORM + else -> AutoBalanceStrategyLocal.UNKNOWN } } diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/chain/model/Chain.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/chain/model/Chain.kt index cdaf7acf91..76b61f9ae2 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/chain/model/Chain.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/chain/model/Chain.kt @@ -122,17 +122,20 @@ data class Chain( } data class Nodes( - val nodeSelectionStrategy: NodeSelectionStrategy, + val autoBalanceStrategy: AutoBalanceStrategy, + val wssNodeSelectionStrategy: NodeSelectionStrategy, val nodes: List, ) { - sealed interface NodeSelectionStrategy { + enum class AutoBalanceStrategy { + ROUND_ROBIN, UNIFORM + } - enum class AutoBalance : NodeSelectionStrategy { - ROUND_ROBIN, UNIFORM - } + sealed class NodeSelectionStrategy { + + object AutoBalance : NodeSelectionStrategy() - class SelectedNode(val nodeUrl: String?, val autoBalanceStrategy: AutoBalance) : NodeSelectionStrategy + class SelectedNode(val unformattedNodeUrl: String) : NodeSelectionStrategy() } } diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/connection/ChainConnection.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/connection/ChainConnection.kt index 3aad16a16f..23db36343b 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/connection/ChainConnection.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/connection/ChainConnection.kt @@ -2,6 +2,7 @@ package io.novafoundation.nova.runtime.multiNetwork.connection import android.util.Log import io.novafoundation.nova.common.utils.LOG_TAG +import io.novafoundation.nova.common.utils.share import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import io.novafoundation.nova.runtime.multiNetwork.connection.autobalance.NodeAutobalancer import io.novasama.substrate_sdk_android.wsrpc.SocketService @@ -17,23 +18,22 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.stateIn import javax.inject.Provider -import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.map class ChainConnectionFactory( private val externalRequirementFlow: Flow, private val nodeAutobalancer: NodeAutobalancer, private val socketServiceProvider: Provider, - private val connectionSecrets: ConnectionSecrets ) { suspend fun create(chain: Chain): ChainConnection { @@ -41,7 +41,6 @@ class ChainConnectionFactory( socketService = socketServiceProvider.get(), externalRequirementFlow = externalRequirementFlow, nodeAutobalancer = nodeAutobalancer, - connectionSecrets = connectionSecrets, initialChain = chain ) @@ -68,7 +67,6 @@ class ChainConnection internal constructor( val socketService: SocketService, private val externalRequirementFlow: Flow, private val nodeAutobalancer: NodeAutobalancer, - private val connectionSecrets: ConnectionSecrets, initialChain: Chain, ) : CoroutineScope by CoroutineScope(Dispatchers.Default), WebSocketResponseInterceptor { @@ -88,16 +86,21 @@ class ChainConnection internal constructor( ).shareIn(scope = this, started = SharingStarted.Eagerly) private val chain = MutableStateFlow(initialChain) + private val availableNodes = chain.map { it.nodes } - .shareIn(scope = this, started = SharingStarted.Eagerly, replay = 1) + .distinctUntilChanged() + .share(SharingStarted.Eagerly) - val currentUrl = chain.flatMapLatest { getNodeUrlFlow(it) } - .shareIn(scope = this, started = SharingStarted.Eagerly, replay = 1) + val currentUrl = nodeAutobalancer.connectionUrlFlow( + chainId = initialChain.id, + changeConnectionEventFlow = nodeChangeSignal, + availableNodesFlow = availableNodes, + ).share(SharingStarted.Eagerly) suspend fun setup() { socketService.setInterceptor(this) - observeCurrentNode(chain.value) + observeCurrentNode() externalRequirementFlow.onEach { if (it == ExternalRequirement.ALLOWED) { @@ -109,27 +112,16 @@ class ChainConnection internal constructor( .launchIn(this) } - private fun getNodeUrlFlow(chain: Chain): Flow { - return getAutobalancedNodeUrlFlow(chain) - } - - private fun getAutobalancedNodeUrlFlow(chain: Chain): Flow { - return nodeAutobalancer.connectionUrlFlow( - chainId = chain.id, - changeConnectionEventFlow = nodeChangeSignal, - availableNodesFlow = availableNodes, - ) - } - - private suspend fun observeCurrentNode(chain: Chain) { + private suspend fun observeCurrentNode() { + // Important - this should be awaited first before setting up externalRequirementFlow subscription + // Otherwise there might be a race between both of them val firstNodeUrl = currentUrl.first()?.saturatedUrl ?: return socketService.start(firstNodeUrl, remainPaused = true) - currentUrl - .mapNotNull { it?.saturatedUrl } + currentUrl.mapNotNull { it?.saturatedUrl } .filter { nodeUrl -> actualUrl() != nodeUrl } .onEach { nodeUrl -> socketService.switchUrl(nodeUrl) } - .onEach { nodeUrl -> Log.d(this@ChainConnection.LOG_TAG, "Switching node in ${chain.name} to $nodeUrl") } + .onEach { nodeUrl -> Log.d(this@ChainConnection.LOG_TAG, "Switching node in ${chain.value.name} to $nodeUrl") } .launchIn(this) } diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/connection/Web3ApiPool.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/connection/Web3ApiPool.kt index 75db664f28..af9a5c7dd3 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/connection/Web3ApiPool.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/connection/Web3ApiPool.kt @@ -2,7 +2,7 @@ package io.novafoundation.nova.runtime.multiNetwork.connection import io.novafoundation.nova.core.ethereum.Web3Api import io.novafoundation.nova.runtime.ethereum.Web3ApiFactory -import io.novafoundation.nova.runtime.ext.httpNodes +import io.novafoundation.nova.runtime.ext.hasHttpNodes import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain.Node.ConnectionType import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId @@ -25,19 +25,19 @@ class Web3ApiPool(private val web3ApiFactory: Web3ApiFactory) { } fun setupHttpsApi(chain: Chain): Web3Api? { - val httpNodes = chain.nodes.httpNodes() + val chainNodes = chain.nodes - if (httpNodes.nodes.isEmpty()) { + if (!chainNodes.hasHttpNodes()) { removeApi(chain.id, ConnectionType.HTTPS) return null } val (web3Api, updatableNodes) = pool.getOrPut(chain.id to ConnectionType.HTTPS) { - web3ApiFactory.createHttps(httpNodes) + web3ApiFactory.createHttps(chainNodes) } - updatableNodes?.updateNodes(httpNodes) + updatableNodes?.updateNodes(chainNodes) return web3Api } diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/connection/autobalance/NodeAutobalancer.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/connection/autobalance/NodeAutobalancer.kt index 763462829b..9b92e9c457 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/connection/autobalance/NodeAutobalancer.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/connection/autobalance/NodeAutobalancer.kt @@ -2,49 +2,47 @@ package io.novafoundation.nova.runtime.multiNetwork.connection.autobalance import android.util.Log import io.novafoundation.nova.common.utils.LOG_TAG -import io.novafoundation.nova.runtime.ext.wssNodes import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId -import io.novafoundation.nova.runtime.multiNetwork.connection.ConnectionSecrets import io.novafoundation.nova.runtime.multiNetwork.connection.NodeWithSaturatedUrl import io.novafoundation.nova.runtime.multiNetwork.connection.autobalance.strategy.NodeSelectionStrategyProvider -import io.novafoundation.nova.runtime.multiNetwork.connection.saturateNodeUrls +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.emitAll -import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.transform +import kotlinx.coroutines.flow.mapNotNull +import kotlinx.coroutines.flow.transformLatest class NodeAutobalancer( private val autobalanceStrategyProvider: NodeSelectionStrategyProvider, - private val connectionSecrets: ConnectionSecrets, ) { + @OptIn(ExperimentalCoroutinesApi::class) fun connectionUrlFlow( chainId: ChainId, changeConnectionEventFlow: Flow, availableNodesFlow: Flow, ): Flow { - return availableNodesFlow.flatMapLatest { nodesConfig -> - autobalanceStrategyProvider.strategyFlowFor(chainId, nodesConfig.nodeSelectionStrategy).transform { strategy -> - Log.d(this@NodeAutobalancer.LOG_TAG, "Using ${nodesConfig.nodeSelectionStrategy} strategy for switching nodes in $chainId") + return availableNodesFlow.transformLatest { nodesConfig -> + Log.d(this@NodeAutobalancer.LOG_TAG, "Using ${nodesConfig.wssNodeSelectionStrategy} strategy for switching nodes in $chainId") - val wssNodes = nodesConfig.wssNodes().saturateNodeUrls(connectionSecrets) + val strategy = autobalanceStrategyProvider.createWss(nodesConfig) - if (wssNodes.isEmpty()) { - Log.w(this@NodeAutobalancer.LOG_TAG, "No wss nodes available for chain $chainId") - - emit(null) - return@transform - } - - val nodeIterator = strategy.generateNodeSequence(wssNodes).iterator() + val nodeIterator = strategy.generateNodeSequence().iterator() + if (!nodeIterator.hasNext()) { + Log.w(this@NodeAutobalancer.LOG_TAG, "No wss nodes available for chain $chainId using strategy $strategy") + return@transformLatest + } - emit(nodeIterator.next()) + emit(nodeIterator.next()) - val updates = changeConnectionEventFlow.map { nodeIterator.next() } - emitAll(updates) + val updates = changeConnectionEventFlow.mapNotNull { + if (nodeIterator.hasNext()) { + nodeIterator.next() + } else { + null + } } + emitAll(updates) } } } diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/connection/autobalance/strategy/NodeSelectionSequenceStrategy.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/connection/autobalance/strategy/NodeSelectionSequenceStrategy.kt deleted file mode 100644 index 1c5ff107b1..0000000000 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/connection/autobalance/strategy/NodeSelectionSequenceStrategy.kt +++ /dev/null @@ -1,12 +0,0 @@ -package io.novafoundation.nova.runtime.multiNetwork.connection.autobalance.strategy - -import io.novafoundation.nova.runtime.multiNetwork.connection.NodeWithSaturatedUrl - -interface NodeSelectionSequenceStrategy { - - fun generateNodeSequence(defaultNodes: List): Sequence -} - -fun NodeSelectionSequenceStrategy.generateNodeIterator(defaultNodes: List): Iterator { - return generateNodeSequence(defaultNodes).iterator() -} diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/connection/autobalance/strategy/NodeSelectionStrategyProvider.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/connection/autobalance/strategy/NodeSelectionStrategyProvider.kt index 5fa99e5538..b4c72e34ca 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/connection/autobalance/strategy/NodeSelectionStrategyProvider.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/connection/autobalance/strategy/NodeSelectionStrategyProvider.kt @@ -1,30 +1,67 @@ package io.novafoundation.nova.runtime.multiNetwork.connection.autobalance.strategy -import io.novafoundation.nova.common.utils.flowOf +import io.novafoundation.nova.runtime.ext.httpNodes +import io.novafoundation.nova.runtime.ext.wssNodes +import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain.Nodes.NodeSelectionStrategy -import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId -import kotlinx.coroutines.flow.Flow +import io.novafoundation.nova.runtime.multiNetwork.connection.ConnectionSecrets +import io.novafoundation.nova.runtime.multiNetwork.connection.saturateNodeUrl +import io.novafoundation.nova.runtime.multiNetwork.connection.saturateNodeUrls -class NodeSelectionStrategyProvider { +class NodeSelectionStrategyProvider( + private val connectionSecrets: ConnectionSecrets, +) { - private val roundRobin = RoundRobinStrategy() - private val uniform = UniformStrategy() + fun createWss(config: Chain.Nodes): NodeSequenceGenerator { + return createNodeSequenceGenerator( + availableNodes = config.wssNodes(), + autobalanceStrategy = config.autoBalanceStrategy, + nodeSelectionStrategy = config.wssNodeSelectionStrategy + ) + } - fun strategyFlowFor(chainId: ChainId, default: NodeSelectionStrategy): Flow { - return flowOf { strategyFor(default) } + fun createHttp(config: Chain.Nodes): NodeSequenceGenerator { + return createNodeSequenceGenerator( + availableNodes = config.httpNodes(), + autobalanceStrategy = config.autoBalanceStrategy, + // Http nodes disregard selected wss strategy and always use auto balance + nodeSelectionStrategy = NodeSelectionStrategy.AutoBalance + ) } - fun strategyFor(config: NodeSelectionStrategy): NodeSelectionSequenceStrategy { - return when (config) { - is NodeSelectionStrategy.AutoBalance -> autobalanceStrategyFor(config) - is NodeSelectionStrategy.SelectedNode -> SelectedNodeStrategy(config.nodeUrl, autobalanceStrategyFor(config.autoBalanceStrategy)) + private fun createNodeSequenceGenerator( + availableNodes: List, + autobalanceStrategy: Chain.Nodes.AutoBalanceStrategy, + nodeSelectionStrategy: NodeSelectionStrategy, + ): NodeSequenceGenerator { + return when (nodeSelectionStrategy) { + NodeSelectionStrategy.AutoBalance -> createAutoBalanceGenerator(autobalanceStrategy, availableNodes) + is NodeSelectionStrategy.SelectedNode -> { + createSelectedNodeGenerator(nodeSelectionStrategy.unformattedNodeUrl, availableNodes) + // Fallback to auto balance in case we failed to setup a selected node strategy + ?: createAutoBalanceGenerator(autobalanceStrategy, availableNodes) + } } } - private fun autobalanceStrategyFor(config: NodeSelectionStrategy.AutoBalance): NodeSelectionSequenceStrategy { - return when (config) { - NodeSelectionStrategy.AutoBalance.ROUND_ROBIN -> roundRobin - NodeSelectionStrategy.AutoBalance.UNIFORM -> uniform + private fun createSelectedNodeGenerator( + selectedUnformattedNodeUrl: String, + availableNodes: List, + ): SelectedNodeGenerator? { + val node = availableNodes.find { it.unformattedUrl == selectedUnformattedNodeUrl } ?: return null + val saturatedNode = node.saturateNodeUrl(connectionSecrets) ?: return null + return SelectedNodeGenerator(saturatedNode) + } + + private fun createAutoBalanceGenerator( + autoBalanceStrategy: Chain.Nodes.AutoBalanceStrategy, + availableNodes: List, + ): NodeSequenceGenerator { + val saturatedUrls = availableNodes.saturateNodeUrls(connectionSecrets) + + return when (autoBalanceStrategy) { + Chain.Nodes.AutoBalanceStrategy.ROUND_ROBIN -> RoundRobinGenerator(saturatedUrls) + Chain.Nodes.AutoBalanceStrategy.UNIFORM -> UniformGenerator(saturatedUrls) } } } diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/connection/autobalance/strategy/NodeSequenceGenerator.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/connection/autobalance/strategy/NodeSequenceGenerator.kt new file mode 100644 index 0000000000..6c1a5ce30b --- /dev/null +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/connection/autobalance/strategy/NodeSequenceGenerator.kt @@ -0,0 +1,12 @@ +package io.novafoundation.nova.runtime.multiNetwork.connection.autobalance.strategy + +import io.novafoundation.nova.runtime.multiNetwork.connection.NodeWithSaturatedUrl + +interface NodeSequenceGenerator { + + fun generateNodeSequence(): Sequence +} + +fun NodeSequenceGenerator.generateNodeIterator(): Iterator { + return generateNodeSequence().iterator() +} diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/connection/autobalance/strategy/RoundRobinStrategy.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/connection/autobalance/strategy/RoundRobinGenerator.kt similarity index 50% rename from runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/connection/autobalance/strategy/RoundRobinStrategy.kt rename to runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/connection/autobalance/strategy/RoundRobinGenerator.kt index a5379f22c3..f36521febd 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/connection/autobalance/strategy/RoundRobinStrategy.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/connection/autobalance/strategy/RoundRobinGenerator.kt @@ -3,9 +3,11 @@ package io.novafoundation.nova.runtime.multiNetwork.connection.autobalance.strat import io.novafoundation.nova.common.utils.cycle import io.novafoundation.nova.runtime.multiNetwork.connection.NodeWithSaturatedUrl -class RoundRobinStrategy : NodeSelectionSequenceStrategy { +class RoundRobinGenerator( + private val availableNodes: List, +) : NodeSequenceGenerator { - override fun generateNodeSequence(defaultNodes: List): Sequence { - return defaultNodes.cycle() + override fun generateNodeSequence(): Sequence { + return availableNodes.cycle() } } diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/connection/autobalance/strategy/UniformStrategy.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/connection/autobalance/strategy/SelectedNodeGenerator.kt similarity index 50% rename from runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/connection/autobalance/strategy/UniformStrategy.kt rename to runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/connection/autobalance/strategy/SelectedNodeGenerator.kt index 8f92592096..959d2139ba 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/connection/autobalance/strategy/UniformStrategy.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/connection/autobalance/strategy/SelectedNodeGenerator.kt @@ -3,9 +3,11 @@ package io.novafoundation.nova.runtime.multiNetwork.connection.autobalance.strat import io.novafoundation.nova.common.utils.cycle import io.novafoundation.nova.runtime.multiNetwork.connection.NodeWithSaturatedUrl -class UniformStrategy : NodeSelectionSequenceStrategy { +class SelectedNodeGenerator( + private val selectedNode: NodeWithSaturatedUrl, +) : NodeSequenceGenerator { - override fun generateNodeSequence(defaultNodes: List): Sequence { - return defaultNodes.shuffled().cycle() + override fun generateNodeSequence(): Sequence { + return listOf(selectedNode).cycle() } } diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/connection/autobalance/strategy/SelectedNodeStrategy.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/connection/autobalance/strategy/SelectedNodeStrategy.kt deleted file mode 100644 index c322fb8473..0000000000 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/connection/autobalance/strategy/SelectedNodeStrategy.kt +++ /dev/null @@ -1,23 +0,0 @@ -package io.novafoundation.nova.runtime.multiNetwork.connection.autobalance.strategy - -import io.novafoundation.nova.runtime.multiNetwork.connection.NodeWithSaturatedUrl - -class SelectedNodeStrategy( - private val selectedUrl: String?, - private val fallbackStrategy: NodeSelectionSequenceStrategy -) : NodeSelectionSequenceStrategy { - - override fun generateNodeSequence(defaultNodes: List): Sequence { - if (selectedUrl == null) { - return fallbackStrategy.generateNodeSequence(defaultNodes) - } - - val selectedNode = defaultNodes.find { it.node.unformattedUrl == selectedUrl } - - return if (selectedNode == null) { - fallbackStrategy.generateNodeSequence(defaultNodes) - } else { - sequenceOf(selectedNode) - } - } -} diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/connection/autobalance/strategy/UniformGenerator.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/connection/autobalance/strategy/UniformGenerator.kt new file mode 100644 index 0000000000..0fdad74534 --- /dev/null +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/connection/autobalance/strategy/UniformGenerator.kt @@ -0,0 +1,13 @@ +package io.novafoundation.nova.runtime.multiNetwork.connection.autobalance.strategy + +import io.novafoundation.nova.common.utils.cycle +import io.novafoundation.nova.runtime.multiNetwork.connection.NodeWithSaturatedUrl + +class UniformGenerator( + private val availabelNodes: List, +) : NodeSequenceGenerator { + + override fun generateNodeSequence(): Sequence { + return availabelNodes.shuffled().cycle() + } +} diff --git a/runtime/src/test/java/io/novafoundation/nova/runtime/multiNetwork/connection/autobalance/NodeAutobalancerTest.kt b/runtime/src/test/java/io/novafoundation/nova/runtime/multiNetwork/connection/autobalance/NodeAutobalancerTest.kt deleted file mode 100644 index 1bc599fe7d..0000000000 --- a/runtime/src/test/java/io/novafoundation/nova/runtime/multiNetwork/connection/autobalance/NodeAutobalancerTest.kt +++ /dev/null @@ -1,90 +0,0 @@ -package io.novafoundation.nova.runtime.multiNetwork.connection.autobalance - -import io.novafoundation.nova.common.utils.second -import io.novafoundation.nova.common.utils.singleReplaySharedFlow -import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain -import io.novafoundation.nova.runtime.multiNetwork.connection.ConnectionSecrets -import io.novafoundation.nova.runtime.multiNetwork.connection.autobalance.strategy.NodeSelectionStrategyProvider -import io.novafoundation.nova.runtime.multiNetwork.connection.autobalance.strategy.RoundRobinStrategy -import io.novafoundation.nova.test_shared.CoroutineTest -import io.novafoundation.nova.test_shared.any -import io.novafoundation.nova.test_shared.whenever -import io.novasama.substrate_sdk_android.wsrpc.state.SocketStateMachine -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.flowOf -import org.junit.Assert.assertEquals -import org.junit.Before -import org.junit.Ignore -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.Mock -import org.mockito.junit.MockitoJUnitRunner - -@RunWith(MockitoJUnitRunner::class) -@Ignore("TODO: fix test") -// TODO valentun: New coroutine test API had some changes which broke those tests. The problem is caused by the implementation of the -// balancingNodeFlow, which use SharedFlow + emitting coroutine, which is not a quite good way to do so. I figured a way to rewrite it in cold way using runningReduce -// but I do not want to make such complex changes right before release. Gonna fix after 3.7.0 -class NodeAutobalancerTest : CoroutineTest() { - - @Mock - lateinit var strategyProvider: NodeSelectionStrategyProvider - - lateinit var autobalancer: NodeAutobalancer - - private val nodes = generateNodes() - private val nodeSelectionStrategy = Chain.Nodes.NodeSelectionStrategy.AutoBalance.ROUND_ROBIN - - private val nodesFlow = MutableStateFlow(Chain.Nodes(nodeSelectionStrategy, nodes)) - private val stateFlow = singleReplaySharedFlow() - private val connectionSecrets = ConnectionSecrets(emptyMap()) - - @Before - fun setup() { - autobalancer = NodeAutobalancer(strategyProvider, connectionSecrets) - whenever(strategyProvider.strategyFlowFor(any(), nodeSelectionStrategy)) - .thenReturn(flowOf(RoundRobinStrategy())) - } - - @Test - fun shouldSelectInitialNode() = runCoroutineTest { - val nodeFlow = nodeFlow() - - val initial = nodeFlow.first() - - assertEquals(nodes.first(), initial) - } - - @Test - fun shouldSelectNodeOnReconnectState() = runCoroutineTest { - val nodeFlow = nodeFlow() - stateFlow.emit(Unit) - - assertEquals(nodes.second(), nodeFlow.first()) - } - - @Test - fun shouldNotAutobalanceIfNotEnoughAttempts() = runCoroutineTest { - val nodeFlow = nodeFlow() - stateFlow.emit(Unit) - - assertEquals(nodes.first(), nodeFlow.first()) - } - - private fun generateNodes() = (1..10).map { - Chain.Node(unformattedUrl = it.toString(), name = it.toString(), chainId = "test", orderId = 0, isCustom = false) - } - - private fun nodeFlow() = autobalancer.connectionUrlFlow( - chainId = "test", - changeConnectionEventFlow = stateFlow, - availableNodesFlow = nodesFlow - ) - - private fun triggerState(attempt: Int) = SocketStateMachine.State.WaitingForReconnect( - url = "test", - attempt = attempt, - pendingSendables = emptySet() - ) -} diff --git a/runtime/src/test/java/io/novafoundation/nova/runtime/multiNetwork/connection/autobalance/strategy/RoundRobinStrategyTest.kt b/runtime/src/test/java/io/novafoundation/nova/runtime/multiNetwork/connection/autobalance/strategy/RoundRobinStrategyTest.kt index 4a211bda77..3c21fcfe1a 100644 --- a/runtime/src/test/java/io/novafoundation/nova/runtime/multiNetwork/connection/autobalance/strategy/RoundRobinStrategyTest.kt +++ b/runtime/src/test/java/io/novafoundation/nova/runtime/multiNetwork/connection/autobalance/strategy/RoundRobinStrategyTest.kt @@ -7,7 +7,6 @@ import org.junit.Test class RoundRobinStrategyTest { - private val strategy = RoundRobinStrategy() private val nodes = listOf( createFakeNode("1"), @@ -15,9 +14,11 @@ class RoundRobinStrategyTest { createFakeNode("3") ) + private val strategy = RoundRobinGenerator(nodes) + @Test fun `collections should have the same sequence`() { - val iterator = strategy.generateNodeSequence(nodes) + val iterator = strategy.generateNodeSequence() .iterator() nodes.forEach { assertEquals(it, iterator.next()) } @@ -25,7 +26,7 @@ class RoundRobinStrategyTest { @Test fun `sequence should be looped`() { - val iterator = strategy.generateNodeSequence(nodes) + val iterator = strategy.generateNodeSequence() .iterator() repeat(nodes.size) { iterator.next() } From bc21bfc17d3956091ca74541aff0bd2c93173251 Mon Sep 17 00:00:00 2001 From: valentunn <70131744+valentunn@users.noreply.github.com> Date: Mon, 7 Oct 2024 10:42:48 +0300 Subject: [PATCH 09/23] Ledger external signing (#1679) * Ledger external signing * Fix tests --- .../nova/MetadataShortenerTest.kt | 2 +- .../nova/MoonbaseSendIntagrationTest.kt | 2 +- build.gradle | 2 +- .../data/extrinsic/RealExtrinsicService.kt | 11 +- .../browser/main/DAppBrowserViewModel.kt | 2 +- .../web3/polkadotJs/model/SignerPayload.kt | 4 + .../states/DefaultPolkadotJsState.kt | 4 +- .../web3/states/hostApi/ConfirmTxResponse.kt | 2 +- .../model/ExternalSignCommunicator.kt | 2 +- .../polkadot/PolkadotSignPayload.kt | 2 + .../polkadot/PolkadotSignerResult.kt | 2 +- .../di/ExternalSignFeatureDependencies.kt | 3 + .../di/modules/sign/PolkadotSignModule.kt | 7 +- .../sign/polkadot/DAppParsedExtrinsic.kt | 3 +- .../PolkadotExternalSignInteractor.kt | 120 ++++++++++++++---- .../requests/polkadot/PolkadotSignRequest.kt | 2 +- .../extrinsic/ExtrinsicBuilderFactory.kt | 13 +- .../extrinsic/multi/ExtrinsicSplitter.kt | 3 +- .../extrinsic/signer/MetadataHashSigning.kt | 16 +++ 19 files changed, 143 insertions(+), 59 deletions(-) create mode 100644 runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/signer/MetadataHashSigning.kt diff --git a/app/src/androidTest/java/io/novafoundation/nova/MetadataShortenerTest.kt b/app/src/androidTest/java/io/novafoundation/nova/MetadataShortenerTest.kt index e44877fb79..69c6c7cccb 100644 --- a/app/src/androidTest/java/io/novafoundation/nova/MetadataShortenerTest.kt +++ b/app/src/androidTest/java/io/novafoundation/nova/MetadataShortenerTest.kt @@ -70,7 +70,7 @@ class MetadataShortenerTest : BaseIntegrationTest() { extrinsicBuilder.nativeTransfer(accountId = signer.accountId, amount = BigInteger.ONE) extrinsicBuilder.systemRemark(remark = byteArrayOf(1, 2, 3)) - extrinsicBuilder.build() + extrinsicBuilder.buildExtrinsic() } @Test diff --git a/app/src/androidTest/java/io/novafoundation/nova/MoonbaseSendIntagrationTest.kt b/app/src/androidTest/java/io/novafoundation/nova/MoonbaseSendIntagrationTest.kt index c0b6c73744..773e2ef19c 100644 --- a/app/src/androidTest/java/io/novafoundation/nova/MoonbaseSendIntagrationTest.kt +++ b/app/src/androidTest/java/io/novafoundation/nova/MoonbaseSendIntagrationTest.kt @@ -89,7 +89,7 @@ class MoonbaseSendIntagrationTest { val extrinsic = extrinsicBuilderFactory.create(chain, signer, accountId) .nativeTransfer(accountId, chain.utilityAsset.planksFromAmount(BigDecimal.ONE), keepAlive = true) - .build() + .buildExtrinsic().extrinsicHex val hash = rpcCalls.submitExtrinsic(chain.id, extrinsic) diff --git a/build.gradle b/build.gradle index 896b43ca14..0c680b1617 100644 --- a/build.gradle +++ b/build.gradle @@ -51,7 +51,7 @@ buildscript { web3jVersion = '4.9.5' - substrateSdkVersion = '2.1.4' + substrateSdkVersion = '2.2.0' gifVersion = '1.2.19' diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/extrinsic/RealExtrinsicService.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/extrinsic/RealExtrinsicService.kt index 67c9445875..7f7c00370a 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/extrinsic/RealExtrinsicService.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/extrinsic/RealExtrinsicService.kt @@ -37,12 +37,12 @@ import io.novafoundation.nova.runtime.network.rpc.RpcCalls import io.novasama.substrate_sdk_android.runtime.definitions.types.fromHex import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.Extrinsic import io.novasama.substrate_sdk_android.runtime.extrinsic.ExtrinsicBuilder +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.first import java.math.BigInteger -import kotlinx.coroutines.CoroutineScope class RealExtrinsicService( private val rpcCalls: RpcCalls, @@ -107,7 +107,8 @@ class RealExtrinsicService( ): FeeResponse { val extrinsic = extrinsicBuilderFactory.createForFee(getFeeSigner(chain, origin), chain) .also { it.formExtrinsic() } - .build(submissionOptions.batchMode) + .buildExtrinsic(submissionOptions.batchMode) + .extrinsicHex return rpcCalls.getExtrinsicFee(chain, extrinsic) } @@ -121,7 +122,7 @@ class RealExtrinsicService( val signer = getFeeSigner(chain, origin) val extrinsicBuilder = extrinsicBuilderFactory.createForFee(signer, chain) extrinsicBuilder.formExtrinsic() - val extrinsic = extrinsicBuilder.build(submissionOptions.batchMode) + val extrinsic = extrinsicBuilder.buildExtrinsic(submissionOptions.batchMode).extrinsicHex return estimateFee(chain, extrinsic, signer, submissionOptions) } @@ -227,7 +228,7 @@ class RealExtrinsicService( feePayment.modifyExtrinsic(extrinsicBuilder) - extrinsicBuilder.build(submissionOptions.batchMode) + extrinsicBuilder.buildExtrinsic(submissionOptions.batchMode).extrinsicHex } extrinsicsToSubmit @@ -254,7 +255,7 @@ class RealExtrinsicService( feePayment.modifyExtrinsic(extrinsicBuilder) - val extrinsic = extrinsicBuilder.build(submissionOptions.batchMode) + val extrinsic = extrinsicBuilder.buildExtrinsic(submissionOptions.batchMode).extrinsicHex return SubmissionRaw(extrinsic, submissionOrigin) } diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/browser/main/DAppBrowserViewModel.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/browser/main/DAppBrowserViewModel.kt index fa8ea65ae5..6dbf8a672d 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/browser/main/DAppBrowserViewModel.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/browser/main/DAppBrowserViewModel.kt @@ -137,7 +137,7 @@ class DAppBrowserViewModel( return when (response) { is ExternalSignCommunicator.Response.Rejected -> ConfirmTxResponse.Rejected(response.requestId) - is ExternalSignCommunicator.Response.Signed -> ConfirmTxResponse.Signed(response.requestId, response.signature) + is ExternalSignCommunicator.Response.Signed -> ConfirmTxResponse.Signed(response.requestId, response.signature, response.modifiedTransaction) is ExternalSignCommunicator.Response.SigningFailed -> ConfirmTxResponse.SigningFailed(response.requestId, response.shouldPresent) is ExternalSignCommunicator.Response.Sent -> ConfirmTxResponse.Sent(response.requestId, response.txHash) } diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/polkadotJs/model/SignerPayload.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/polkadotJs/model/SignerPayload.kt index c2f6717620..c3034aaebe 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/polkadotJs/model/SignerPayload.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/polkadotJs/model/SignerPayload.kt @@ -18,6 +18,8 @@ sealed class SignerPayload { val specVersion: String, val tip: String, val transactionVersion: String, + val metadataHash: String?, + val withSignedTransaction: Boolean?, val signedExtensions: List, val version: Int ) : SignerPayload() @@ -60,12 +62,14 @@ fun mapPolkadotJsSignerPayloadToPolkadotPayload(signerPayload: SignerPayload): P blockNumber = blockNumber, era = era, genesisHash = genesisHash, + metadataHash = metadataHash, method = method, nonce = nonce, specVersion = specVersion, tip = tip, transactionVersion = transactionVersion, signedExtensions = signedExtensions, + withSignedTransaction = withSignedTransaction, version = version ) } diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/polkadotJs/states/DefaultPolkadotJsState.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/polkadotJs/states/DefaultPolkadotJsState.kt index 7d60144ec3..5a190e25c0 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/polkadotJs/states/DefaultPolkadotJsState.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/polkadotJs/states/DefaultPolkadotJsState.kt @@ -7,7 +7,6 @@ import io.novafoundation.nova.feature_dapp_impl.R import io.novafoundation.nova.feature_dapp_impl.domain.DappInteractor import io.novafoundation.nova.feature_dapp_impl.domain.browser.polkadotJs.PolkadotJsExtensionInteractor import io.novafoundation.nova.feature_dapp_impl.web3.polkadotJs.PolkadotJsTransportRequest -import io.novafoundation.nova.feature_external_sign_api.model.signPayload.polkadot.PolkadotSignerResult import io.novafoundation.nova.feature_dapp_impl.web3.polkadotJs.model.mapPolkadotJsSignerPayloadToPolkadotPayload import io.novafoundation.nova.feature_dapp_impl.web3.session.Web3Session import io.novafoundation.nova.feature_dapp_impl.web3.states.BaseState @@ -17,6 +16,7 @@ import io.novafoundation.nova.feature_dapp_impl.web3.states.Web3StateMachineHost import io.novafoundation.nova.feature_dapp_impl.web3.states.Web3StateMachineHost.NotAuthorizedException import io.novafoundation.nova.feature_dapp_impl.web3.states.hostApi.ConfirmTxResponse import io.novafoundation.nova.feature_external_sign_api.model.signPayload.ExternalSignRequest +import io.novafoundation.nova.feature_external_sign_api.model.signPayload.polkadot.PolkadotSignerResult import kotlinx.coroutines.flow.flowOf class DefaultPolkadotJsState( @@ -75,7 +75,7 @@ class DefaultPolkadotJsState( when (val response = hostApi.confirmTx(signRequest)) { is ConfirmTxResponse.Rejected -> request.reject(NotAuthorizedException) is ConfirmTxResponse.Sent -> throw IllegalStateException("Unexpected 'Sent' response for PolkadotJs extension") - is ConfirmTxResponse.Signed -> request.accept(PolkadotSignerResult(response.requestId, response.signature)) + is ConfirmTxResponse.Signed -> request.accept(PolkadotSignerResult(response.requestId, response.signature, response.modifiedTransaction)) is ConfirmTxResponse.SigningFailed -> { if (response.shouldPresent) hostApi.showError(resourceManager.getString(R.string.dapp_sign_extrinsic_failed)) diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/states/hostApi/ConfirmTxResponse.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/states/hostApi/ConfirmTxResponse.kt index 792a14ca54..9466a9d194 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/states/hostApi/ConfirmTxResponse.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/states/hostApi/ConfirmTxResponse.kt @@ -11,7 +11,7 @@ sealed class ConfirmTxResponse : Parcelable { class Rejected(override val requestId: String) : ConfirmTxResponse() @Parcelize - class Signed(override val requestId: String, val signature: String) : ConfirmTxResponse() + class Signed(override val requestId: String, val signature: String, val modifiedTransaction: String?) : ConfirmTxResponse() @Parcelize class Sent(override val requestId: String, val txHash: String) : ConfirmTxResponse() diff --git a/feature-external-sign-api/src/main/java/io/novafoundation/nova/feature_external_sign_api/model/ExternalSignCommunicator.kt b/feature-external-sign-api/src/main/java/io/novafoundation/nova/feature_external_sign_api/model/ExternalSignCommunicator.kt index c3e8a76fbf..d324c7bfaf 100644 --- a/feature-external-sign-api/src/main/java/io/novafoundation/nova/feature_external_sign_api/model/ExternalSignCommunicator.kt +++ b/feature-external-sign-api/src/main/java/io/novafoundation/nova/feature_external_sign_api/model/ExternalSignCommunicator.kt @@ -23,7 +23,7 @@ interface ExternalSignCommunicator : ExternalSignRequester, ExternalSignResponde class Rejected(override val requestId: String) : Response() @Parcelize - class Signed(override val requestId: String, val signature: String) : Response() + class Signed(override val requestId: String, val signature: String, val modifiedTransaction: String? = null) : Response() @Parcelize class Sent(override val requestId: String, val txHash: String) : Response() diff --git a/feature-external-sign-api/src/main/java/io/novafoundation/nova/feature_external_sign_api/model/signPayload/polkadot/PolkadotSignPayload.kt b/feature-external-sign-api/src/main/java/io/novafoundation/nova/feature_external_sign_api/model/signPayload/polkadot/PolkadotSignPayload.kt index 2085daa013..88b221ec0d 100644 --- a/feature-external-sign-api/src/main/java/io/novafoundation/nova/feature_external_sign_api/model/signPayload/polkadot/PolkadotSignPayload.kt +++ b/feature-external-sign-api/src/main/java/io/novafoundation/nova/feature_external_sign_api/model/signPayload/polkadot/PolkadotSignPayload.kt @@ -19,6 +19,8 @@ sealed class PolkadotSignPayload : Parcelable { val specVersion: String, val tip: String, val transactionVersion: String, + val metadataHash: String?, + val withSignedTransaction: Boolean?, val signedExtensions: List, val version: Int ) : PolkadotSignPayload() diff --git a/feature-external-sign-api/src/main/java/io/novafoundation/nova/feature_external_sign_api/model/signPayload/polkadot/PolkadotSignerResult.kt b/feature-external-sign-api/src/main/java/io/novafoundation/nova/feature_external_sign_api/model/signPayload/polkadot/PolkadotSignerResult.kt index 0847b687d2..ea5b4601a5 100644 --- a/feature-external-sign-api/src/main/java/io/novafoundation/nova/feature_external_sign_api/model/signPayload/polkadot/PolkadotSignerResult.kt +++ b/feature-external-sign-api/src/main/java/io/novafoundation/nova/feature_external_sign_api/model/signPayload/polkadot/PolkadotSignerResult.kt @@ -1,3 +1,3 @@ package io.novafoundation.nova.feature_external_sign_api.model.signPayload.polkadot -class PolkadotSignerResult(val id: String, val signature: String) +class PolkadotSignerResult(val id: String, val signature: String, val signedTransaction: String?) diff --git a/feature-external-sign-impl/src/main/java/io/novafoundation/nova/feature_external_sign_impl/di/ExternalSignFeatureDependencies.kt b/feature-external-sign-impl/src/main/java/io/novafoundation/nova/feature_external_sign_impl/di/ExternalSignFeatureDependencies.kt index b02246d59c..4a496c133e 100644 --- a/feature-external-sign-impl/src/main/java/io/novafoundation/nova/feature_external_sign_impl/di/ExternalSignFeatureDependencies.kt +++ b/feature-external-sign-impl/src/main/java/io/novafoundation/nova/feature_external_sign_impl/di/ExternalSignFeatureDependencies.kt @@ -18,6 +18,7 @@ import io.novafoundation.nova.feature_wallet_api.domain.interfaces.WalletReposit import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeLoaderMixin import io.novafoundation.nova.runtime.di.ExtrinsicSerialization import io.novafoundation.nova.runtime.ethereum.gas.GasPriceProviderFactory +import io.novafoundation.nova.runtime.extrinsic.metadata.MetadataShortenerService import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.network.rpc.RpcCalls import okhttp3.OkHttpClient @@ -64,4 +65,6 @@ interface ExternalSignFeatureDependencies { val gasPriceProviderFactory: GasPriceProviderFactory val rpcCalls: RpcCalls + + val metadataShortenerService: MetadataShortenerService } diff --git a/feature-external-sign-impl/src/main/java/io/novafoundation/nova/feature_external_sign_impl/di/modules/sign/PolkadotSignModule.kt b/feature-external-sign-impl/src/main/java/io/novafoundation/nova/feature_external_sign_impl/di/modules/sign/PolkadotSignModule.kt index 9c757dbc5e..97854c1df7 100644 --- a/feature-external-sign-impl/src/main/java/io/novafoundation/nova/feature_external_sign_impl/di/modules/sign/PolkadotSignModule.kt +++ b/feature-external-sign-impl/src/main/java/io/novafoundation/nova/feature_external_sign_impl/di/modules/sign/PolkadotSignModule.kt @@ -11,6 +11,7 @@ import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepos import io.novafoundation.nova.feature_external_sign_impl.domain.sign.polkadot.PolkadotSignInteractorFactory import io.novafoundation.nova.feature_wallet_api.domain.interfaces.TokenRepository import io.novafoundation.nova.runtime.di.ExtrinsicSerialization +import io.novafoundation.nova.runtime.extrinsic.metadata.MetadataShortenerService import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry @Module @@ -25,7 +26,8 @@ class PolkadotSignModule { tokenRepository: TokenRepository, @ExtrinsicSerialization extrinsicGson: Gson, addressIconGenerator: AddressIconGenerator, - signerProvider: SignerProvider + signerProvider: SignerProvider, + metadataShortenerService: MetadataShortenerService ) = PolkadotSignInteractorFactory( extrinsicService = extrinsicService, chainRegistry = chainRegistry, @@ -33,6 +35,7 @@ class PolkadotSignModule { tokenRepository = tokenRepository, extrinsicGson = extrinsicGson, addressIconGenerator = addressIconGenerator, - signerProvider = signerProvider + signerProvider = signerProvider, + metadataShortenerService = metadataShortenerService ) } diff --git a/feature-external-sign-impl/src/main/java/io/novafoundation/nova/feature_external_sign_impl/domain/sign/polkadot/DAppParsedExtrinsic.kt b/feature-external-sign-impl/src/main/java/io/novafoundation/nova/feature_external_sign_impl/domain/sign/polkadot/DAppParsedExtrinsic.kt index 9adf25a405..214abbcd06 100644 --- a/feature-external-sign-impl/src/main/java/io/novafoundation/nova/feature_external_sign_impl/domain/sign/polkadot/DAppParsedExtrinsic.kt +++ b/feature-external-sign-impl/src/main/java/io/novafoundation/nova/feature_external_sign_impl/domain/sign/polkadot/DAppParsedExtrinsic.kt @@ -4,7 +4,7 @@ import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.Era import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.Extrinsic import java.math.BigInteger -class DAppParsedExtrinsic( +data class DAppParsedExtrinsic( val address: String, val nonce: BigInteger, val specVersion: Int, @@ -13,5 +13,6 @@ class DAppParsedExtrinsic( val era: Era, val blockHash: ByteArray, val tip: BigInteger, + val metadataHash: ByteArray?, val call: Extrinsic.EncodingInstance.CallRepresentation ) diff --git a/feature-external-sign-impl/src/main/java/io/novafoundation/nova/feature_external_sign_impl/domain/sign/polkadot/PolkadotExternalSignInteractor.kt b/feature-external-sign-impl/src/main/java/io/novafoundation/nova/feature_external_sign_impl/domain/sign/polkadot/PolkadotExternalSignInteractor.kt index f5be17d291..bd36f264a4 100644 --- a/feature-external-sign-impl/src/main/java/io/novafoundation/nova/feature_external_sign_impl/domain/sign/polkadot/PolkadotExternalSignInteractor.kt +++ b/feature-external-sign-impl/src/main/java/io/novafoundation/nova/feature_external_sign_impl/domain/sign/polkadot/PolkadotExternalSignInteractor.kt @@ -7,6 +7,7 @@ import io.novafoundation.nova.common.address.createAddressModel import io.novafoundation.nova.common.utils.asHexString import io.novafoundation.nova.common.utils.bigIntegerFromHex import io.novafoundation.nova.common.utils.intFromHex +import io.novafoundation.nova.common.utils.singleReplaySharedFlow import io.novafoundation.nova.common.validation.EmptyValidationSystem import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService import io.novafoundation.nova.feature_account_api.data.mappers.mapChainToUi @@ -28,8 +29,10 @@ import io.novafoundation.nova.feature_wallet_api.domain.model.Token import io.novafoundation.nova.runtime.ext.accountIdOf import io.novafoundation.nova.runtime.ext.utilityAsset import io.novafoundation.nova.runtime.extrinsic.CustomSignedExtensions +import io.novafoundation.nova.runtime.extrinsic.metadata.MetadataShortenerService import io.novafoundation.nova.runtime.extrinsic.signer.FeeSigner import io.novafoundation.nova.runtime.extrinsic.signer.NovaSigner +import io.novafoundation.nova.runtime.extrinsic.signer.generateMetadataProofWithSignerRestrictions import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import io.novafoundation.nova.runtime.multiNetwork.getChainOrNull @@ -40,8 +43,10 @@ import io.novasama.substrate_sdk_android.runtime.definitions.types.fromHex import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.EraType import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.Extrinsic.EncodingInstance.CallRepresentation import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.GenericCall +import io.novasama.substrate_sdk_android.runtime.extrinsic.CheckMetadataHash import io.novasama.substrate_sdk_android.runtime.extrinsic.ExtrinsicBuilder import io.novasama.substrate_sdk_android.runtime.extrinsic.Nonce +import io.novasama.substrate_sdk_android.runtime.extrinsic.signer.SendableExtrinsic import io.novasama.substrate_sdk_android.runtime.extrinsic.signer.SignerPayloadRaw import io.novasama.substrate_sdk_android.runtime.extrinsic.signer.fromHex import io.novasama.substrate_sdk_android.runtime.extrinsic.signer.fromUtf8 @@ -50,6 +55,7 @@ import io.novasama.substrate_sdk_android.wsrpc.request.runtime.chain.RuntimeVers import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.emitAll +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flow import kotlinx.coroutines.withContext @@ -60,6 +66,7 @@ class PolkadotSignInteractorFactory( private val tokenRepository: TokenRepository, private val extrinsicGson: Gson, private val addressIconGenerator: AddressIconGenerator, + private val metadataShortenerService: MetadataShortenerService, private val signerProvider: SignerProvider, ) { @@ -72,7 +79,8 @@ class PolkadotSignInteractorFactory( addressIconGenerator = addressIconGenerator, request = request, wallet = wallet, - signerProvider = signerProvider + signerProvider = signerProvider, + metadataShortenerService = metadataShortenerService ) } @@ -84,12 +92,15 @@ class PolkadotExternalSignInteractor( private val addressIconGenerator: AddressIconGenerator, private val request: ExternalSignRequest.Polkadot, private val signerProvider: SignerProvider, + private val metadataShortenerService: MetadataShortenerService, wallet: ExternalSignWallet, accountRepository: AccountRepository ) : BaseExternalSignInteractor(accountRepository, wallet, signerProvider) { private val signPayload = request.payload + private val actualParsedExtrinsic = singleReplaySharedFlow() + override val validationSystem: ConfirmDAppOperationValidationSystem = EmptyValidationSystem() override suspend fun createAccountAddressModel(): AddressModel { @@ -126,8 +137,8 @@ class PolkadotExternalSignInteractor( is PolkadotSignPayload.Raw -> signBytes(signPayload) } }.fold( - onSuccess = { signature -> - ExternalSignCommunicator.Response.Signed(request.id, signature) + onSuccess = { signedResult -> + ExternalSignCommunicator.Response.Signed(request.id, signedResult.signature, signedResult.modifiedTransaction) }, onFailure = { error -> error.failedSigningIfNotCancelled(request.id) @@ -137,7 +148,7 @@ class PolkadotExternalSignInteractor( override suspend fun readableOperationContent(): String = withContext(Dispatchers.Default) { when (signPayload) { - is PolkadotSignPayload.Json -> readableExtrinsicContent(signPayload) + is PolkadotSignPayload.Json -> readableExtrinsicContent() is PolkadotSignPayload.Raw -> readableBytesContent(signPayload) } } @@ -148,29 +159,22 @@ class PolkadotExternalSignInteractor( val chain = signPayload.chainOrNull() ?: return@withContext null val signer = signPayload.feeSigner() - val extrinsicBuilder = signPayload.toExtrinsicBuilderWithoutCall(signer) - val runtime = chainRegistry.getRuntime(chain.id) + val (extrinsic, _, parsedExtrinsic) = signPayload.analyzeAndSign(signer) - val extrinsic = when (val callRepresentation = signPayload.callRepresentation(runtime)) { - is CallRepresentation.Instance -> extrinsicBuilder.call(callRepresentation.call).build() - is CallRepresentation.Bytes -> extrinsicBuilder.build(rawCallBytes = callRepresentation.bytes) - } + actualParsedExtrinsic.emit(parsedExtrinsic) - extrinsicService.estimateFee(chain, extrinsic, signer) + extrinsicService.estimateFee(chain, extrinsic.extrinsicHex, signer) } private fun readableBytesContent(signBytesPayload: PolkadotSignPayload.Raw): String { return signBytesPayload.data } - private suspend fun readableExtrinsicContent(extrinsicPayload: PolkadotSignPayload.Json): String { - val runtime = chainRegistry.getRuntime(extrinsicPayload.chain().id) - val parsedExtrinsic = parseDAppExtrinsic(runtime, extrinsicPayload) - - return extrinsicGson.toJson(parsedExtrinsic) + private suspend fun readableExtrinsicContent(): String { + return extrinsicGson.toJson(actualParsedExtrinsic.first()) } - private suspend fun signBytes(signBytesPayload: PolkadotSignPayload.Raw): String { + private suspend fun signBytes(signBytesPayload: PolkadotSignPayload.Raw): SignedResult { // assumption - only substrate dApps val substrateAccountId = signBytesPayload.address.toAccountId() @@ -181,18 +185,17 @@ class PolkadotExternalSignInteractor( SignerPayloadRaw.fromUtf8(signBytesPayload.data, substrateAccountId) } - return signer.signRaw(payload).asHexString() + val signature = signer.signRaw(payload).asHexString() + return SignedResult(signature, modifiedTransaction = null) } - private suspend fun signExtrinsic(extrinsicPayload: PolkadotSignPayload.Json): String { - val runtime = chainRegistry.getRuntime(extrinsicPayload.chain().id) + private suspend fun signExtrinsic(extrinsicPayload: PolkadotSignPayload.Json): SignedResult { val signer = resolveWalletSigner() - val extrinsicBuilder = extrinsicPayload.toExtrinsicBuilderWithoutCall(signer) + val (extrinsic, modifiedOriginal) = extrinsicPayload.analyzeAndSign(signer) - return when (val callRepresentation = extrinsicPayload.callRepresentation(runtime)) { - is CallRepresentation.Instance -> extrinsicBuilder.call(callRepresentation.call).buildSignature() - is CallRepresentation.Bytes -> extrinsicBuilder.buildSignature(rawCallBytes = callRepresentation.bytes) - } + val modifiedTx = if (modifiedOriginal) extrinsic.extrinsicHex else null + + return SignedResult(extrinsic.signatureHex, modifiedTx) } private suspend fun PolkadotSignPayload.Json.feeSigner(): FeeSigner { @@ -201,14 +204,16 @@ class PolkadotExternalSignInteractor( return signerProvider.feeSigner(resolveMetaAccount(), chain) } - private suspend fun PolkadotSignPayload.Json.toExtrinsicBuilderWithoutCall(signer: NovaSigner): ExtrinsicBuilder { + private suspend fun PolkadotSignPayload.Json.analyzeAndSign(signer: NovaSigner): ActualExtrinsic { val chain = chain() val runtime = chainRegistry.getRuntime(genesisHash) val parsedExtrinsic = parseDAppExtrinsic(runtime, this) val accountId = chain.accountIdOf(address) - return with(parsedExtrinsic) { + val actualMetadataHash = actualMetadataHash(chain, signer) + + val builder = with(parsedExtrinsic) { ExtrinsicBuilder( runtime = runtime, nonce = Nonce.singleTx(nonce), @@ -219,12 +224,44 @@ class PolkadotExternalSignInteractor( signer = signer, accountId = accountId, genesisHash = genesisHash, + checkMetadataHash = actualMetadataHash.checkMetadataHash, customSignedExtensions = CustomSignedExtensions.extensionsWithValues(), blockHash = blockHash, era = era, tip = tip ) } + + val extrinsic = when (val callRepresentation = callRepresentation(runtime)) { + is CallRepresentation.Instance -> builder.call(callRepresentation.call).buildExtrinsic() + is CallRepresentation.Bytes -> builder.buildExtrinsic(rawCallBytes = callRepresentation.bytes) + } + + val actualParsedExtrinsic = parsedExtrinsic.copy( + metadataHash = actualMetadataHash.checkMetadataHash.metadataHash + ) + + return ActualExtrinsic( + signedExtrinsic = extrinsic, + modifiedOriginal = actualMetadataHash.modifiedOriginal, + actualParsedExtrinsic = actualParsedExtrinsic + ) + } + + private suspend fun PolkadotSignPayload.Json.actualMetadataHash(chain: Chain, signer: NovaSigner): ActualMetadataHash { + // If a dapp haven't declared a permission to modify extrinsic - return whatever metadataHash present in payload + if (withSignedTransaction != true) { + return ActualMetadataHash(modifiedOriginal = false, hexHash = metadataHash) + } + + // If a dapp have specified metadata hash explicitly - use it + if (metadataHash != null) { + return ActualMetadataHash(modifiedOriginal = false, hexHash = metadataHash) + } + + // Else generate and use our own proof + val metadataProof = metadataShortenerService.generateMetadataProofWithSignerRestrictions(chain, signer) + return ActualMetadataHash(modifiedOriginal = true, checkMetadataHash = metadataProof.checkMetadataHash) } private fun PolkadotSignPayload.Json.callRepresentation(runtime: RuntimeSnapshot): CallRepresentation = runCatching { @@ -250,8 +287,35 @@ class PolkadotExternalSignInteractor( blockHash = blockHash.fromHex(), era = EraType.fromHex(runtime, era), tip = tip.bigIntegerFromHex(), - call = callRepresentation(runtime) + call = callRepresentation(runtime), + metadataHash = metadataHash?.fromHex() ) } } + + private class ActualMetadataHash(val modifiedOriginal: Boolean, val checkMetadataHash: CheckMetadataHash) { + + constructor(modifiedOriginal: Boolean, hash: ByteArray?) : this(modifiedOriginal, CheckMetadataHash(hash)) + + constructor(modifiedOriginal: Boolean, hexHash: String?) : this(modifiedOriginal, hexHash?.fromHex()) + } + + private data class ActualExtrinsic( + val signedExtrinsic: SendableExtrinsic, + val modifiedOriginal: Boolean, + val actualParsedExtrinsic: DAppParsedExtrinsic + ) + + private data class SignedResult(val signature: String, val modifiedTransaction: String?) } + +private fun CheckMetadataHash(hash: ByteArray?): CheckMetadataHash { + return if (hash != null) { + CheckMetadataHash.Enabled(hash) + } else { + CheckMetadataHash.Disabled + } +} + +private val CheckMetadataHash.metadataHash: ByteArray? + get() = if (this is CheckMetadataHash.Enabled) hash else null diff --git a/feature-wallet-connect-impl/src/main/java/io/novafoundation/nova/feature_wallet_connect_impl/domain/session/requests/polkadot/PolkadotSignRequest.kt b/feature-wallet-connect-impl/src/main/java/io/novafoundation/nova/feature_wallet_connect_impl/domain/session/requests/polkadot/PolkadotSignRequest.kt index ac24a9d5f0..bee96dcaa2 100644 --- a/feature-wallet-connect-impl/src/main/java/io/novafoundation/nova/feature_wallet_connect_impl/domain/session/requests/polkadot/PolkadotSignRequest.kt +++ b/feature-wallet-connect-impl/src/main/java/io/novafoundation/nova/feature_wallet_connect_impl/domain/session/requests/polkadot/PolkadotSignRequest.kt @@ -18,7 +18,7 @@ class PolkadotSignRequest( ) : SignWalletConnectRequest(sessionRequest, context) { override suspend fun signedResponse(response: ExternalSignCommunicator.Response.Signed): Wallet.Params.SessionRequestResponse { - val responseData = PolkadotSignerResult(id, response.signature) + val responseData = PolkadotSignerResult(id, signature = response.signature, response.modifiedTransaction) val responseJson = gson.toJson(responseData) return sessionRequest.approved(responseJson) diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/ExtrinsicBuilderFactory.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/ExtrinsicBuilderFactory.kt index 0b2860d074..3d1c1bcefa 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/ExtrinsicBuilderFactory.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/ExtrinsicBuilderFactory.kt @@ -3,9 +3,9 @@ package io.novafoundation.nova.runtime.extrinsic import io.novafoundation.nova.common.utils.orZero import io.novafoundation.nova.runtime.ext.addressOf import io.novafoundation.nova.runtime.ext.requireGenesisHash -import io.novafoundation.nova.runtime.extrinsic.metadata.MetadataProof import io.novafoundation.nova.runtime.extrinsic.metadata.MetadataShortenerService import io.novafoundation.nova.runtime.extrinsic.signer.NovaSigner +import io.novafoundation.nova.runtime.extrinsic.signer.generateMetadataProofWithSignerRestrictions import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import io.novafoundation.nova.runtime.multiNetwork.getRuntime @@ -87,15 +87,4 @@ class ExtrinsicBuilderFactory( newElement } } - - private suspend fun MetadataShortenerService.generateMetadataProofWithSignerRestrictions( - chain: Chain, - signer: NovaSigner, - ): MetadataProof { - return if (signer.supportsCheckMetadataHash(chain)) { - generateMetadataProof(chain.id) - } else { - generateDisabledMetadataProof(chain.id) - } - } } diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/multi/ExtrinsicSplitter.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/multi/ExtrinsicSplitter.kt index 7c11f5c515..9703246a7d 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/multi/ExtrinsicSplitter.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/multi/ExtrinsicSplitter.kt @@ -108,6 +108,7 @@ internal class RealExtrinsicSplitter( accountId = signer.signerAccountId(chain) ) .call(call) - .build() + .buildExtrinsic() + .extrinsicHex } } diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/signer/MetadataHashSigning.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/signer/MetadataHashSigning.kt new file mode 100644 index 0000000000..2cc1a26eab --- /dev/null +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/signer/MetadataHashSigning.kt @@ -0,0 +1,16 @@ +package io.novafoundation.nova.runtime.extrinsic.signer + +import io.novafoundation.nova.runtime.extrinsic.metadata.MetadataProof +import io.novafoundation.nova.runtime.extrinsic.metadata.MetadataShortenerService +import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain + +suspend fun MetadataShortenerService.generateMetadataProofWithSignerRestrictions( + chain: Chain, + signer: NovaSigner, +): MetadataProof { + return if (signer.supportsCheckMetadataHash(chain)) { + generateMetadataProof(chain.id) + } else { + generateDisabledMetadataProof(chain.id) + } +} From 79ae97af7a50a0f357f35f91d6eef490b2f8a5d4 Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Wed, 9 Oct 2024 11:17:32 +0200 Subject: [PATCH 10/23] Fixed crash on delete chain --- .../nova/core_db/AppDatabase.kt | 5 ++- .../nova/core_db/dao/MetaAccountDao.kt | 12 ++++++ .../63_64_AddChainForeignKeyForProxy.kt | 37 +++++++++++++++++++ .../model/chain/account/MetaAccountLocal.kt | 5 +++ .../model/chain/account/ProxyAccountLocal.kt | 7 ++++ .../domain/interfaces/AccountInteractor.kt | 2 + .../domain/interfaces/AccountRepository.kt | 2 + .../data/repository/AccountRepositoryImpl.kt | 4 ++ .../datasource/AccountDataSource.kt | 2 + .../datasource/AccountDataSourceImpl.kt | 4 ++ .../domain/AccountInteractorImpl.kt | 22 ++++++++--- .../di/SettingsFeatureModule.kt | 6 ++- .../NetworkManagementChainInteractor.kt | 8 +++- 13 files changed, 105 insertions(+), 11 deletions(-) create mode 100644 core-db/src/main/java/io/novafoundation/nova/core_db/migrations/63_64_AddChainForeignKeyForProxy.kt diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/AppDatabase.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/AppDatabase.kt index c51dddb0d7..5a315b581b 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/AppDatabase.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/AppDatabase.kt @@ -51,6 +51,7 @@ import io.novafoundation.nova.core_db.migrations.AddBalanceModesToAssets_51_52 import io.novafoundation.nova.core_db.migrations.AddBrowserHostSettings_34_35 import io.novafoundation.nova.core_db.migrations.AddBuyProviders_7_8 import io.novafoundation.nova.core_db.migrations.AddChainColor_4_5 +import io.novafoundation.nova.core_db.migrations.AddChainForeignKeyForProxy_63_64 import io.novafoundation.nova.core_db.migrations.AddConnectionStateToChains_53_54 import io.novafoundation.nova.core_db.migrations.AddContributions_23_24 import io.novafoundation.nova.core_db.migrations.AddCurrencies_18_19 @@ -150,7 +151,7 @@ import io.novafoundation.nova.core_db.model.operation.SwapTypeLocal import io.novafoundation.nova.core_db.model.operation.TransferTypeLocal @Database( - version = 63, + version = 64, entities = [ AccountLocal::class, NodeLocal::class, @@ -248,7 +249,7 @@ abstract class AppDatabase : RoomDatabase() { .addMigrations(AddFungibleNfts_55_56, ChainPushSupport_56_57) .addMigrations(AddLocalMigratorVersionToChainRuntimes_57_58, AddGloballyUniqueIdToMetaAccounts_58_59) .addMigrations(ChainNetworkManagement_59_60, AddBalanceHolds_60_61, ChainNetworkManagement_61_62) - .addMigrations(TinderGovBasket_62_63) + .addMigrations(TinderGovBasket_62_63, AddChainForeignKeyForProxy_63_64) .build() } return instance!! diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt index c1bd8796b5..f44aa3db4a 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt @@ -220,6 +220,18 @@ interface MetaAccountDao { @Query("SELECT * FROM meta_accounts WHERE isSelected = 1") suspend fun selectedMetaAccount(): RelationJoinedMetaAccountInfo? + @Query( + """ + DELETE FROM meta_accounts + WHERE id IN ( + SELECT proxiedMetaId + FROM proxy_accounts + WHERE chainId = :chainId + ) + """ + ) + fun deleteProxiedMetaAccountsByChain(chainId: String) + @Transaction suspend fun insertMetaAndChainAccounts( metaAccount: MetaAccountLocal, diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/migrations/63_64_AddChainForeignKeyForProxy.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/migrations/63_64_AddChainForeignKeyForProxy.kt new file mode 100644 index 0000000000..1e04855972 --- /dev/null +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/migrations/63_64_AddChainForeignKeyForProxy.kt @@ -0,0 +1,37 @@ +package io.novafoundation.nova.core_db.migrations + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +val AddChainForeignKeyForProxy_63_64 = object : Migration(63, 64) { + + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL( + """ + CREATE TABLE IF NOT EXISTS `proxy_accounts_new` ( + `proxiedMetaId` INTEGER NOT NULL, + `proxyMetaId` INTEGER NOT NULL, + `chainId` TEXT NOT NULL, + `proxiedAccountId` BLOB NOT NULL, + `proxyType` TEXT NOT NULL, + PRIMARY KEY(`proxyMetaId`, `proxiedAccountId`, `chainId`, `proxyType`), + FOREIGN KEY(`proxiedMetaId`) REFERENCES `meta_accounts`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE, + FOREIGN KEY(`proxyMetaId`) REFERENCES `meta_accounts`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE, + FOREIGN KEY(`chainId`) REFERENCES `chains`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE + ) + """ + ) + + database.execSQL( + """ + INSERT INTO `proxy_accounts_new` (`proxiedMetaId`, `proxyMetaId`, `chainId`, `proxiedAccountId`, `proxyType`) + SELECT `proxiedMetaId`, `proxyMetaId`, `chainId`, `proxiedAccountId`, `proxyType` + FROM `proxy_accounts` + """ + ) + + database.execSQL("DROP TABLE `proxy_accounts`") + + database.execSQL("ALTER TABLE `proxy_accounts_new` RENAME TO `proxy_accounts`") + } +} diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/MetaAccountLocal.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/MetaAccountLocal.kt index 6c093104a4..ac574c3043 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/MetaAccountLocal.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/MetaAccountLocal.kt @@ -7,6 +7,11 @@ import androidx.room.PrimaryKey import io.novafoundation.nova.core.model.CryptoType import java.util.UUID +/* + TODO: on next migration please add following changes: + - Foreign key for parentMetaId to remove proxy meta account automatically when proxied is deleted + - Foreign key to ProxyAccountLocal to remove proxies meta accounts automatically when chain is deleted + */ @Entity( tableName = MetaAccountLocal.TABLE_NAME, indices = [ diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/ProxyAccountLocal.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/ProxyAccountLocal.kt index 6a69446458..1d2cfb5034 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/ProxyAccountLocal.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/ProxyAccountLocal.kt @@ -4,6 +4,7 @@ import androidx.room.Entity import androidx.room.ForeignKey import androidx.room.Ignore import io.novafoundation.nova.common.utils.Identifiable +import io.novafoundation.nova.core_db.model.chain.ChainLocal import io.novasama.substrate_sdk_android.extensions.toHexString @Entity( @@ -21,6 +22,12 @@ import io.novasama.substrate_sdk_android.extensions.toHexString entity = MetaAccountLocal::class, onDelete = ForeignKey.CASCADE ), + ForeignKey( + parentColumns = ["id"], + childColumns = ["chainId"], + entity = ChainLocal::class, + onDelete = ForeignKey.CASCADE + ), ], primaryKeys = ["proxyMetaId", "proxiedAccountId", "chainId", "proxyType"] ) diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountInteractor.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountInteractor.kt index ea0e752b91..496a655d8e 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountInteractor.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountInteractor.kt @@ -65,4 +65,6 @@ interface AccountInteractor { suspend fun hasSecretsAccounts(): Boolean suspend fun hasCustomChainAccounts(metaId: Long): Boolean + + suspend fun deleteProxiedMetaAccountsByChain(chainId: String) } diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountRepository.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountRepository.kt index e2bd4405c8..a4589addbd 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountRepository.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountRepository.kt @@ -133,4 +133,6 @@ interface AccountRepository { suspend fun generateRestoreJson(metaAccount: MetaAccount, password: String): String suspend fun hasSecretsAccounts(): Boolean + + suspend fun deleteProxiedMetaAccountsByChain(chainId: String) } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/AccountRepositoryImpl.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/AccountRepositoryImpl.kt index 8d027b2c75..d6ce300a4b 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/AccountRepositoryImpl.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/AccountRepositoryImpl.kt @@ -299,6 +299,10 @@ class AccountRepositoryImpl( return accountDataSource.hasSecretsAccounts() } + override suspend fun deleteProxiedMetaAccountsByChain(chainId: String) { + accountDataSource.deleteProxiedMetaAccountsByChain(chainId) + } + override fun nodesFlow(): Flow> { return nodeDao.nodesFlow() .mapList { mapNodeLocalToNode(it) } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSource.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSource.kt index a8f8596cf5..38d90549a0 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSource.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSource.kt @@ -105,6 +105,8 @@ interface AccountDataSource : SecretStoreV1 { suspend fun hasSecretsAccounts(): Boolean suspend fun getMetaAccountIdsByType(type: LightMetaAccount.Type): List + + suspend fun deleteProxiedMetaAccountsByChain(chainId: String) } suspend fun AccountDataSource.getMetaAccountTypeOrThrow(metaId: Long): LightMetaAccount.Type { diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSourceImpl.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSourceImpl.kt index 2e0c5e7460..b6eb324565 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSourceImpl.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSourceImpl.kt @@ -152,6 +152,10 @@ class AccountDataSourceImpl( return metaAccountDao.getMetaAccountIdsByType(mapMetaAccountTypeToLocal(type)) } + override suspend fun deleteProxiedMetaAccountsByChain(chainId: String) { + return metaAccountDao.deleteProxiedMetaAccountsByChain(chainId) + } + override suspend fun hasSecretsAccounts(): Boolean { return metaAccountDao.hasMetaAccountsByType(MetaAccountLocal.Type.SECRETS) } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/AccountInteractorImpl.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/AccountInteractorImpl.kt index c87d50820b..b457841e17 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/AccountInteractorImpl.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/AccountInteractorImpl.kt @@ -184,12 +184,7 @@ class AccountInteractorImpl( override suspend fun removeDeactivatedMetaAccounts() { accountRepository.removeDeactivatedMetaAccounts() - if (!accountRepository.isAccountSelected()) { - val metaAccounts = getActiveMetaAccounts() - if (metaAccounts.isNotEmpty()) { - accountRepository.selectMetaAccount(metaAccounts.first().id) - } - } + switchMetaAccountIfAccountNotSelected() } override suspend fun switchToNotDeactivatedAccountIfNeeded() { @@ -210,4 +205,19 @@ class AccountInteractorImpl( val metaAccount = getMetaAccount(metaId) return metaAccount.chainAccounts.isNotEmpty() } + + override suspend fun deleteProxiedMetaAccountsByChain(chainId: String) { + accountRepository.deleteProxiedMetaAccountsByChain(chainId) + + switchMetaAccountIfAccountNotSelected() + } + + private suspend fun switchMetaAccountIfAccountNotSelected() { + if (!accountRepository.isAccountSelected()) { + val metaAccounts = getActiveMetaAccounts() + if (metaAccounts.isNotEmpty()) { + accountRepository.selectMetaAccount(metaAccounts.first().id) + } + } + } } diff --git a/feature-settings-impl/src/main/java/io/novafoundation/nova/feature_settings_impl/di/SettingsFeatureModule.kt b/feature-settings-impl/src/main/java/io/novafoundation/nova/feature_settings_impl/di/SettingsFeatureModule.kt index eef8f65b2a..d13c099193 100644 --- a/feature-settings-impl/src/main/java/io/novafoundation/nova/feature_settings_impl/di/SettingsFeatureModule.kt +++ b/feature-settings-impl/src/main/java/io/novafoundation/nova/feature_settings_impl/di/SettingsFeatureModule.kt @@ -11,6 +11,7 @@ import io.novafoundation.nova.common.data.network.coingecko.CoinGeckoLinkParser import io.novafoundation.nova.common.data.repository.BannerVisibilityRepository import io.novafoundation.nova.common.di.scope.FeatureScope import io.novafoundation.nova.common.resources.ResourceManager +import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountInteractor import io.novafoundation.nova.feature_assets.domain.tokens.add.validations.CoinGeckoLinkValidationFactory import io.novafoundation.nova.feature_settings_impl.data.NodeChainIdRepositoryFactory import io.novafoundation.nova.feature_settings_impl.domain.AddNetworkInteractor @@ -70,9 +71,10 @@ class SettingsFeatureModule { fun provideNetworkManagementChainInteractor( chainRegistry: ChainRegistry, nodeHealthStateTesterFactory: NodeHealthStateTesterFactory, - chainRepository: ChainRepository + chainRepository: ChainRepository, + accountInteractor: AccountInteractor ): NetworkManagementChainInteractor { - return RealNetworkManagementChainInteractor(chainRegistry, nodeHealthStateTesterFactory, chainRepository) + return RealNetworkManagementChainInteractor(chainRegistry, nodeHealthStateTesterFactory, chainRepository, accountInteractor) } @Provides diff --git a/feature-settings-impl/src/main/java/io/novafoundation/nova/feature_settings_impl/domain/NetworkManagementChainInteractor.kt b/feature-settings-impl/src/main/java/io/novafoundation/nova/feature_settings_impl/domain/NetworkManagementChainInteractor.kt index df828da919..7826013109 100644 --- a/feature-settings-impl/src/main/java/io/novafoundation/nova/feature_settings_impl/domain/NetworkManagementChainInteractor.kt +++ b/feature-settings-impl/src/main/java/io/novafoundation/nova/feature_settings_impl/domain/NetworkManagementChainInteractor.kt @@ -2,6 +2,7 @@ package io.novafoundation.nova.feature_settings_impl.domain import io.novafoundation.nova.common.utils.combine import io.novafoundation.nova.common.utils.flowOf +import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountInteractor import io.novafoundation.nova.runtime.ext.Geneses import io.novafoundation.nova.runtime.ext.genesisHash import io.novafoundation.nova.runtime.ext.isCustomNetwork @@ -15,12 +16,14 @@ import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain.Nodes.NodeS import io.novafoundation.nova.runtime.multiNetwork.connection.node.healthState.NodeHealthStateTesterFactory import io.novafoundation.nova.runtime.repository.ChainRepository import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull +import kotlinx.coroutines.withContext class ChainNetworkState( val chain: Chain, @@ -62,7 +65,8 @@ interface NetworkManagementChainInteractor { class RealNetworkManagementChainInteractor( private val chainRegistry: ChainRegistry, private val nodeHealthStateTesterFactory: NodeHealthStateTesterFactory, - private val chainRepository: ChainRepository + private val chainRepository: ChainRepository, + private val accountInteractor: AccountInteractor ) : NetworkManagementChainInteractor { override fun chainStateFlow(chainId: String, coroutineScope: CoroutineScope): Flow { @@ -106,6 +110,8 @@ class RealNetworkManagementChainInteractor( val chain = chainRegistry.getChain(chainId) require(chain.isCustomNetwork) + + withContext(Dispatchers.Default) { accountInteractor.deleteProxiedMetaAccountsByChain(chainId) } // Delete proxied meta accounts manually chainRepository.deleteNetwork(chainId) } From 9249f385db24e52b9c06cecccdb1e51b5c4661a9 Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Wed, 9 Oct 2024 11:51:55 +0200 Subject: [PATCH 11/23] Fixed binding for killder referenda --- .../data/repository/v2/GovV2OnChainReferendaRepository.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/repository/v2/GovV2OnChainReferendaRepository.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/repository/v2/GovV2OnChainReferendaRepository.kt index e175bcc1e9..6f62803387 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/repository/v2/GovV2OnChainReferendaRepository.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/repository/v2/GovV2OnChainReferendaRepository.kt @@ -188,7 +188,7 @@ class GovV2OnChainReferendaRepository( "Rejected" -> OnChainReferendumStatus.Rejected(bindCompletedReferendumSince(asDictEnum.value)) "Cancelled" -> OnChainReferendumStatus.Cancelled(bindCompletedReferendumSince(asDictEnum.value)) "TimedOut" -> OnChainReferendumStatus.TimedOut(bindCompletedReferendumSince(asDictEnum.value)) - "Killed" -> OnChainReferendumStatus.Killed(bindCompletedReferendumSince(asDictEnum.value)) + "Killed" -> OnChainReferendumStatus.Killed(bindNumber(asDictEnum.value)) else -> throw IllegalArgumentException("Unsupported referendum status") } @@ -197,7 +197,7 @@ class GovV2OnChainReferendaRepository( status = referendumStatus ) } - .onFailure { Log.e(this.LOG_TAG, "Failed to decode on-chain referendum", it) } + .onFailure { Log.e(this.LOG_TAG, "Failed to decode on-chain referendum $id", it) } .getOrNull() private fun bindDecidingStatus(decoded: Any?): DecidingStatus? { From 45e4254a1cb2a3e793c1d089e789a82b0d19e7e6 Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Wed, 9 Oct 2024 12:49:25 +0200 Subject: [PATCH 12/23] Hide voting power button if swipe gov cards is empty --- .../presentation/tindergov/cards/TinderGovCardsFragment.kt | 3 +-- .../presentation/tindergov/cards/TinderGovCardsViewModel.kt | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/cards/TinderGovCardsFragment.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/cards/TinderGovCardsFragment.kt index 24afb4eaaf..a678bcc80d 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/cards/TinderGovCardsFragment.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/cards/TinderGovCardsFragment.kt @@ -122,8 +122,6 @@ class TinderGovCardsFragment : BaseFragment(), TinderGo } } - viewModel.manageVotingPowerAvailable.observe(tinderGovCardsSettings::setVisible) - viewModel.basketModelFlow.observe { tinderGovCardsBasketItems.text = it.items.toString() tinderGovCardsBasketItems.setTextColorRes(it.textColorRes) @@ -164,6 +162,7 @@ class TinderGovCardsFragment : BaseFragment(), TinderGo } viewModel.hasReferendaToVote.observe { + tinderGovCardsSettings.isVisible = it tinderGovCardsControlView.setVisible(it, falseState = View.INVISIBLE) // To avoid click if referenda cards is empty diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/cards/TinderGovCardsViewModel.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/cards/TinderGovCardsViewModel.kt index 434a2c3ded..437a4c59aa 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/cards/TinderGovCardsViewModel.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/cards/TinderGovCardsViewModel.kt @@ -99,8 +99,6 @@ class TinderGovCardsViewModel( private var isVotingInProgress = MutableStateFlow(false) - val manageVotingPowerAvailable = topCardFlow.map { it != null } - val isCardDraggingAvailable = isVotingInProgress.map { !it } val basketModelFlow = basketFlow From e26e6129d287226e1ae0b647e24c4ae73ded23f6 Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Wed, 9 Oct 2024 12:56:02 +0200 Subject: [PATCH 13/23] Update TinderGovBasketFragment.kt --- .../presentation/tindergov/basket/TinderGovBasketFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/basket/TinderGovBasketFragment.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/basket/TinderGovBasketFragment.kt index 44af75d766..5520a5a3ae 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/basket/TinderGovBasketFragment.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/tindergov/basket/TinderGovBasketFragment.kt @@ -74,7 +74,7 @@ class TinderGovBasketFragment : BaseFragment(), Tinder onNegativeClick = { event.onSuccess(false) }, positiveTextRes = R.string.common_remove, negativeTextRes = R.string.common_cancel, - styleRes = R.style.AccentNegativeAlertDialogTheme, + styleRes = R.style.AccentNegativeAlertDialogTheme_Reversed, ) { setTitle(event.payload) setMessage(R.string.swipe_gov_basket_remove_item_confirm_message) From 1dc5a610dc8f8b4ffb266ee7a3f9ce2ecf6ba0b1 Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Wed, 9 Oct 2024 13:40:13 +0200 Subject: [PATCH 14/23] Fixed JsonDeserializer --- .../nova/common/data/network/runtime/model/SystemProperties.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/java/io/novafoundation/nova/common/data/network/runtime/model/SystemProperties.kt b/common/src/main/java/io/novafoundation/nova/common/data/network/runtime/model/SystemProperties.kt index 29ad0ac458..ae801b64b2 100644 --- a/common/src/main/java/io/novafoundation/nova/common/data/network/runtime/model/SystemProperties.kt +++ b/common/src/main/java/io/novafoundation/nova/common/data/network/runtime/model/SystemProperties.kt @@ -26,7 +26,7 @@ private class WrapToListSerializer : JsonDeserializer> { } return json.asJsonArray.map { - context.deserialize(json, valueType) + context.deserialize(it, valueType) } } } From 880c94fd32afe118c4a1d117bf06be5d5eb95a84 Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Thu, 10 Oct 2024 09:48:26 +0200 Subject: [PATCH 15/23] Changed settings ordering --- common/src/main/res/values/strings.xml | 4 +- .../src/main/res/layout/fragment_settings.xml | 43 +++++++++---------- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index b208452f63..f75f1f1edc 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -1,6 +1,9 @@ + + Get support via Email + You have already voted for all available referenda Confirm your votes Vote list @@ -2036,7 +2039,6 @@ Search results will be displayed here Search results Community - Email General Preferences Security diff --git a/feature-settings-impl/src/main/res/layout/fragment_settings.xml b/feature-settings-impl/src/main/res/layout/fragment_settings.xml index 25ece4f973..b456414973 100644 --- a/feature-settings-impl/src/main/res/layout/fragment_settings.xml +++ b/feature-settings-impl/src/main/res/layout/fragment_settings.xml @@ -161,66 +161,65 @@ + android:text="@string/settings_support" /> + app:icon="@drawable/ic_mail_outline" + app:title="@string/settings_email" /> + app:icon="@drawable/ic_rate_us" + app:title="@string/about_rate_app" /> - + app:icon="@drawable/ic_nova_wiki" + app:title="@string/settings_wiki" /> + + android:text="@string/settings_community" /> + app:icon="@drawable/ic_tg" + app:title="@string/about_telegram_v2_2_0" /> + app:icon="@drawable/ic_twitter" + app:title="@string/settings_twitter" /> + app:icon="@drawable/ic_youtube" + app:title="@string/settings_youtube" /> - Date: Thu, 10 Oct 2024 13:37:22 +0200 Subject: [PATCH 16/23] Block swipe gov for unsupported chains when proxied is using --- .../tindergov/TinderGovInteractor.kt | 11 ++++++ .../StartStakingLandingValidationSystem.kt | 14 ++++++++ .../StartSwipeGovValidationFailure.kt | 14 ++++++++ .../StartSwipeGovValidationPayload.kt | 9 +++++ .../referenda/list/ReferendaListFragment.kt | 3 ++ .../referenda/list/ReferendaListViewModel.kt | 34 ++++++++++++++++--- .../referenda/list/di/ReferendaListModule.kt | 12 +++++-- .../validation/StartSwipeGovValidationUi.kt | 22 ++++++++++++ 8 files changed, 113 insertions(+), 6 deletions(-) create mode 100644 feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/tindergov/validation/StartStakingLandingValidationSystem.kt create mode 100644 feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/tindergov/validation/StartSwipeGovValidationFailure.kt create mode 100644 feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/tindergov/validation/StartSwipeGovValidationPayload.kt create mode 100644 feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/list/validation/StartSwipeGovValidationUi.kt diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/tindergov/TinderGovInteractor.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/tindergov/TinderGovInteractor.kt index 76beae00a3..ca594501f8 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/tindergov/TinderGovInteractor.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/tindergov/TinderGovInteractor.kt @@ -2,6 +2,7 @@ package io.novafoundation.nova.feature_governance_impl.domain.referendum.tinderg import io.novafoundation.nova.common.domain.filterLoaded import io.novafoundation.nova.common.utils.flowOfAll +import io.novafoundation.nova.common.validation.ValidationSystem import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository import io.novafoundation.nova.feature_governance_api.data.model.TinderGovBasketItem import io.novafoundation.nova.feature_governance_api.data.model.VotingPower @@ -17,6 +18,10 @@ import io.novafoundation.nova.feature_governance_impl.data.repository.tindergov. import io.novafoundation.nova.feature_governance_impl.domain.referendum.details.call.ReferendumPreImageParser import io.novafoundation.nova.feature_governance_impl.domain.referendum.list.ReferendaSharedComputation import io.novafoundation.nova.feature_governance_impl.domain.referendum.list.filtering.ReferendaFilteringProvider +import io.novafoundation.nova.feature_governance_impl.domain.referendum.tindergov.validation.StartStakingLandingValidationSystem +import io.novafoundation.nova.feature_governance_impl.domain.referendum.tindergov.validation.StartSwipeGovValidationFailure +import io.novafoundation.nova.feature_governance_impl.domain.referendum.tindergov.validation.StartSwipeGovValidationPayload +import io.novafoundation.nova.feature_governance_impl.domain.referendum.tindergov.validation.startSwipeGovValidation import io.novafoundation.nova.feature_wallet_api.domain.AssetUseCase import io.novafoundation.nova.feature_wallet_api.domain.getCurrentAsset import io.novafoundation.nova.feature_wallet_api.domain.model.Asset @@ -54,6 +59,8 @@ interface TinderGovInteractor { suspend fun getVotingPowerState(): VotingPowerState suspend fun awaitAllItemsVoted(coroutineScope: CoroutineScope, basket: List) + + fun startSwipeGovValidationSystem(): StartStakingLandingValidationSystem } class RealTinderGovInteractor( @@ -130,6 +137,10 @@ class RealTinderGovInteractor( }.first() } + override fun startSwipeGovValidationSystem(): StartStakingLandingValidationSystem { + return ValidationSystem.startSwipeGovValidation() + } + private fun TinderGovBasketItem.isItemNotAvailableToVote( availableToVoteReferenda: Set, asset: Asset diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/tindergov/validation/StartStakingLandingValidationSystem.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/tindergov/validation/StartStakingLandingValidationSystem.kt new file mode 100644 index 0000000000..abb109bf3a --- /dev/null +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/tindergov/validation/StartStakingLandingValidationSystem.kt @@ -0,0 +1,14 @@ +package io.novafoundation.nova.feature_governance_impl.domain.referendum.tindergov.validation + +import io.novafoundation.nova.common.validation.ValidationSystem +import io.novafoundation.nova.feature_account_api.domain.validation.hasChainAccount + +typealias StartStakingLandingValidationSystem = ValidationSystem + +fun ValidationSystem.Companion.startSwipeGovValidation(): StartStakingLandingValidationSystem = ValidationSystem { + hasChainAccount( + chain = { it.chain }, + metaAccount = { it.metaAccount }, + error = StartSwipeGovValidationFailure::NoChainAccountFound + ) +} diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/tindergov/validation/StartSwipeGovValidationFailure.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/tindergov/validation/StartSwipeGovValidationFailure.kt new file mode 100644 index 0000000000..dc782e4bd1 --- /dev/null +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/tindergov/validation/StartSwipeGovValidationFailure.kt @@ -0,0 +1,14 @@ +package io.novafoundation.nova.feature_governance_impl.domain.referendum.tindergov.validation + +import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount +import io.novafoundation.nova.feature_account_api.domain.validation.NoChainAccountFoundError +import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain + +sealed class StartSwipeGovValidationFailure { + + class NoChainAccountFound( + override val chain: Chain, + override val account: MetaAccount, + override val addAccountState: NoChainAccountFoundError.AddAccountState + ) : StartSwipeGovValidationFailure(), NoChainAccountFoundError +} diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/tindergov/validation/StartSwipeGovValidationPayload.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/tindergov/validation/StartSwipeGovValidationPayload.kt new file mode 100644 index 0000000000..d27cff5cb7 --- /dev/null +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/tindergov/validation/StartSwipeGovValidationPayload.kt @@ -0,0 +1,9 @@ +package io.novafoundation.nova.feature_governance_impl.domain.referendum.tindergov.validation + +import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount +import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain + +class StartSwipeGovValidationPayload( + val chain: Chain, + val metaAccount: MetaAccount +) diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/list/ReferendaListFragment.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/list/ReferendaListFragment.kt index e284d5e9b4..5ccafa4fc6 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/list/ReferendaListFragment.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/list/ReferendaListFragment.kt @@ -8,6 +8,7 @@ import androidx.recyclerview.widget.ConcatAdapter import coil.ImageLoader import io.novafoundation.nova.common.di.FeatureUtils import io.novafoundation.nova.common.domain.dataOrNull +import io.novafoundation.nova.common.mixin.impl.observeValidations import io.novafoundation.nova.feature_governance_api.di.GovernanceFeatureApi import io.novafoundation.nova.feature_governance_impl.R import io.novafoundation.nova.feature_governance_impl.di.GovernanceFeatureComponent @@ -50,6 +51,8 @@ class ReferendaListFragment : BaseReferendaListFragment( override fun subscribe(viewModel: ReferendaListViewModel) { subscribeOnAssetClick(viewModel.assetSelectorMixin, imageLoader) + observeValidations(viewModel) + subscribeOnAssetChange(viewModel.assetSelectorMixin) { referendaHeaderAdapter.setAsset(it) } diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/list/ReferendaListViewModel.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/list/ReferendaListViewModel.kt index 176e60a989..3f7f0ece25 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/list/ReferendaListViewModel.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/list/ReferendaListViewModel.kt @@ -7,6 +7,7 @@ import io.novafoundation.nova.common.domain.ExtendedLoadingState import io.novafoundation.nova.common.domain.dataOrNull import io.novafoundation.nova.common.list.toListWithHeaders import io.novafoundation.nova.common.domain.mapLoading +import io.novafoundation.nova.common.mixin.api.Validatable import io.novafoundation.nova.common.resources.ResourceManager import io.novafoundation.nova.common.utils.LOG_TAG import io.novafoundation.nova.common.utils.combineToPair @@ -14,6 +15,8 @@ import io.novafoundation.nova.common.utils.firstLoaded import io.novafoundation.nova.common.utils.formatting.format import io.novafoundation.nova.common.utils.inBackground import io.novafoundation.nova.common.utils.withItemScope +import io.novafoundation.nova.common.validation.ValidationExecutor +import io.novafoundation.nova.common.validation.progressConsumer import io.novafoundation.nova.common.view.PlaceholderModel import io.novafoundation.nova.core.updater.UpdateSystem import io.novafoundation.nova.feature_account_api.domain.interfaces.SelectedAccountUseCase @@ -34,10 +37,13 @@ import io.novafoundation.nova.feature_governance_impl.presentation.GovernanceRou import io.novafoundation.nova.feature_governance_impl.presentation.referenda.common.ReferendumFormatter import io.novafoundation.nova.feature_governance_impl.presentation.referenda.common.list.ReferendaListStateModel import io.novafoundation.nova.feature_governance_api.presentation.referenda.details.ReferendumDetailsPayload +import io.novafoundation.nova.feature_governance_impl.domain.referendum.tindergov.TinderGovInteractor +import io.novafoundation.nova.feature_governance_impl.domain.referendum.tindergov.validation.StartSwipeGovValidationPayload import io.novafoundation.nova.feature_governance_impl.presentation.referenda.list.model.ReferendaGroupModel import io.novafoundation.nova.feature_governance_impl.presentation.referenda.list.model.ReferendumModel import io.novafoundation.nova.feature_governance_impl.presentation.referenda.list.model.TinderGovBannerModel import io.novafoundation.nova.feature_governance_impl.presentation.referenda.list.model.toReferendumDetailsPrefilledData +import io.novafoundation.nova.feature_governance_impl.presentation.referenda.list.validation.handleStartSwipeGovValidationFailure import io.novafoundation.nova.feature_governance_impl.presentation.view.GovernanceLocksModel import io.novafoundation.nova.feature_wallet_api.domain.model.Asset import io.novafoundation.nova.feature_wallet_api.presentation.mixin.assetSelector.AssetSelectorFactory @@ -64,8 +70,12 @@ class ReferendaListViewModel( private val governanceRouter: GovernanceRouter, private val referendumFormatter: ReferendumFormatter, private val governanceDAppsInteractor: GovernanceDAppsInteractor, - private val referendaSummaryInteractor: ReferendaSummaryInteractor -) : BaseViewModel(), WithAssetSelector { + private val referendaSummaryInteractor: ReferendaSummaryInteractor, + private val tinderGovInteractor: TinderGovInteractor, + private val selectedMetaAccountUseCase: SelectedAccountUseCase, + private val validationExecutor: ValidationExecutor, +) : BaseViewModel(), WithAssetSelector, + Validatable by validationExecutor { override val assetSelectorMixin = assetSelectorFactory.create( scope = this, @@ -148,8 +158,24 @@ class ReferendaListViewModel( governanceRouter.openReferendum(payload) } - fun openTinderGovCards() { - governanceRouter.openTinderGovCards() + fun openTinderGovCards() = launch { + val payload = StartSwipeGovValidationPayload( + chain = selectedAssetSharedState.chain(), + metaAccount = selectedMetaAccountUseCase.getSelectedMetaAccount() + ) + + validationExecutor.requireValid( + validationSystem = tinderGovInteractor.startSwipeGovValidationSystem(), + payload = payload, + validationFailureTransformerCustom = { validationFailure, _ -> + handleStartSwipeGovValidationFailure( + resourceManager, + validationFailure + ) + } + ) { + governanceRouter.openTinderGovCards() + } } private fun mapLocksOverviewToUi(locksOverview: GovernanceLocksOverview?, asset: Asset): GovernanceLocksModel? { diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/list/di/ReferendaListModule.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/list/di/ReferendaListModule.kt index 38f39b5a48..4dcb779f0e 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/list/di/ReferendaListModule.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/list/di/ReferendaListModule.kt @@ -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.core.updater.UpdateSystem import io.novafoundation.nova.feature_account_api.domain.interfaces.SelectedAccountUseCase import io.novafoundation.nova.feature_governance_api.domain.referendum.list.ReferendaListInteractor @@ -16,6 +17,7 @@ import io.novafoundation.nova.feature_governance_impl.domain.summary.ReferendaSu import io.novafoundation.nova.feature_governance_impl.data.GovernanceSharedState import io.novafoundation.nova.feature_governance_impl.domain.dapp.GovernanceDAppsInteractor import io.novafoundation.nova.feature_governance_impl.domain.filters.ReferendaFiltersInteractor +import io.novafoundation.nova.feature_governance_impl.domain.referendum.tindergov.TinderGovInteractor import io.novafoundation.nova.feature_governance_impl.presentation.GovernanceRouter import io.novafoundation.nova.feature_governance_impl.presentation.referenda.common.ReferendumFormatter import io.novafoundation.nova.feature_governance_impl.presentation.referenda.list.ReferendaListViewModel @@ -38,7 +40,10 @@ class ReferendaListModule { governanceRouter: GovernanceRouter, referendumFormatter: ReferendumFormatter, governanceDAppsInteractor: GovernanceDAppsInteractor, - summaryInteractor: ReferendaSummaryInteractor + summaryInteractor: ReferendaSummaryInteractor, + tinderGovInteractor: TinderGovInteractor, + selectedMetaAccountUseCase: SelectedAccountUseCase, + validationExecutor: ValidationExecutor, ): ViewModel { return ReferendaListViewModel( assetSelectorFactory = assetSelectorFactory, @@ -51,7 +56,10 @@ class ReferendaListModule { governanceRouter = governanceRouter, referendumFormatter = referendumFormatter, governanceDAppsInteractor = governanceDAppsInteractor, - referendaSummaryInteractor = summaryInteractor + referendaSummaryInteractor = summaryInteractor, + tinderGovInteractor = tinderGovInteractor, + selectedMetaAccountUseCase = selectedMetaAccountUseCase, + validationExecutor = validationExecutor ) } diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/list/validation/StartSwipeGovValidationUi.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/list/validation/StartSwipeGovValidationUi.kt new file mode 100644 index 0000000000..c3380e931b --- /dev/null +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/list/validation/StartSwipeGovValidationUi.kt @@ -0,0 +1,22 @@ +package io.novafoundation.nova.feature_governance_impl.presentation.referenda.list.validation + +import io.novafoundation.nova.common.resources.ResourceManager +import io.novafoundation.nova.common.validation.TransformedFailure +import io.novafoundation.nova.common.validation.ValidationStatus +import io.novafoundation.nova.feature_account_api.domain.validation.handleChainAccountNotFound +import io.novafoundation.nova.feature_governance_impl.R +import io.novafoundation.nova.feature_governance_impl.domain.referendum.tindergov.validation.StartSwipeGovValidationFailure + +fun handleStartSwipeGovValidationFailure( + resourceManager: ResourceManager, + validationStatus: ValidationStatus.NotValid +): TransformedFailure { + return when (val reason = validationStatus.reason) { + is StartSwipeGovValidationFailure.NoChainAccountFound -> handleChainAccountNotFound( + failure = reason, + addAccountDescriptionRes = R.string.common_network_not_supported, + resourceManager = resourceManager, + goToWalletDetails = { } + ) + } +} From de2996650aaf4c6b0704c8a27848a37d89c5c678 Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Thu, 10 Oct 2024 17:41:37 +0200 Subject: [PATCH 17/23] Fixed past dates crashes --- .../nova/common/utils/formatting/NumberFormatters.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/formatting/NumberFormatters.kt b/common/src/main/java/io/novafoundation/nova/common/utils/formatting/NumberFormatters.kt index b0f219daa3..8c8b907eef 100644 --- a/common/src/main/java/io/novafoundation/nova/common/utils/formatting/NumberFormatters.kt +++ b/common/src/main/java/io/novafoundation/nova/common/utils/formatting/NumberFormatters.kt @@ -128,7 +128,6 @@ fun Date.formatDateSinceEpoch(resourceManager: ResourceManager): String { val currentDays = System.currentTimeMillis().daysFromMillis() val diff = currentDays - time.daysFromMillis() - if (diff < 0) throw IllegalArgumentException("Past date should be less than current") return when (diff) { 0L -> resourceManager.getString(R.string.today) 1L -> resourceManager.getString(R.string.yesterday) From 872625224ce1c7ad3b0ba97cf922e146a6f7cba0 Mon Sep 17 00:00:00 2001 From: leohar Date: Fri, 11 Oct 2024 16:51:57 +0100 Subject: [PATCH 18/23] rename string values --- common/src/main/res/values-es/strings.xml | 4 ++-- common/src/main/res/values-fr-rFR/strings.xml | 4 ++-- common/src/main/res/values-in/strings.xml | 4 ++-- common/src/main/res/values-it/strings.xml | 4 ++-- common/src/main/res/values-ja/strings.xml | 4 ++-- common/src/main/res/values-ko/strings.xml | 6 +++--- common/src/main/res/values-pl/strings.xml | 4 ++-- common/src/main/res/values-pt/strings.xml | 4 ++-- common/src/main/res/values-ru/strings.xml | 4 ++-- common/src/main/res/values-tr/strings.xml | 4 ++-- common/src/main/res/values-vi/strings.xml | 4 ++-- common/src/main/res/values-zh-rCN/strings.xml | 4 ++-- common/src/main/res/values/strings.xml | 1 - 13 files changed, 25 insertions(+), 26 deletions(-) diff --git a/common/src/main/res/values-es/strings.xml b/common/src/main/res/values-es/strings.xml index 1ff7e730c6..9c7ef274da 100644 --- a/common/src/main/res/values-es/strings.xml +++ b/common/src/main/res/values-es/strings.xml @@ -1131,7 +1131,7 @@ Por favor, asegúrate de que la biometría está activada en los Ajustes Biometría desactivada en los Ajustes Comunidad - Email + Obtenga ayuda por Email General Cada operación de firma en monederos con par de claves (creados en nova wallet o importados) debería requerir verificación PIN antes de construir la firma Solicitar autenticación para firmar operaciones @@ -1143,7 +1143,7 @@ Seguridad Soporte & Retroalimentación Twitter - Wiki + Wiki y centro de ayuda Youtube La convicción se establecerá en 0.1x cuando se Abstenga No puedes hacer stake con Staking Directo y Pools de Nominación al mismo tiempo diff --git a/common/src/main/res/values-fr-rFR/strings.xml b/common/src/main/res/values-fr-rFR/strings.xml index 93563f4c2d..3fe1fb2531 100644 --- a/common/src/main/res/values-fr-rFR/strings.xml +++ b/common/src/main/res/values-fr-rFR/strings.xml @@ -1131,7 +1131,7 @@ Veuillez vous assurer que la biométrie est activée dans les paramètres La biométrie est désactivée dans les paramètres Communauté - E-mail + Obtenez de l\'aide par Email Général Chaque opération de signature sur les portefeuilles avec paire de clés (créée dans Nova Wallet ou importée) doit nécessiter une vérification PIN avant de construire la signature Demander une authentification pour signer les opérations @@ -1143,7 +1143,7 @@ Sécurité Assistance et commentaires Twitter - Wiki + Wiki et centre d\'aide YouTube La conviction sera fixée à 0,1x lorsque vous vous abstenez Vous ne pouvez pas staker avec le Stake direct et les Pools de Nomination en même temps diff --git a/common/src/main/res/values-in/strings.xml b/common/src/main/res/values-in/strings.xml index 8849ecdfb8..2c83b7e843 100644 --- a/common/src/main/res/values-in/strings.xml +++ b/common/src/main/res/values-in/strings.xml @@ -1123,7 +1123,7 @@ Pastikan biometrik diaktifkan dalam Pengaturan Biometrik dinonaktifkan dalam Pengaturan Komunitas - Email + Dapatkan dukungan melalui Email Umum Setiap operasi tanda tangan pada dompet dengan pasangan kunci (dibuat di dompet nova atau diimpor) harus memerlukan verifikasi PIN sebelum membuat tanda tangan Minta autentikasi untuk penandatanganan operasi @@ -1135,7 +1135,7 @@ Keamanan Dukungan & Masukan Twitter - Wiki + Wiki & Pusat Bantuan Youtube Keyakinan akan diatur ke 0,1x ketika Abstain Anda tidak dapat melakukan staking dengan Staking Langsung dan Pool Nominasi pada saat yang sama diff --git a/common/src/main/res/values-it/strings.xml b/common/src/main/res/values-it/strings.xml index 47d2572d74..df030d8362 100644 --- a/common/src/main/res/values-it/strings.xml +++ b/common/src/main/res/values-it/strings.xml @@ -1131,7 +1131,7 @@ Si prega, assicurarsi che la biometria sia abilitata nelle impostazioni Biometria disabilitata nelle impostazioni Comunità - Email + Ottieni supporto via Email Generale Ogni operazione di firma sui portafogli con coppia di chiavi (creata in un portafoglio di nova o importata) dovrebbe richiedere la verifica del PIN prima di costruire la firma Richiedi autenticazione per la firma operazioni @@ -1143,7 +1143,7 @@ Sicurezza Supporto e Feedback Twitter - Wiki + Wiki e Centro assistenza Youtube La convinzione sarà impostata a 0.1x quando Abstain Non puoi fare staking contemporaneamente con Direct Staking e con Nomination Pools diff --git a/common/src/main/res/values-ja/strings.xml b/common/src/main/res/values-ja/strings.xml index d76dbd4d06..afb5a07e86 100644 --- a/common/src/main/res/values-ja/strings.xml +++ b/common/src/main/res/values-ja/strings.xml @@ -1123,7 +1123,7 @@ 設定で生体認証が有効になっていることを確認してください 設定で生体認証が無効になっています コミュニティ - Eメール + メールでサポートを受ける 一般 ウォレットでの署名操作(Nova Walletで作成またはインポートされたもの)は、署名の作成前にPIN確認が必要です 操作の署名に認証を要求 @@ -1135,7 +1135,7 @@ セキュリティ サポート&フィードバック Twitter - ウィキ + ウィキとヘルプセンター Youtube 棄権時には信念が0.1xに設定されます Direct StakingとNomination Poolsを同時にステークすることはできません diff --git a/common/src/main/res/values-ko/strings.xml b/common/src/main/res/values-ko/strings.xml index 2c227c0998..d33b4e4b7e 100644 --- a/common/src/main/res/values-ko/strings.xml +++ b/common/src/main/res/values-ko/strings.xml @@ -81,7 +81,7 @@ %s에 대한 Nova Wallet 접근을 허용하십시오 Bluetooth 및 위치 지갑에 추가하려면 %s - 계정을 선택 + 계정을 선택 Ledger Nano X 연결 작업을 서명하고 계정을 새로운 Generic Ledger 앱으로 마이그레이션하려면 Migration 앱을 설치하고 여십시오. 레거시 구형 및 Migration Ledger 앱은 더 이상 지원되지 않을 것입니다. 새로운 Ledger 앱이 출시되었습니다 @@ -1123,7 +1123,7 @@ 설정에서 생체 인식이 활성화되었는지 확인하세요 설정에서 생체 인식이 비활성화되었습니다 커뮤니티 - 이메일 + 이메일을 통해 지원 받기 일반 키 페어가 있는 지갑(Nova Wallet에서 생성 또는 가져옴)에 대한 각 서명 작업은 서명을 생성하기 전에 PIN 확인이 필요합니다 작업 서명을 위해 인증 요청 @@ -1135,7 +1135,7 @@ 보안 지원 및 피드백 Twitter - 위키 + 위키 및 도움말 센터 Youtube 기권 시 확신도가 0.1x로 설정됩니다 직접 Staking과 Nomination Pools를 동시에 사용할 수 없습니다 diff --git a/common/src/main/res/values-pl/strings.xml b/common/src/main/res/values-pl/strings.xml index 889f66d6bf..a77a24f988 100644 --- a/common/src/main/res/values-pl/strings.xml +++ b/common/src/main/res/values-pl/strings.xml @@ -1147,7 +1147,7 @@ Proszę upewnij się, że biometria jest włączona w Ustawieniach Biometria wyłączona w Ustawieniach Społeczność - Email + Uzyskaj wsparcie przez Email Ogólne Każda operacja podpisania na portfelach z parą kluczy (utworzonych w Nova Wallet lub zaimportowanych) powinna wymagać weryfikacji PIN przed utworzeniem podpisu Prośba o uwierzytelnienie dla operacji podpisywania @@ -1159,7 +1159,7 @@ Bezpieczeństwo Wsparcie i opinie Twitter - Wiki + Wiki i Centrum Pomocy Youtube Conviction zostanie ustawione na 0.1x podczas Wstrzymania się Nie możesz stake z Direct Staking i Nomination Pools jednocześnie diff --git a/common/src/main/res/values-pt/strings.xml b/common/src/main/res/values-pt/strings.xml index 28432220cc..f42fb5c89f 100644 --- a/common/src/main/res/values-pt/strings.xml +++ b/common/src/main/res/values-pt/strings.xml @@ -1131,7 +1131,7 @@ Por favor, certifique-se de que a biometria está ativada nas Configurações Biometria desativada nas Configurações Comunidade - Escrever para os desenvolvedores + Obtenha suporte via Email Geral Cada operação de assinatura em carteiras com par de chaves (criada na nova wallet ou importada) deve exigir a verificação do PIN antes da construção da assinatura Solicitar autenticação para assinatura de operações @@ -1143,7 +1143,7 @@ Segurança Suporte & Feedback Twitter - Wiki + Wiki e Central de Ajuda Youtube A convicção será definida como 0.1x quando Abstain Você não pode fazer staking com Staking Direto e Pools de Nomeação ao mesmo tempo diff --git a/common/src/main/res/values-ru/strings.xml b/common/src/main/res/values-ru/strings.xml index 3726bd30f6..cd1f87d762 100644 --- a/common/src/main/res/values-ru/strings.xml +++ b/common/src/main/res/values-ru/strings.xml @@ -1147,7 +1147,7 @@ Пожалуйста, убедитесь, что биометрия включена в настройках Биометрия отключена в настройках Сообщество - Написать разработчикам + Получите поддержку по Email Основные Каждая операция подписи на кошельках с парой ключей (созданной в кошельке nova или импортированной) должна требовать проверки PIN-кода перед созданием подписи Запрашивать аутентификацию для подписи операций @@ -1159,7 +1159,7 @@ Безопасность Поддержка и обратная связь Twitter - Руководство пользователя + Вики и справочный центр Youtube Убежденность будет установлена на 0.1x при Воздержании Невозможно стейкать напрямую и в пуле одновременно diff --git a/common/src/main/res/values-tr/strings.xml b/common/src/main/res/values-tr/strings.xml index d88a54ece3..aa73d206fc 100644 --- a/common/src/main/res/values-tr/strings.xml +++ b/common/src/main/res/values-tr/strings.xml @@ -1131,7 +1131,7 @@ Lütfen, biyometrinin Ayarlar\'da etkinleştirildiğinden emin olun Ayarlar\'da biyometrik kapalı Topluluk - Email + Email yoluyla destek alın Genel Anahtar çiftiyle (nova cüzdanında oluşturulan veya içe aktarılan) cüzdanlarda sign işlemi yapılırken, imza oluşturmadan önce PIN doğrulaması gerektirir Operasyon imzalanırken kimlik doğrulama isteyin @@ -1143,7 +1143,7 @@ Güvenlik Destek & Geribildirim Twitter - Wiki + Wiki & Yardım Merkezi Youtube Çekinmelerde 0.1x olarak Ayarlanacak Aynı anda hem Doğrudan Stake hem de Nomination Havuzlarını kullanarak stake yapamazsınız diff --git a/common/src/main/res/values-vi/strings.xml b/common/src/main/res/values-vi/strings.xml index a99abb7976..79df480757 100644 --- a/common/src/main/res/values-vi/strings.xml +++ b/common/src/main/res/values-vi/strings.xml @@ -1123,7 +1123,7 @@ Vui lòng, đảm bảo sinh trắc học đã được bật trong Cài đặt Sinh trắc học đã bị vô hiệu hóa trong Cài đặt Cộng đồng - Email + Nhận hỗ trợ qua Email Chung Mỗi hoạt động ký trên ví với cặp khóa (tạo trong ví nova hoặc nhập) cần yêu cầu xác minh PIN trước khi tạo chữ ký Yêu cầu xác thực cho các hoạt động ký @@ -1135,7 +1135,7 @@ Bảo mật Hỗ trợ & Phản hồi Twitter - Wiki + Wiki & Trung tâm trợ giúp Youtube Độ xác tín sẽ được đặt là 0.1x khi bỏ phiếu Abstain Bạn không thể Stake với Direct Staking và Nomination Pools cùng một lúc diff --git a/common/src/main/res/values-zh-rCN/strings.xml b/common/src/main/res/values-zh-rCN/strings.xml index 99a9839a61..cc1c960d19 100644 --- a/common/src/main/res/values-zh-rCN/strings.xml +++ b/common/src/main/res/values-zh-rCN/strings.xml @@ -1123,7 +1123,7 @@ 请确保在设置中启用了生物识别 设置中禁用了生物识别 社区 - 电子邮件 + 通过电子邮件获得支持 通用 带有密钥对的钱包(在nova钱包中创建或导入的)上的每次签名操作都应在构造签名前要求进行PIN验证 请求操作签名的认证 @@ -1135,7 +1135,7 @@ 安全性 支持和反馈 Twitter - 维基 + 维基百科和帮助中心 Youtube 弃权时将把决心设置为 0.1x 不能同时使用直接质押和提名池进行质押 diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index f75f1f1edc..9f1a4f0c90 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -1,7 +1,6 @@ - Get support via Email You have already voted for all available referenda From a56f5157b5974c5784f9110443ec8a3cfa002f52 Mon Sep 17 00:00:00 2001 From: leohar Date: Mon, 14 Oct 2024 16:18:26 +0100 Subject: [PATCH 19/23] fix some strings --- common/src/main/res/values-fr-rFR/strings.xml | 14 +++++++------- common/src/main/res/values-ko/strings.xml | 10 +++++----- common/src/main/res/values-pl/strings.xml | 2 +- common/src/main/res/values-vi/strings.xml | 2 +- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/common/src/main/res/values-fr-rFR/strings.xml b/common/src/main/res/values-fr-rFR/strings.xml index 3fe1fb2531..df5fbce85c 100644 --- a/common/src/main/res/values-fr-rFR/strings.xml +++ b/common/src/main/res/values-fr-rFR/strings.xml @@ -198,7 +198,7 @@ Staking Acquisition Acheter des tokens - Vous avez récupéré vos DOT des crowdloans ? Commencez à staker vos DOT aujourd\'hui pour obtenir les récompenses maximales ! + Vous avez récupéré vos DOT des crowdloans ? Commencez à staker vos DOT aujourd\'hui pour obtenir les récompenses maximales ! Boostez vos DOT 🚀 Filtrer les actifs Tous les réseaux @@ -525,10 +525,10 @@ Ajouter une connexion Scannez le code QR Un problème a été identifié avec votre sauvegarde. Vous avez la possibilité de supprimer la sauvegarde actuelle et d\'en créer une nouvelle. %s avant de continuer. - Assurez-vous d\'avoir sauvegardé les phrases secrètes pour tous les portefeuilles + Assurez-vous d\'avoir sauvegardé les phrases secrètes pour tous les portefeuilles Sauvegarde trouvée mais vide ou corrompue À l\'avenir, sans le mot de passe de sauvegarde, il est impossible de restaurer vos portefeuilles à partir de la Sauvegarde Cloud.\n%s - Ce mot de passe ne peut pas être récupéré. + Ce mot de passe ne peut pas être récupéré. Souvenez-vous du mot de passe de sauvegarde Confirmer le mot de passe Mot de passe de sauvegarde @@ -1417,7 +1417,7 @@ Je veux staker Yield Boost Type de staking - Vous retirez tous vos tokens et ne pouvez pas en stake davantage. + Vous retirez tous vos tokens et ne pouvez pas en stake davantage. Impossible de stake plus Lors d\'un retrait partiel, vous devez laisser au moins %s en jeu. Voulez-vous effectuer un retrait complet en libérant également %s restants? Montant trop faible reste en stake @@ -1466,8 +1466,8 @@ Donnez un nom à votre portefeuille Cela ne sera visible que par vous et vous pourrez le modifier plus tard. Votre portefeuille est prêt - Vous avez des tokens verrouillés sur votre solde en raison de %s. Pour continuer, vous devez entrer moins de %s ou plus de %s. Pour stake un autre montant, vous devez supprimer vos verrous %s. - Vous ne pouvez pas stake le montant spécifié + Vous avez des tokens verrouillés sur votre solde en raison de %s. Pour continuer, vous devez entrer moins de %s ou plus de %s. Pour stake un autre montant, vous devez supprimer vos verrous %s. + Vous ne pouvez pas stake le montant spécifié Sélectionné : %d (max %d) Solde disponible : %1$s (%2$s) %s avec vos tokens staked @@ -1563,7 +1563,7 @@ Aucune donnée récupérée Vous avez déjà voté pour tous les référendums disponibles ou il n\'y a pas de référendums pour voter en ce moment. Revenez plus tard. Vous avez déjà voté pour tous les référendums disponibles - Demandé : + Demandé : Liste de vote %d restant Confirmer les votes diff --git a/common/src/main/res/values-ko/strings.xml b/common/src/main/res/values-ko/strings.xml index d33b4e4b7e..4142aa26be 100644 --- a/common/src/main/res/values-ko/strings.xml +++ b/common/src/main/res/values-ko/strings.xml @@ -81,7 +81,7 @@ %s에 대한 Nova Wallet 접근을 허용하십시오 Bluetooth 및 위치 지갑에 추가하려면 %s - 계정을 선택 + 계정을 선택 Ledger Nano X 연결 작업을 서명하고 계정을 새로운 Generic Ledger 앱으로 마이그레이션하려면 Migration 앱을 설치하고 여십시오. 레거시 구형 및 Migration Ledger 앱은 더 이상 지원되지 않을 것입니다. 새로운 Ledger 앱이 출시되었습니다 @@ -564,8 +564,8 @@ 크라우드론 종료됨 추천 코드를 입력하세요 크라우드론 정보 - 크라우드론 %s 배우기]]> - 크라우드론 웹사이트]]> + 크라우드론 %s 배우기 + %s의 크라우드론 웹사이트 임대 기간 파라체인을 선택하여 %s을(를) 기여하세요. 기여한 토큰을 돌려받게 되며, 파라체인이 슬롯에 당선되면 경매 종료 후 보상을 받게 됩니다. 기여를 위해 지갑에 %s 계정을 추가해야 합니다. @@ -821,7 +821,7 @@ 비밀번호 구절을 공유하지 마세요! 아무도 화면을 볼 수 없도록 하고\n스크린샷을 찍지 마세요 아무에게도 %s 하지 마세요 - 공유하지 마세요 + 공유하지 마세요 다른 것을 시도해 보세요. 유효하지 않은 니모닉 구절입니다. 단어 순서를 다시 한번 확인하세요 %d 개 이상의 지갑을 선택할 수 없습니다 @@ -1137,7 +1137,7 @@ Twitter 위키 및 도움말 센터 Youtube - 기권 시 확신도가 0.1x로 설정됩니다 + 기권 시 확신도가 0.1x로 설정됩니다 직접 Staking과 Nomination Pools를 동시에 사용할 수 없습니다 이미 Staking 중 고급 Staking 관리 diff --git a/common/src/main/res/values-pl/strings.xml b/common/src/main/res/values-pl/strings.xml index a77a24f988..641ea2cf6e 100644 --- a/common/src/main/res/values-pl/strings.xml +++ b/common/src/main/res/values-pl/strings.xml @@ -535,7 +535,7 @@ Dodaj połączenie Skanuj kod QR Wykryto problem z twoją kopią zapasową. Masz możliwość usunięcia bieżącej kopii zapasowej i utworzenia nowej. %s przed kontynuowaniem. - Upewnij się, że zapisałeś Passphrases dla wszystkich portfeli + Upewnij się, że zapisałeś Passphrases dla wszystkich portfeli Znaleziono kopię zapasową, ale jest pusta lub uszkodzona W przyszłości bez hasła kopii zapasowej nie będzie można przywrócić portfeli z kopii zapasowej w Chmurze.\n%s Tego hasła nie można odzyskać. diff --git a/common/src/main/res/values-vi/strings.xml b/common/src/main/res/values-vi/strings.xml index 79df480757..6ebc3b7831 100644 --- a/common/src/main/res/values-vi/strings.xml +++ b/common/src/main/res/values-vi/strings.xml @@ -1137,7 +1137,7 @@ Twitter Wiki & Trung tâm trợ giúp Youtube - Độ xác tín sẽ được đặt là 0.1x khi bỏ phiếu Abstain + Độ xác tín sẽ được đặt là 0.1x khi bỏ phiếu Abstain Bạn không thể Stake với Direct Staking và Nomination Pools cùng một lúc Đã staking Quản lý Stake nâng cao From d3cd237084ad6466b947024e248cb963471bf0c3 Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Tue, 15 Oct 2024 08:29:53 +0200 Subject: [PATCH 20/23] Fixed ordering on settings --- common/src/main/res/values/strings.xml | 5 +++-- .../src/main/res/layout/fragment_settings.xml | 13 +++++++------ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index f75f1f1edc..3d88d1a07f 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -1,6 +1,9 @@ + + Wiki & Help Center + Get support via Email @@ -627,8 +630,6 @@ Referendum not found - Wiki - Swap Repeat the operation diff --git a/feature-settings-impl/src/main/res/layout/fragment_settings.xml b/feature-settings-impl/src/main/res/layout/fragment_settings.xml index b456414973..110b3b98b1 100644 --- a/feature-settings-impl/src/main/res/layout/fragment_settings.xml +++ b/feature-settings-impl/src/main/res/layout/fragment_settings.xml @@ -175,20 +175,21 @@ app:title="@string/settings_email" /> + app:icon="@drawable/ic_nova_wiki" + app:title="@string/settings_wiki" /> + app:icon="@drawable/ic_rate_us" + app:title="@string/about_rate_app" /> + Date: Tue, 15 Oct 2024 09:14:08 +0200 Subject: [PATCH 21/23] Redirect to wallet details --- .../presentation/referenda/list/ReferendaListViewModel.kt | 3 ++- .../referenda/list/validation/StartSwipeGovValidationUi.kt | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/list/ReferendaListViewModel.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/list/ReferendaListViewModel.kt index 3f7f0ece25..2ae22f6eb8 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/list/ReferendaListViewModel.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/list/ReferendaListViewModel.kt @@ -170,7 +170,8 @@ class ReferendaListViewModel( validationFailureTransformerCustom = { validationFailure, _ -> handleStartSwipeGovValidationFailure( resourceManager, - validationFailure + validationFailure, + governanceRouter ) } ) { diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/list/validation/StartSwipeGovValidationUi.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/list/validation/StartSwipeGovValidationUi.kt index c3380e931b..71309d053d 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/list/validation/StartSwipeGovValidationUi.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/list/validation/StartSwipeGovValidationUi.kt @@ -6,17 +6,19 @@ import io.novafoundation.nova.common.validation.ValidationStatus import io.novafoundation.nova.feature_account_api.domain.validation.handleChainAccountNotFound import io.novafoundation.nova.feature_governance_impl.R import io.novafoundation.nova.feature_governance_impl.domain.referendum.tindergov.validation.StartSwipeGovValidationFailure +import io.novafoundation.nova.feature_governance_impl.presentation.GovernanceRouter fun handleStartSwipeGovValidationFailure( resourceManager: ResourceManager, - validationStatus: ValidationStatus.NotValid + validationStatus: ValidationStatus.NotValid, + router: GovernanceRouter ): TransformedFailure { return when (val reason = validationStatus.reason) { is StartSwipeGovValidationFailure.NoChainAccountFound -> handleChainAccountNotFound( failure = reason, addAccountDescriptionRes = R.string.common_network_not_supported, resourceManager = resourceManager, - goToWalletDetails = { } + goToWalletDetails = { router.openWalletDetails(reason.account.id) } ) } } From 9af72eeecc0f1234a5f9bf4d1dfe77f07ea77df9 Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Tue, 15 Oct 2024 09:29:49 +0200 Subject: [PATCH 22/23] Run ktlint --- .../domain/referendum/tindergov/TinderGovInteractor.kt | 2 -- .../presentation/referenda/list/ReferendaListViewModel.kt | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/tindergov/TinderGovInteractor.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/tindergov/TinderGovInteractor.kt index ca594501f8..9ed15518eb 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/tindergov/TinderGovInteractor.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/tindergov/TinderGovInteractor.kt @@ -19,8 +19,6 @@ import io.novafoundation.nova.feature_governance_impl.domain.referendum.details. import io.novafoundation.nova.feature_governance_impl.domain.referendum.list.ReferendaSharedComputation import io.novafoundation.nova.feature_governance_impl.domain.referendum.list.filtering.ReferendaFilteringProvider import io.novafoundation.nova.feature_governance_impl.domain.referendum.tindergov.validation.StartStakingLandingValidationSystem -import io.novafoundation.nova.feature_governance_impl.domain.referendum.tindergov.validation.StartSwipeGovValidationFailure -import io.novafoundation.nova.feature_governance_impl.domain.referendum.tindergov.validation.StartSwipeGovValidationPayload import io.novafoundation.nova.feature_governance_impl.domain.referendum.tindergov.validation.startSwipeGovValidation import io.novafoundation.nova.feature_wallet_api.domain.AssetUseCase import io.novafoundation.nova.feature_wallet_api.domain.getCurrentAsset diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/list/ReferendaListViewModel.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/list/ReferendaListViewModel.kt index 2ae22f6eb8..e04028a84e 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/list/ReferendaListViewModel.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/list/ReferendaListViewModel.kt @@ -16,7 +16,6 @@ import io.novafoundation.nova.common.utils.formatting.format import io.novafoundation.nova.common.utils.inBackground import io.novafoundation.nova.common.utils.withItemScope import io.novafoundation.nova.common.validation.ValidationExecutor -import io.novafoundation.nova.common.validation.progressConsumer import io.novafoundation.nova.common.view.PlaceholderModel import io.novafoundation.nova.core.updater.UpdateSystem import io.novafoundation.nova.feature_account_api.domain.interfaces.SelectedAccountUseCase @@ -74,7 +73,8 @@ class ReferendaListViewModel( private val tinderGovInteractor: TinderGovInteractor, private val selectedMetaAccountUseCase: SelectedAccountUseCase, private val validationExecutor: ValidationExecutor, -) : BaseViewModel(), WithAssetSelector, +) : BaseViewModel(), + WithAssetSelector, Validatable by validationExecutor { override val assetSelectorMixin = assetSelectorFactory.create( From 16ebf4000f129d547c499c1df696281b9b3cd697 Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Tue, 15 Oct 2024 09:51:58 +0200 Subject: [PATCH 23/23] Bump version code --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 7d993ad980..3500232981 100644 --- a/build.gradle +++ b/build.gradle @@ -1,8 +1,8 @@ buildscript { ext { // App version - versionName = '8.7.0' - versionCode = 155 + versionName = '8.7.2' + versionCode = 156 applicationId = "io.novafoundation.nova" releaseApplicationSuffix = "market"