diff --git a/.github/scripts/pr_comment_extract_data.py b/.github/scripts/pr_comment_extract_data.py new file mode 100755 index 0000000000..855c97b503 --- /dev/null +++ b/.github/scripts/pr_comment_extract_data.py @@ -0,0 +1,26 @@ +import requests +import sys +import os +from datetime import datetime + + +def parse_base_params(comment_link): + response = requests.get(comment_link).json() + try: + lines = response["body"].split("\n") + except: + print("Please, create a comment with 'Release severity: Major | Critical | Normal' and 'Release notes:'") + exit(1) + + time = datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ') + severity = next((s.split(': ')[1] for s in lines if s.startswith('Release severity: ')), '').strip() + + if not time or not severity: + print(f"Current values: Time - '{time}', Severity - '{severity}'") + print("Time or severity cannot be empty, please set it in the PR comment with lines 'Release time:' and 'Release severity:'") + sys.exit(1) + + with open(os.getenv('GITHUB_ENV'), 'a') as f: + f.write(f'TIME={time}\n') + f.write(f'SEVERITY={severity}\n') +parse_base_params(os.getenv("COMMENT_LINK")) diff --git a/.github/workflows/pr_workflow.yml b/.github/workflows/pr_workflow.yml index 5dd04c2251..bea90ff331 100644 --- a/.github/workflows/pr_workflow.yml +++ b/.github/workflows/pr_workflow.yml @@ -3,11 +3,16 @@ name: PR Workflow on: pull_request: branches: - - 'develop' # master - + - 'master' + pull_request_review_comment: + types: [created, edited, deleted] + jobs: checkRef: + if: github.event.pull_request.base.ref == 'master' || github.event_name == 'pull_request' runs-on: ubuntu-latest + outputs: + is_rc: ${{ steps.check_ref.outputs.ref_contains_rc }} steps: - uses: actions/checkout@v2 @@ -15,7 +20,7 @@ jobs: id: check_ref run: | echo ${{ github.head_ref || github.ref_name }} - if [[ "${{ github.head_ref || github.ref_name }}" == *"/rc/"* ]]; then + if [[ "${{ github.head_ref || github.ref_name }}" == *"rc"* ]]; then echo "ref_contains_rc=1" >> $GITHUB_OUTPUT else echo "ref_contains_rc=0" >> $GITHUB_OUTPUT @@ -25,19 +30,46 @@ jobs: run: | echo "Output: ${{ steps.check_ref.outputs.ref_contains_rc }}" - build: + make-or-update-pr: runs-on: ubuntu-latest + permissions: write-all + needs: checkRef + if: needs.checkRef.outputs.is_rc == '1' steps: - - name: Check if comment contains "Release notes" - if: github.ref == 'refs/heads/master' - uses: actions/github-script@v4 + - uses: actions/checkout@v4 + - name: Find Comment + uses: peter-evans/find-comment@v2 + id: fc + with: + issue-number: ${{ github.event.pull_request.number }} + body-includes: Release notes + + - name: Create comment link + id: create_link + run: | + echo "COMMENT_LINK=https://api.github.com/repos/${{ github.repository }}/issues/comments/${{ steps.fc.outputs.comment-id }}" >> $GITHUB_ENV + shell: bash + + - name: Extract version from branch name + id: extract_version + run: | + VERSION=${{ github.head_ref }} + echo "::set-output name=version::${VERSION#*rc/}" + + - uses: tibdex/github-app-token@v1 + id: generate-token + with: + app_id: ${{ secrets.PR_APP_ID }} + private_key: ${{ secrets.PR_APP_TOKEN }} + + - name: Run Python script + run: python .github/scripts/pr_comment_extract_data.py + + - name: Create new branch and file in nova-wallet-android-releases repo + uses: peter-evans/repository-dispatch@v2 with: - script: | - const comment = github.event.pull_request.comments.find(c => c.body.includes('Release notes')); - if (!comment) { - console.log('No comment found with "Release notes"'); - throw new Error('Missing release notes'); - } else { - console.log('Comment found with "Release notes"'); - } + token: ${{ steps.generate-token.outputs.token }} + repository: novasamatech/nova-wallet-android-releases + event-type: create-pr + client-payload: '{"version": "${{ steps.extract_version.outputs.version }}", "comment_link": "${{ env.COMMENT_LINK }}", "time": "${{ env.TIME }}", "severity": "${{ env.SEVERITY }}"}' diff --git a/app/src/androidTest/java/io/novafoundation/nova/SwapServiceIntegrationTest.kt b/app/src/androidTest/java/io/novafoundation/nova/SwapServiceIntegrationTest.kt index 4b77198ad8..978370d7b8 100644 --- a/app/src/androidTest/java/io/novafoundation/nova/SwapServiceIntegrationTest.kt +++ b/app/src/androidTest/java/io/novafoundation/nova/SwapServiceIntegrationTest.kt @@ -42,7 +42,7 @@ class SwapServiceIntegrationTest : BaseIntegrationTest() { @Test fun shouldRetrieveAvailableDirections() = runTest { - val allAvailableAssetIdsToSwap = swapService.assetsAvailableForSwap(this) + val allAvailableAssetIdsToSwap = swapService.assetsAvailableForSwap(this).first() val allAvailableChainAssetsToSwap = allAvailableAssetIdsToSwap.map { val (chain, asset) = chainRegistry.chainWithAsset(it.chainId, it.assetId) @@ -84,7 +84,8 @@ class SwapServiceIntegrationTest : BaseIntegrationTest() { assetOut = siri, swapLimit = SwapLimit.SpecifiedIn( expectedAmountIn = siri.planksFromAmount(0.000001.toBigDecimal()), - amountOutMin = Balance.ZERO + amountOutMin = Balance.ZERO, + expectedAmountOut = Balance.ZERO ), customFeeAsset = null, nativeAsset = arbitraryAssetUseCase.assetFlow(westmint.commissionAsset).first() @@ -106,7 +107,8 @@ class SwapServiceIntegrationTest : BaseIntegrationTest() { assetOut = wnd, swapLimit = SwapLimit.SpecifiedIn( expectedAmountIn = siri.planksFromAmount(0.000001.toBigDecimal()), - amountOutMin = Balance.ZERO + amountOutMin = Balance.ZERO, + expectedAmountOut = Balance.ZERO ), customFeeAsset = siri, nativeAsset = arbitraryAssetUseCase.assetFlow(westmint.commissionAsset).first() @@ -146,7 +148,7 @@ class SwapServiceIntegrationTest : BaseIntegrationTest() { } private suspend fun CoroutineScope.findAvailableDirectionsFor(asset: Chain.Asset) { - val directionsForWnd = swapService.availableSwapDirectionsFor(asset, this) + val directionsForWnd = swapService.availableSwapDirectionsFor(asset, this).first() val directionsForWndFormatted = directionsForWnd.map { otherId -> val otherAsset = chainRegistry.asset(otherId) diff --git a/app/src/androidTest/java/io/novafoundation/nova/balances/BalancesIntegrationTest.kt b/app/src/androidTest/java/io/novafoundation/nova/balances/BalancesIntegrationTest.kt index 4fe47c79d4..1d250ffa17 100644 --- a/app/src/androidTest/java/io/novafoundation/nova/balances/BalancesIntegrationTest.kt +++ b/app/src/androidTest/java/io/novafoundation/nova/balances/BalancesIntegrationTest.kt @@ -7,6 +7,7 @@ import io.novafoundation.nova.common.data.network.runtime.binding.AccountInfo import io.novafoundation.nova.common.data.network.runtime.binding.bindAccountInfo import io.novafoundation.nova.common.di.FeatureUtils import io.novafoundation.nova.common.utils.fromJson +import io.novafoundation.nova.common.utils.hasModule import io.novafoundation.nova.common.utils.system import io.novafoundation.nova.feature_account_api.di.AccountFeatureApi import io.novafoundation.nova.feature_account_impl.di.AccountFeatureComponent @@ -125,6 +126,11 @@ class BalancesIntegrationTest( withTimeout(80.seconds) { extrinsicService.estimateFee(chain) { systemRemark(byteArrayOf(0)) + + val haveBatch = runtime.metadata.hasModule("Utility") + if (haveBatch) { + systemRemark(byteArrayOf(0)) + } } } } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ce74838383..71de0f9b2c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -9,12 +9,15 @@ - - - @@ -22,23 +25,23 @@ + android:supportsRtl="true" + tools:replace="android:allowBackup,android:fullBackupContent,android:dataExtractionRules" + tools:targetApi="s"> + android:windowSoftInputMode="adjustResize"> @@ -62,6 +65,17 @@ + + + + + + + + + + android:exported="false" + android:grantUriPermissions="true"> diff --git a/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/AccountNavigationModule.kt b/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/AccountNavigationModule.kt index 045b268d88..3440ff71f3 100644 --- a/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/AccountNavigationModule.kt +++ b/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/AccountNavigationModule.kt @@ -4,7 +4,6 @@ import dagger.Module import dagger.Provides import io.novafoundation.nova.app.root.navigation.NavigationHolder import io.novafoundation.nova.app.root.navigation.Navigator -import io.novafoundation.nova.app.root.navigation.account.AdvancedEncryptionCommunicatorImpl import io.novafoundation.nova.app.root.navigation.account.PolkadotVaultVariantSignCommunicatorImpl import io.novafoundation.nova.app.root.navigation.account.SelectAddressCommunicatorImpl import io.novafoundation.nova.app.root.navigation.account.SelectWalletCommunicatorImpl @@ -15,7 +14,6 @@ import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.l import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectWallet.SelectWalletCommunicator import io.novafoundation.nova.feature_account_impl.data.signer.paritySigner.PolkadotVaultVariantSignCommunicator import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter -import io.novafoundation.nova.feature_account_impl.presentation.AdvancedEncryptionCommunicator import io.novafoundation.nova.feature_assets.presentation.AssetsRouter @Module @@ -27,12 +25,6 @@ class AccountNavigationModule { navigationHolder: NavigationHolder ): PinCodeTwoFactorVerificationCommunicator = PinCodeTwoFactorVerificationCommunicatorImpl(navigationHolder) - @Provides - @ApplicationScope - fun provideAdvancedEncryptionCommunicator( - navigationHolder: NavigationHolder - ): AdvancedEncryptionCommunicator = AdvancedEncryptionCommunicatorImpl(navigationHolder) - @Provides @ApplicationScope fun provideSelectWalletCommunicator( diff --git a/app/src/main/java/io/novafoundation/nova/app/root/di/RootComponent.kt b/app/src/main/java/io/novafoundation/nova/app/root/di/RootComponent.kt index a6a49263f6..5205194bf1 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/di/RootComponent.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/di/RootComponent.kt @@ -11,9 +11,15 @@ import io.novafoundation.nova.common.di.CommonApi import io.novafoundation.nova.common.di.scope.FeatureScope import io.novafoundation.nova.core_db.di.DbApi import io.novafoundation.nova.feature_account_api.di.AccountFeatureApi +import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter import io.novafoundation.nova.feature_assets.di.AssetsFeatureApi +import io.novafoundation.nova.feature_assets.presentation.AssetsRouter import io.novafoundation.nova.feature_crowdloan_api.di.CrowdloanFeatureApi import io.novafoundation.nova.feature_currency_api.di.CurrencyFeatureApi +import io.novafoundation.nova.feature_dapp_api.di.DAppFeatureApi +import io.novafoundation.nova.feature_dapp_impl.DAppRouter +import io.novafoundation.nova.feature_governance_api.di.GovernanceFeatureApi +import io.novafoundation.nova.feature_governance_impl.presentation.GovernanceRouter import io.novafoundation.nova.feature_staking_api.di.StakingFeatureApi import io.novafoundation.nova.feature_versions_api.di.VersionsFeatureApi import io.novafoundation.nova.feature_wallet_api.di.WalletFeatureApi @@ -40,6 +46,10 @@ interface RootComponent { fun create( @BindsInstance navigationHolder: NavigationHolder, @BindsInstance rootRouter: RootRouter, + @BindsInstance governanceRouter: GovernanceRouter, + @BindsInstance dAppRouter: DAppRouter, + @BindsInstance assetsRouter: AssetsRouter, + @BindsInstance accountRouter: AccountRouter, @BindsInstance stakingDashboardNavigator: StakingDashboardNavigator, deps: RootDependencies ): RootComponent @@ -53,6 +63,8 @@ interface RootComponent { CrowdloanFeatureApi::class, AssetsFeatureApi::class, CurrencyFeatureApi::class, + GovernanceFeatureApi::class, + DAppFeatureApi::class, DbApi::class, CommonApi::class, RuntimeApi::class, diff --git a/app/src/main/java/io/novafoundation/nova/app/root/di/RootDependencies.kt b/app/src/main/java/io/novafoundation/nova/app/root/di/RootDependencies.kt index 72d5bdb62a..43bb8dc126 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/di/RootDependencies.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/di/RootDependencies.kt @@ -11,13 +11,17 @@ import io.novafoundation.nova.common.utils.sequrity.AutomaticInteractionGate import io.novafoundation.nova.common.utils.sequrity.BackgroundAccessObserver import io.novafoundation.nova.common.utils.systemCall.SystemCallExecutor import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository +import io.novafoundation.nova.feature_account_api.domain.account.common.EncryptionDefaults import io.novafoundation.nova.feature_assets.data.network.BalancesUpdateSystem import io.novafoundation.nova.feature_crowdloan_api.data.repository.CrowdloanRepository import io.novafoundation.nova.feature_crowdloan_api.domain.contributions.ContributionsInteractor import io.novafoundation.nova.feature_currency_api.domain.CurrencyInteractor +import io.novafoundation.nova.feature_dapp_api.data.repository.DAppMetadataRepository +import io.novafoundation.nova.feature_governance_api.data.MutableGovernanceState import io.novafoundation.nova.feature_staking_api.domain.api.StakingRepository import io.novafoundation.nova.feature_versions_api.domain.UpdateNotificationsInteractor import io.novafoundation.nova.feature_wallet_api.domain.interfaces.WalletRepository +import io.novafoundation.nova.feature_wallet_connect_api.domain.sessions.WalletConnectSessionsUseCase import io.novafoundation.nova.feature_wallet_connect_api.presentation.WalletConnectService import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.connection.ChainConnection @@ -57,6 +61,12 @@ interface RootDependencies { fun rootScope(): RootScope + fun governanceStateUpdater(): MutableGovernanceState + + fun dappMetadataRepository(): DAppMetadataRepository + + fun encryptionDefaults(): EncryptionDefaults + val systemCallExecutor: SystemCallExecutor val contextManager: ContextManager @@ -66,4 +76,6 @@ interface RootDependencies { val imageLoader: ImageLoader val automaticInteractionGate: AutomaticInteractionGate + + val walletConnectSessionsUseCase: WalletConnectSessionsUseCase } diff --git a/app/src/main/java/io/novafoundation/nova/app/root/di/RootFeatureHolder.kt b/app/src/main/java/io/novafoundation/nova/app/root/di/RootFeatureHolder.kt index be166a6508..bef327f6a2 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/di/RootFeatureHolder.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/di/RootFeatureHolder.kt @@ -8,9 +8,15 @@ import io.novafoundation.nova.common.di.FeatureContainer import io.novafoundation.nova.common.di.scope.ApplicationScope import io.novafoundation.nova.core_db.di.DbApi import io.novafoundation.nova.feature_account_api.di.AccountFeatureApi +import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter import io.novafoundation.nova.feature_assets.di.AssetsFeatureApi +import io.novafoundation.nova.feature_assets.presentation.AssetsRouter import io.novafoundation.nova.feature_crowdloan_api.di.CrowdloanFeatureApi import io.novafoundation.nova.feature_currency_api.di.CurrencyFeatureApi +import io.novafoundation.nova.feature_dapp_api.di.DAppFeatureApi +import io.novafoundation.nova.feature_dapp_impl.DAppRouter +import io.novafoundation.nova.feature_governance_api.di.GovernanceFeatureApi +import io.novafoundation.nova.feature_governance_impl.presentation.GovernanceRouter import io.novafoundation.nova.feature_staking_api.di.StakingFeatureApi import io.novafoundation.nova.feature_versions_api.di.VersionsFeatureApi import io.novafoundation.nova.feature_wallet_api.di.WalletFeatureApi @@ -22,6 +28,10 @@ import javax.inject.Inject class RootFeatureHolder @Inject constructor( private val navigationHolder: NavigationHolder, private val navigator: Navigator, + private val governanceRouter: GovernanceRouter, + private val dAppRouter: DAppRouter, + private val accountRouter: AccountRouter, + private val assetsRouter: AssetsRouter, private val stakingDashboardNavigator: StakingDashboardNavigator, featureContainer: FeatureContainer ) : FeatureApiHolder(featureContainer) { @@ -36,12 +46,14 @@ class RootFeatureHolder @Inject constructor( .assetsFeatureApi(getFeature(AssetsFeatureApi::class.java)) .currencyFeatureApi(getFeature(CurrencyFeatureApi::class.java)) .crowdloanFeatureApi(getFeature(CrowdloanFeatureApi::class.java)) + .governanceFeatureApi(getFeature(GovernanceFeatureApi::class.java)) + .dAppFeatureApi(getFeature(DAppFeatureApi::class.java)) .runtimeApi(getFeature(RuntimeApi::class.java)) .versionsFeatureApi(getFeature(VersionsFeatureApi::class.java)) .walletConnectFeatureApi(getFeature(WalletConnectFeatureApi::class.java)) .build() return DaggerRootComponent.factory() - .create(navigationHolder, navigator, stakingDashboardNavigator, rootFeatureDependencies) + .create(navigationHolder, navigator, governanceRouter, dAppRouter, assetsRouter, accountRouter, stakingDashboardNavigator, rootFeatureDependencies) } } diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/Navigator.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/Navigator.kt index 1d7106f0f2..11573d44a6 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/Navigator.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/Navigator.kt @@ -15,6 +15,8 @@ import io.novafoundation.nova.feature_account_api.domain.model.PolkadotVaultVari import io.novafoundation.nova.feature_account_api.presenatation.account.add.AddAccountPayload import io.novafoundation.nova.feature_account_api.presenatation.account.add.ImportAccountPayload import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter +import io.novafoundation.nova.feature_account_impl.presentation.account.advancedEncryption.AdvancedEncryptionFragment +import io.novafoundation.nova.feature_account_impl.presentation.account.advancedEncryption.AdvancedEncryptionModePayload import io.novafoundation.nova.feature_account_impl.presentation.account.details.AccountDetailsFragment import io.novafoundation.nova.feature_account_impl.presentation.exporting.ExportPayload import io.novafoundation.nova.feature_account_impl.presentation.exporting.json.confirm.ExportJsonConfirmFragment @@ -150,6 +152,10 @@ class Navigator( } } + override fun openAdvancedSettings(payload: AdvancedEncryptionModePayload) { + navController?.navigate(R.id.action_open_advancedEncryptionFragment, AdvancedEncryptionFragment.getBundle(payload)) + } + override fun openConfirmMnemonicOnCreate(confirmMnemonicPayload: ConfirmMnemonicPayload) { val bundle = ConfirmMnemonicFragment.getBundle(confirmMnemonicPayload) @@ -160,13 +166,13 @@ class Navigator( } override fun openImportAccountScreen(payload: ImportAccountPayload) { - val destination = when (val currentDestinationId = navController?.currentDestination?.id) { - R.id.welcomeFragment -> R.id.action_welcomeFragment_to_import_nav_graph - R.id.accountDetailsFragment -> R.id.action_accountDetailsFragment_to_import_nav_graph - else -> throw IllegalArgumentException("Unknown current destination to open import account screen: $currentDestinationId") + val currentDestination = navController?.currentDestination ?: return + val actionId = when (currentDestination.id) { + // Wee need the slpash fragment case to close app if we use back navigation in import mnemonic screen + R.id.splashFragment -> R.id.action_splashFragment_to_import_nav_graph + else -> R.id.action_import_nav_graph } - - navController?.navigate(destination, ImportAccountFragment.getBundle(payload)) + navController?.navigate(actionId, ImportAccountFragment.getBundle(payload)) } override fun openMnemonicScreen(accountName: String?, addAccountPayload: AddAccountPayload) { @@ -345,6 +351,8 @@ class Navigator( } override fun openStaking() { + if (navController?.currentDestination?.id != R.id.mainFragment) navController?.navigate(R.id.action_open_main) + stakingDashboardDelegate.openStakingDashboard() } diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/account/AdvancedEncryptionCommunicatorImpl.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/account/AdvancedEncryptionCommunicatorImpl.kt deleted file mode 100644 index 7ccffcd4b2..0000000000 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/account/AdvancedEncryptionCommunicatorImpl.kt +++ /dev/null @@ -1,19 +0,0 @@ -package io.novafoundation.nova.app.root.navigation.account - -import io.novafoundation.nova.app.R -import io.novafoundation.nova.app.root.navigation.NavStackInterScreenCommunicator -import io.novafoundation.nova.app.root.navigation.NavigationHolder -import io.novafoundation.nova.feature_account_impl.presentation.AdvancedEncryptionCommunicator -import io.novafoundation.nova.feature_account_impl.presentation.AdvancedEncryptionCommunicator.Response -import io.novafoundation.nova.feature_account_impl.presentation.account.advancedEncryption.AdvancedEncryptionFragment -import io.novafoundation.nova.feature_account_impl.presentation.account.advancedEncryption.AdvancedEncryptionPayload - -class AdvancedEncryptionCommunicatorImpl( - navigationHolder: NavigationHolder -) : NavStackInterScreenCommunicator(navigationHolder), AdvancedEncryptionCommunicator { - - override fun openRequest(request: AdvancedEncryptionPayload) { - super.openRequest(request) - navController.navigate(R.id.action_open_advancedEncryptionFragment, AdvancedEncryptionFragment.getBundle(request)) - } -} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/dApp/DAppNavigator.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/dApp/DAppNavigator.kt index e4d843ef2c..f39f944c4f 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/dApp/DAppNavigator.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/dApp/DAppNavigator.kt @@ -11,18 +11,23 @@ import io.novafoundation.nova.feature_dapp_impl.presentation.search.DappSearchFr import io.novafoundation.nova.feature_dapp_impl.presentation.search.SearchPayload class DAppNavigator( - navigationHolder: NavigationHolder, + private val navigationHolder: NavigationHolder, ) : BaseNavigator(navigationHolder), DAppRouter { override fun openChangeAccount() = performNavigation(R.id.action_open_switch_wallet) - override fun openDAppBrowser(initialUrl: String) = performNavigation( - cases = arrayOf( - R.id.mainFragment to R.id.action_mainFragment_to_dappBrowserGraph, - R.id.dappSearchFragment to R.id.action_dappSearchFragment_to_dapp_browser_graph, - ), - args = DAppBrowserFragment.getBundle(initialUrl) - ) + override fun openDAppBrowser(initialUrl: String) { + // Close deapp browser if it is already opened + // TODO it's better to provide new url to existing browser + val currentDestination = navigationHolder.navController?.currentDestination + + val destinationId = when (currentDestination?.id) { + R.id.DAppBrowserFragment -> R.id.action_DAppBrowserFragment_to_DAppBrowserFragment + R.id.dappSearchFragment -> R.id.action_dappSearchFragment_to_dapp_browser_graph + else -> R.id.action_dappBrowserGraph + } + performNavigation(destinationId, DAppBrowserFragment.getBundle(initialUrl)) + } override fun openDappSearch() = performNavigation( actionId = R.id.action_mainFragment_to_dappSearchGraph, diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/governance/GovernanceNavigator.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/governance/GovernanceNavigator.kt index 60513da35b..16db060824 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/governance/GovernanceNavigator.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/governance/GovernanceNavigator.kt @@ -45,14 +45,13 @@ class GovernanceNavigator( ) : BaseNavigator(navigationHolder), GovernanceRouter { override fun openReferendum(payload: ReferendumDetailsPayload) { - performNavigation( - cases = arrayOf( - R.id.mainFragment to R.id.action_open_referendum_details, - R.id.votedReferendaFragment to R.id.action_open_referendum_details, - R.id.referendaSearchFragment to R.id.action_open_referendum_details_from_referenda_search, - ), - args = ReferendumDetailsFragment.getBundle(payload) - ) + val currentDestination = navigationHolder.navController?.currentDestination + val destinationId = when (currentDestination?.id) { + R.id.referendumDetailsFragment -> R.id.action_referendumDetailsFragment_to_referendumDetailsFragment + R.id.referendaSearchFragment -> R.id.action_open_referendum_details_from_referenda_search + else -> R.id.action_open_referendum_details + } + performNavigation(destinationId, ReferendumDetailsFragment.getBundle(payload)) } override fun openReferendumFullDetails(payload: ReferendumFullDetailsPayload) = performNavigation( diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/staking/relaychain/RelayStakingNavigator.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/staking/relaychain/RelayStakingNavigator.kt index 9729eb897d..7f426bd48c 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/staking/relaychain/RelayStakingNavigator.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/staking/relaychain/RelayStakingNavigator.kt @@ -180,7 +180,7 @@ class RelayStakingNavigator( override fun openRebag() = performNavigation(R.id.action_stakingFragment_to_rebag) override fun openDAppBrowser(url: String) = performNavigation( - actionId = R.id.action_mainFragment_to_dappBrowserGraph, + actionId = R.id.action_dappBrowserGraph, args = DAppBrowserFragment.getBundle(url) ) diff --git a/app/src/main/java/io/novafoundation/nova/app/root/presentation/RootActivity.kt b/app/src/main/java/io/novafoundation/nova/app/root/presentation/RootActivity.kt index 4ab2ffb150..58a5b37723 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/presentation/RootActivity.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/presentation/RootActivity.kt @@ -126,9 +126,9 @@ class RootActivity : BaseActivity(), SplashBackgroundHolder { } private fun processIntent(intent: Intent) { - val uri = intent.data?.toString() - - uri?.let { viewModel.externalUrlOpened(uri) } + intent.data?.let { + viewModel.handleDeepLink(it) + } } // private fun processJsonOpenIntent() { diff --git a/app/src/main/java/io/novafoundation/nova/app/root/presentation/RootViewModel.kt b/app/src/main/java/io/novafoundation/nova/app/root/presentation/RootViewModel.kt index 6c0cf0d5b2..3eb2780af2 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/presentation/RootViewModel.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/presentation/RootViewModel.kt @@ -1,19 +1,25 @@ package io.novafoundation.nova.app.root.presentation +import android.net.Uri import androidx.lifecycle.viewModelScope -import io.novafoundation.nova.app.R import io.novafoundation.nova.app.root.domain.RootInteractor +import io.novafoundation.nova.app.root.presentation.deepLinks.CallbackEvent +import io.novafoundation.nova.app.root.presentation.deepLinks.DeepLinkHandler +import io.novafoundation.nova.app.root.presentation.deepLinks.common.DeepLinkHandlingException +import io.novafoundation.nova.app.root.presentation.deepLinks.common.formatDeepLinkHandlingException import io.novafoundation.nova.common.base.BaseViewModel import io.novafoundation.nova.common.mixin.api.NetworkStateMixin import io.novafoundation.nova.common.mixin.api.NetworkStateUi import io.novafoundation.nova.common.resources.ResourceManager import io.novafoundation.nova.common.sequrity.SafeModeService import io.novafoundation.nova.common.utils.coroutines.RootScope +import io.novafoundation.nova.common.utils.sequrity.AutomaticInteractionGate import io.novafoundation.nova.common.utils.sequrity.BackgroundAccessObserver import io.novafoundation.nova.core.updater.Updater import io.novafoundation.nova.feature_crowdloan_api.domain.contributions.ContributionsInteractor import io.novafoundation.nova.feature_currency_api.domain.CurrencyInteractor import io.novafoundation.nova.feature_versions_api.domain.UpdateNotificationsInteractor +import io.novafoundation.nova.feature_wallet_connect_api.domain.sessions.WalletConnectSessionsUseCase import io.novafoundation.nova.feature_wallet_connect_api.presentation.WalletConnectService import io.novafoundation.nova.runtime.multiNetwork.connection.ChainConnection.ExternalRequirement import kotlinx.coroutines.cancel @@ -34,6 +40,9 @@ class RootViewModel( private val safeModeService: SafeModeService, private val updateNotificationsInteractor: UpdateNotificationsInteractor, private val walletConnectService: WalletConnectService, + private val walletConnectSessionsUseCase: WalletConnectSessionsUseCase, + private val deepLinkHandler: DeepLinkHandler, + private val automaticInteractionGate: AutomaticInteractionGate, private val rootScope: RootScope ) : BaseViewModel(), NetworkStateUi by networkStateMixin { @@ -55,11 +64,33 @@ class RootViewModel( syncCurrencies() + syncWalletConnectSessions() + updatePhishingAddresses() walletConnectService.onPairErrorLiveData.observeForever { showError(it.peekContent()) } + + subscribeDeepLinkCallback() + } + + private fun subscribeDeepLinkCallback() { + deepLinkHandler.callbackFlow + .onEach { handleDeepLinkCallbackEvent(it) } + .launchIn(this) + } + + private fun handleDeepLinkCallbackEvent(event: CallbackEvent) { + when (event) { + is CallbackEvent.Message -> { + showMessage(event.message) + } + } + } + + private fun syncWalletConnectSessions() = launch { + walletConnectSessionsUseCase.syncActiveSessions() } private fun checkForUpdates() { @@ -112,12 +143,6 @@ class RootViewModel( } } - fun externalUrlOpened(uri: String) { - if (interactor.isBuyProviderRedirectLink(uri)) { - showMessage(resourceManager.getString(R.string.buy_completed)) - } - } - private fun verifyUserIfNeed() { launch { if (interactor.isAccountSelected() && interactor.isPinCodeSet()) { @@ -135,4 +160,15 @@ class RootViewModel( override fun onCleared() { rootScope.cancel() } + + fun handleDeepLink(data: Uri) { + launch { + try { + deepLinkHandler.handleDeepLink(data) + } catch (e: DeepLinkHandlingException) { + val errorMessage = formatDeepLinkHandlingException(resourceManager, e) + showError(errorMessage) + } + } + } } diff --git a/app/src/main/java/io/novafoundation/nova/app/root/presentation/deepLinks/DeepLinkHandler.kt b/app/src/main/java/io/novafoundation/nova/app/root/presentation/deepLinks/DeepLinkHandler.kt new file mode 100644 index 0000000000..a49bd6c4cd --- /dev/null +++ b/app/src/main/java/io/novafoundation/nova/app/root/presentation/deepLinks/DeepLinkHandler.kt @@ -0,0 +1,17 @@ +package io.novafoundation.nova.app.root.presentation.deepLinks + +import android.net.Uri +import kotlinx.coroutines.flow.Flow + +interface DeepLinkHandler { + + val callbackFlow: Flow + + suspend fun matches(data: Uri): Boolean + + suspend fun handleDeepLink(data: Uri) +} + +sealed interface CallbackEvent { + data class Message(val message: String) : CallbackEvent +} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/presentation/deepLinks/RootDeepLinkHandler.kt b/app/src/main/java/io/novafoundation/nova/app/root/presentation/deepLinks/RootDeepLinkHandler.kt new file mode 100644 index 0000000000..cfac9aad8e --- /dev/null +++ b/app/src/main/java/io/novafoundation/nova/app/root/presentation/deepLinks/RootDeepLinkHandler.kt @@ -0,0 +1,23 @@ +package io.novafoundation.nova.app.root.presentation.deepLinks + +import android.net.Uri +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.merge + +class RootDeepLinkHandler( + private val nestedHandlers: Collection, +) : DeepLinkHandler { + + override val callbackFlow: Flow = nestedHandlers + .mapNotNull { it.callbackFlow } + .merge() + + override suspend fun matches(data: Uri): Boolean { + return nestedHandlers.any { it.matches(data) } + } + + override suspend fun handleDeepLink(data: Uri) { + nestedHandlers.find { it.matches(data) } + ?.handleDeepLink(data) + } +} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/presentation/deepLinks/common/DeepLinkErrorFormatter.kt b/app/src/main/java/io/novafoundation/nova/app/root/presentation/deepLinks/common/DeepLinkErrorFormatter.kt new file mode 100644 index 0000000000..8211a50d51 --- /dev/null +++ b/app/src/main/java/io/novafoundation/nova/app/root/presentation/deepLinks/common/DeepLinkErrorFormatter.kt @@ -0,0 +1,47 @@ +package io.novafoundation.nova.app.root.presentation.deepLinks.common + +import io.novafoundation.nova.app.R +import io.novafoundation.nova.app.root.presentation.deepLinks.common.DeepLinkHandlingException.DAppHandlingException +import io.novafoundation.nova.app.root.presentation.deepLinks.common.DeepLinkHandlingException.ImportMnemonicHandlingException +import io.novafoundation.nova.app.root.presentation.deepLinks.common.DeepLinkHandlingException.ReferendumHandlingException +import io.novafoundation.nova.common.resources.ResourceManager + +fun formatDeepLinkHandlingException(resourceManager: ResourceManager, exception: DeepLinkHandlingException): String { + return when (exception) { + is ReferendumHandlingException -> handleReferendumException(resourceManager, exception) + + is DAppHandlingException -> handleDAppException(resourceManager, exception) + + is ImportMnemonicHandlingException -> handleImportMnemonicException(resourceManager, exception) + } +} + +private fun handleReferendumException(resourceManager: ResourceManager, exception: ReferendumHandlingException): String { + return when (exception) { + ReferendumHandlingException.ReferendumIsNotSpecified -> resourceManager.getString(R.string.referendim_details_not_found_title) + + ReferendumHandlingException.ChainIsNotFound -> resourceManager.getString(R.string.deep_linking_chain_id_is_not_found) + + ReferendumHandlingException.GovernanceTypeIsNotSpecified -> resourceManager.getString(R.string.deep_linking_governance_type_is_not_specified) + + ReferendumHandlingException.GovernanceTypeIsNotSupported -> resourceManager.getString(R.string.deep_linking_governance_type_is_not_supported) + } +} + +fun handleDAppException(resourceManager: ResourceManager, exception: DAppHandlingException): String { + return when (exception) { + DAppHandlingException.UrlIsInvalid -> resourceManager.getString(R.string.deep_linking_url_is_invalid) + + is DAppHandlingException.DomainIsNotMatched -> resourceManager.getString(R.string.deep_linking_domain_is_not_matched, exception.domain) + } +} + +fun handleImportMnemonicException(resourceManager: ResourceManager, exception: ImportMnemonicHandlingException): String { + return when (exception) { + ImportMnemonicHandlingException.InvalidMnemonic -> resourceManager.getString(R.string.deep_linking_invalid_mnemonic) + + ImportMnemonicHandlingException.InvalidCryptoType -> resourceManager.getString(R.string.deep_linking_invalid_crypto_type) + + ImportMnemonicHandlingException.InvalidDerivationPath -> resourceManager.getString(R.string.deep_linking_invalid_derivation_path) + } +} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/presentation/deepLinks/common/DeepLinkHandlingException.kt b/app/src/main/java/io/novafoundation/nova/app/root/presentation/deepLinks/common/DeepLinkHandlingException.kt new file mode 100644 index 0000000000..664ab35eaf --- /dev/null +++ b/app/src/main/java/io/novafoundation/nova/app/root/presentation/deepLinks/common/DeepLinkHandlingException.kt @@ -0,0 +1,31 @@ +package io.novafoundation.nova.app.root.presentation.deepLinks.common + +sealed class DeepLinkHandlingException : Exception() { + + sealed class ReferendumHandlingException : DeepLinkHandlingException() { + + object ReferendumIsNotSpecified : ReferendumHandlingException() + + object ChainIsNotFound : ReferendumHandlingException() + + object GovernanceTypeIsNotSpecified : ReferendumHandlingException() + + object GovernanceTypeIsNotSupported : ReferendumHandlingException() + } + + sealed class DAppHandlingException : DeepLinkHandlingException() { + + object UrlIsInvalid : DAppHandlingException() + + class DomainIsNotMatched(val domain: String) : DAppHandlingException() + } + + sealed class ImportMnemonicHandlingException : DeepLinkHandlingException() { + + object InvalidMnemonic : ImportMnemonicHandlingException() + + object InvalidCryptoType : ImportMnemonicHandlingException() + + object InvalidDerivationPath : ImportMnemonicHandlingException() + } +} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/presentation/deepLinks/handlers/BuyCallbackDeepLinkHandler.kt b/app/src/main/java/io/novafoundation/nova/app/root/presentation/deepLinks/handlers/BuyCallbackDeepLinkHandler.kt new file mode 100644 index 0000000000..82387bc2f7 --- /dev/null +++ b/app/src/main/java/io/novafoundation/nova/app/root/presentation/deepLinks/handlers/BuyCallbackDeepLinkHandler.kt @@ -0,0 +1,27 @@ +package io.novafoundation.nova.app.root.presentation.deepLinks.handlers + +import android.net.Uri +import io.novafoundation.nova.app.R +import io.novafoundation.nova.app.root.domain.RootInteractor +import io.novafoundation.nova.app.root.presentation.deepLinks.CallbackEvent +import io.novafoundation.nova.app.root.presentation.deepLinks.DeepLinkHandler +import io.novafoundation.nova.common.resources.ResourceManager +import io.novafoundation.nova.common.utils.singleReplaySharedFlow +import kotlinx.coroutines.flow.MutableSharedFlow + +class BuyCallbackDeepLinkHandler( + private val interactor: RootInteractor, + private val resourceManager: ResourceManager +) : DeepLinkHandler { + + override val callbackFlow: MutableSharedFlow = singleReplaySharedFlow() + + override suspend fun matches(data: Uri): Boolean { + return interactor.isBuyProviderRedirectLink(data.toString()) + } + + override suspend fun handleDeepLink(data: Uri) { + val message = resourceManager.getString(R.string.buy_completed) + callbackFlow.emit(CallbackEvent.Message(message)) + } +} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/presentation/deepLinks/handlers/DAppDeepLinkHandler.kt b/app/src/main/java/io/novafoundation/nova/app/root/presentation/deepLinks/handlers/DAppDeepLinkHandler.kt new file mode 100644 index 0000000000..0afdc6a690 --- /dev/null +++ b/app/src/main/java/io/novafoundation/nova/app/root/presentation/deepLinks/handlers/DAppDeepLinkHandler.kt @@ -0,0 +1,47 @@ +package io.novafoundation.nova.app.root.presentation.deepLinks.handlers + +import android.net.Uri +import io.novafoundation.nova.app.root.presentation.deepLinks.CallbackEvent +import io.novafoundation.nova.app.root.presentation.deepLinks.DeepLinkHandler +import io.novafoundation.nova.app.root.presentation.deepLinks.common.DeepLinkHandlingException.DAppHandlingException +import io.novafoundation.nova.common.utils.Urls +import io.novafoundation.nova.common.utils.sequrity.AutomaticInteractionGate +import io.novafoundation.nova.common.utils.sequrity.awaitInteractionAllowed +import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository +import io.novafoundation.nova.feature_dapp_api.data.repository.DAppMetadataRepository +import io.novafoundation.nova.feature_dapp_impl.DAppRouter +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow + +private const val DAPP_DEEP_LINK_PREFIX = "/open/dapp" + +class DAppDeepLinkHandler( + private val accountRepository: AccountRepository, + private val dappRepository: DAppMetadataRepository, + private val dAppRouter: DAppRouter, + private val automaticInteractionGate: AutomaticInteractionGate +) : DeepLinkHandler { + + override val callbackFlow: Flow = emptyFlow() + + override suspend fun matches(data: Uri): Boolean { + val path = data.path ?: return false + return path.startsWith(DAPP_DEEP_LINK_PREFIX) + } + + override suspend fun handleDeepLink(data: Uri) { + automaticInteractionGate.awaitInteractionAllowed() + + val url = data.getDappUrl() ?: throw DAppHandlingException.UrlIsInvalid + val normalizedUrl = runCatching { Urls.normalizeUrl(url) }.getOrNull() ?: throw DAppHandlingException.UrlIsInvalid + + val dAppMetadata = dappRepository.syncAndGetDapp(normalizedUrl) + if (dAppMetadata == null) throw DAppHandlingException.DomainIsNotMatched(normalizedUrl) + + dAppRouter.openDAppBrowser(normalizedUrl) + } + + private fun Uri.getDappUrl(): String? { + return getQueryParameter("url") + } +} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/presentation/deepLinks/handlers/ImportMnemonicDeepLinkHandler.kt b/app/src/main/java/io/novafoundation/nova/app/root/presentation/deepLinks/handlers/ImportMnemonicDeepLinkHandler.kt new file mode 100644 index 0000000000..65134ab986 --- /dev/null +++ b/app/src/main/java/io/novafoundation/nova/app/root/presentation/deepLinks/handlers/ImportMnemonicDeepLinkHandler.kt @@ -0,0 +1,118 @@ +package io.novafoundation.nova.app.root.presentation.deepLinks.handlers + +import android.net.Uri +import com.walletconnect.util.hexToBytes +import io.novafoundation.nova.app.root.presentation.deepLinks.CallbackEvent +import io.novafoundation.nova.app.root.presentation.deepLinks.DeepLinkHandler +import io.novafoundation.nova.app.root.presentation.deepLinks.common.DeepLinkHandlingException +import io.novafoundation.nova.app.root.presentation.deepLinks.common.DeepLinkHandlingException.ImportMnemonicHandlingException +import io.novafoundation.nova.common.utils.sequrity.AutomaticInteractionGate +import io.novafoundation.nova.common.utils.sequrity.awaitInteractionAllowed +import io.novafoundation.nova.core.model.CryptoType +import io.novafoundation.nova.feature_account_api.data.derivationPath.DerivationPathDecoder +import io.novafoundation.nova.feature_account_api.presenatation.account.add.AddAccountPayload +import io.novafoundation.nova.feature_account_api.presenatation.account.add.ImportAccountPayload +import io.novafoundation.nova.feature_account_api.presenatation.account.add.ImportType +import io.novafoundation.nova.feature_account_api.presenatation.account.common.model.AdvancedEncryptionModel +import io.novafoundation.nova.feature_account_api.domain.account.common.EncryptionDefaults +import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository +import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter +import jp.co.soramitsu.fearless_utils.encrypt.mnemonic.Mnemonic +import jp.co.soramitsu.fearless_utils.encrypt.mnemonic.MnemonicCreator +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow + +private const val IMPORT_WALLET_DEEP_LINK_PREFIX = "/create/wallet" + +class ImportMnemonicDeepLinkHandler( + private val accountRouter: AccountRouter, + private val encryptionDefaults: EncryptionDefaults, + private val accountRepository: AccountRepository, + private val automaticInteractionGate: AutomaticInteractionGate, +) : DeepLinkHandler { + + override val callbackFlow: Flow = emptyFlow() + + override suspend fun matches(data: Uri): Boolean { + val path = data.path ?: return false + + return path.startsWith(IMPORT_WALLET_DEEP_LINK_PREFIX) + } + + override suspend fun handleDeepLink(data: Uri) { + if (accountRepository.hasMetaAccounts()) { + automaticInteractionGate.awaitInteractionAllowed() + } + + val mnemonic = data.getMnemonic() + val substrateDP = data.getSubstrateDP() + val ethereumDerivationPath = data.getEthereumDP() + + val isDerivationPathsValid = isDerivationPathsValid(substrateDP, ethereumDerivationPath) + if (!isDerivationPathsValid) throw ImportMnemonicHandlingException.InvalidDerivationPath + + val importAccountPayload = ImportAccountPayload( + prepareMnemonicPreset( + mnemonic = mnemonic.words, + substrateCryptoType = data.getSubstrateCryptoType().asCryptoType { encryptionDefaults.substrateCryptoType }, + substrateDP = data.getSubstrateDP(), + ethereumDP = data.getEthereumDP() + ), + AddAccountPayload.MetaAccount + ) + + accountRouter.openImportAccountScreen(importAccountPayload) + } + + private fun prepareMnemonicPreset( + mnemonic: String, + substrateCryptoType: CryptoType?, + substrateDP: String?, + ethereumDP: String? + ): ImportType.Mnemonic { + return ImportType.Mnemonic( + mnemonic = mnemonic, + preset = AdvancedEncryptionModel( + substrateCryptoType = substrateCryptoType ?: encryptionDefaults.substrateCryptoType, + substrateDerivationPath = substrateDP ?: encryptionDefaults.substrateDerivationPath, + ethereumCryptoType = encryptionDefaults.ethereumCryptoType, + ethereumDerivationPath = ethereumDP ?: encryptionDefaults.ethereumDerivationPath + ) + ) + } + + private fun Uri.getMnemonic(): Mnemonic { + val mnemonicHex = getQueryParameter("mnemonic") + return runCatching { MnemonicCreator.fromEntropy(mnemonicHex!!.hexToBytes()) }.getOrNull() + ?: throw ImportMnemonicHandlingException.InvalidMnemonic + } + + private fun Uri.getSubstrateCryptoType(): String? { + return getQueryParameter("cryptoType") + } + + private fun Uri.getSubstrateDP(): String? { + return getQueryParameter("substrateDP") + } + + private fun Uri.getEthereumDP(): String? { + return getQueryParameter("evmDP") + } + + private fun String?.asCryptoType(fallback: () -> CryptoType): CryptoType { + val intCryptoType = this?.toIntOrNull() + + return when (intCryptoType) { + null -> fallback() + 0 -> CryptoType.SR25519 + 1 -> CryptoType.ED25519 + 2 -> CryptoType.ECDSA + else -> throw DeepLinkHandlingException.ImportMnemonicHandlingException.InvalidCryptoType + } + } + + private fun isDerivationPathsValid(substrateDP: String?, ethereumDP: String?): Boolean { + return DerivationPathDecoder.isEthereumDerivationPathValid(ethereumDP) && + DerivationPathDecoder.isSubstrateDerivationPathValid(substrateDP) + } +} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/presentation/deepLinks/handlers/ReferendumDeepLinkHandler.kt b/app/src/main/java/io/novafoundation/nova/app/root/presentation/deepLinks/handlers/ReferendumDeepLinkHandler.kt new file mode 100644 index 0000000000..869b02e206 --- /dev/null +++ b/app/src/main/java/io/novafoundation/nova/app/root/presentation/deepLinks/handlers/ReferendumDeepLinkHandler.kt @@ -0,0 +1,81 @@ +package io.novafoundation.nova.app.root.presentation.deepLinks.handlers + +import android.net.Uri +import io.novafoundation.nova.app.root.presentation.deepLinks.CallbackEvent +import io.novafoundation.nova.app.root.presentation.deepLinks.DeepLinkHandler +import io.novafoundation.nova.app.root.presentation.deepLinks.common.DeepLinkHandlingException.ReferendumHandlingException +import io.novafoundation.nova.common.utils.sequrity.AutomaticInteractionGate +import io.novafoundation.nova.common.utils.sequrity.awaitInteractionAllowed +import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository +import io.novafoundation.nova.feature_governance_api.data.MutableGovernanceState +import io.novafoundation.nova.feature_governance_impl.presentation.GovernanceRouter +import io.novafoundation.nova.feature_governance_impl.presentation.referenda.details.ReferendumDetailsPayload +import io.novafoundation.nova.runtime.ext.utilityAsset +import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry +import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain +import io.novafoundation.nova.runtime.multiNetwork.getChainOrNull +import java.math.BigInteger +import kotlinx.coroutines.flow.MutableSharedFlow + +private const val GOV_DEEP_LINK_PREFIX = "/open/gov" + +class ReferendumDeepLinkHandler( + private val governanceRouter: GovernanceRouter, + private val chainRegistry: ChainRegistry, + private val mutableGovernanceState: MutableGovernanceState, + private val accountRepository: AccountRepository, + private val automaticInteractionGate: AutomaticInteractionGate +) : DeepLinkHandler { + + override val callbackFlow = MutableSharedFlow() + + override suspend fun matches(data: Uri): Boolean { + val path = data.path ?: return false + + return path.startsWith(GOV_DEEP_LINK_PREFIX) + } + + override suspend fun handleDeepLink(data: Uri) { + automaticInteractionGate.awaitInteractionAllowed() + + val chainId = data.getChainId() ?: throw ReferendumHandlingException.ChainIsNotFound + val referendumId = data.getReferendumId() ?: throw ReferendumHandlingException.ReferendumIsNotSpecified + val chain = chainRegistry.getChainOrNull(chainId) ?: throw ReferendumHandlingException.ChainIsNotFound + val governanceType = data.getGovernanceType(chain) + val payload = ReferendumDetailsPayload(referendumId) + + mutableGovernanceState.update(chain.id, chain.utilityAsset.id, governanceType) + governanceRouter.openReferendum(payload) + } + + private fun Uri.getChainId(): String? { + return getQueryParameter("chainId") + } + + private fun Uri.getReferendumId(): BigInteger? { + return getQueryParameter("id") + ?.toBigIntegerOrNull() + } + + private suspend fun Uri.getGovernanceType(chain: Chain): Chain.Governance { + val supportedGov = chain.governance + val govType = getQueryParameter("type") + ?.toIntOrNull() + + val cantSelectGovType = govType == null && supportedGov.size > 1 && !supportedGov.contains(Chain.Governance.V2) + + return when { + cantSelectGovType -> throw ReferendumHandlingException.GovernanceTypeIsNotSpecified + govType == null && supportedGov.contains(Chain.Governance.V2) -> Chain.Governance.V2 + govType == null && supportedGov.size == 1 -> supportedGov.first() + govType == 0 && supportedGov.contains(Chain.Governance.V2) -> Chain.Governance.V2 + govType == 1 && supportedGov.contains(Chain.Governance.V1) -> Chain.Governance.V1 + govType !in 0..1 -> throw ReferendumHandlingException.GovernanceTypeIsNotSupported + else -> throw ReferendumHandlingException.GovernanceTypeIsNotSupported + } + } + + private fun Chain.Governance.takeIfContainedIn(list: List): Chain.Governance? { + return list.firstOrNull { it == this } + } +} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/presentation/deepLinks/handlers/StakingDashboardDeepLinkHandler.kt b/app/src/main/java/io/novafoundation/nova/app/root/presentation/deepLinks/handlers/StakingDashboardDeepLinkHandler.kt new file mode 100644 index 0000000000..4223980631 --- /dev/null +++ b/app/src/main/java/io/novafoundation/nova/app/root/presentation/deepLinks/handlers/StakingDashboardDeepLinkHandler.kt @@ -0,0 +1,31 @@ +package io.novafoundation.nova.app.root.presentation.deepLinks.handlers + +import android.net.Uri +import io.novafoundation.nova.app.root.presentation.deepLinks.CallbackEvent +import io.novafoundation.nova.app.root.presentation.deepLinks.DeepLinkHandler +import io.novafoundation.nova.common.utils.sequrity.AutomaticInteractionGate +import io.novafoundation.nova.common.utils.sequrity.awaitInteractionAllowed +import io.novafoundation.nova.feature_assets.presentation.AssetsRouter +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow + +private const val STAKING_DASHBOARD_DEEP_LINK_PREFIX = "/open/staking" + +class StakingDashboardDeepLinkHandler( + private val assetsRouter: AssetsRouter, + private val automaticInteractionGate: AutomaticInteractionGate +) : DeepLinkHandler { + + override val callbackFlow: Flow = emptyFlow() + + override suspend fun matches(data: Uri): Boolean { + val path = data.path ?: return false + return path.startsWith(STAKING_DASHBOARD_DEEP_LINK_PREFIX) + } + + override suspend fun handleDeepLink(data: Uri) { + automaticInteractionGate.awaitInteractionAllowed() + + assetsRouter.openStaking() + } +} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/presentation/di/DeepLinkModule.kt b/app/src/main/java/io/novafoundation/nova/app/root/presentation/di/DeepLinkModule.kt new file mode 100644 index 0000000000..37bf9c2d27 --- /dev/null +++ b/app/src/main/java/io/novafoundation/nova/app/root/presentation/di/DeepLinkModule.kt @@ -0,0 +1,103 @@ +package io.novafoundation.nova.app.root.presentation.di + +import dagger.Module +import dagger.Provides +import dagger.multibindings.IntoSet +import io.novafoundation.nova.app.root.domain.RootInteractor +import io.novafoundation.nova.app.root.presentation.deepLinks.handlers.BuyCallbackDeepLinkHandler +import io.novafoundation.nova.app.root.presentation.deepLinks.handlers.DAppDeepLinkHandler +import io.novafoundation.nova.app.root.presentation.deepLinks.DeepLinkHandler +import io.novafoundation.nova.app.root.presentation.deepLinks.handlers.ImportMnemonicDeepLinkHandler +import io.novafoundation.nova.app.root.presentation.deepLinks.handlers.ReferendumDeepLinkHandler +import io.novafoundation.nova.app.root.presentation.deepLinks.RootDeepLinkHandler +import io.novafoundation.nova.app.root.presentation.deepLinks.handlers.StakingDashboardDeepLinkHandler +import io.novafoundation.nova.common.resources.ResourceManager +import io.novafoundation.nova.common.utils.sequrity.AutomaticInteractionGate +import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository +import io.novafoundation.nova.feature_account_api.domain.account.common.EncryptionDefaults +import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter +import io.novafoundation.nova.feature_assets.presentation.AssetsRouter +import io.novafoundation.nova.feature_dapp_api.data.repository.DAppMetadataRepository +import io.novafoundation.nova.feature_dapp_impl.DAppRouter +import io.novafoundation.nova.feature_governance_api.data.MutableGovernanceState +import io.novafoundation.nova.feature_governance_impl.presentation.GovernanceRouter +import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry + +@Module +class DeepLinkModule { + + @Provides + @IntoSet + fun provideStakingDashboardDeepLinkHandler( + assetsRouter: AssetsRouter, + automaticInteractionGate: AutomaticInteractionGate + ): DeepLinkHandler { + return StakingDashboardDeepLinkHandler(assetsRouter, automaticInteractionGate) + } + + @Provides + @IntoSet + fun provideImportMnemonicDeepLinkHandler( + accountRouter: AccountRouter, + encryptionDefaults: EncryptionDefaults, + accountRepository: AccountRepository, + automaticInteractionGate: AutomaticInteractionGate + ): DeepLinkHandler { + return ImportMnemonicDeepLinkHandler( + accountRouter, + encryptionDefaults, + accountRepository, + automaticInteractionGate + ) + } + + @Provides + @IntoSet + fun provideDappDeepLinkHandler( + accountRepository: AccountRepository, + dAppMetadataRepository: DAppMetadataRepository, + dAppRouter: DAppRouter, + automaticInteractionGate: AutomaticInteractionGate + ): DeepLinkHandler { + return DAppDeepLinkHandler( + accountRepository, + dAppMetadataRepository, + dAppRouter, + automaticInteractionGate + ) + } + + @Provides + @IntoSet + fun provideReferendumDeepLinkHandler( + governanceRouter: GovernanceRouter, + chainRegistry: ChainRegistry, + mutableGovernanceState: MutableGovernanceState, + accountRepository: AccountRepository, + automaticInteractionGate: AutomaticInteractionGate + ): DeepLinkHandler { + return ReferendumDeepLinkHandler( + governanceRouter, + chainRegistry, + mutableGovernanceState, + accountRepository, + automaticInteractionGate + ) + } + + @Provides + @IntoSet + fun provideBuyCallbackDeepLinkHandler( + interactor: RootInteractor, + resourceManager: ResourceManager + ): DeepLinkHandler { + return BuyCallbackDeepLinkHandler(interactor, resourceManager) + } + + @Provides + fun provideRootDeepLinkHandler( + deepLinkHandlers: Set<@JvmSuppressWildcards DeepLinkHandler> + ): RootDeepLinkHandler { + return RootDeepLinkHandler(deepLinkHandlers.toList()) + } +} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/presentation/di/RootActivityModule.kt b/app/src/main/java/io/novafoundation/nova/app/root/presentation/di/RootActivityModule.kt index 069489b081..5ac9495209 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/presentation/di/RootActivityModule.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/presentation/di/RootActivityModule.kt @@ -8,24 +8,28 @@ import dagger.Provides import dagger.multibindings.IntoMap import io.novafoundation.nova.app.root.domain.RootInteractor import io.novafoundation.nova.app.root.presentation.RootRouter -import io.novafoundation.nova.common.utils.coroutines.RootScope import io.novafoundation.nova.app.root.presentation.RootViewModel +import io.novafoundation.nova.app.root.presentation.deepLinks.RootDeepLinkHandler import io.novafoundation.nova.common.di.viewmodel.ViewModelKey import io.novafoundation.nova.common.di.viewmodel.ViewModelModule import io.novafoundation.nova.common.mixin.api.NetworkStateMixin import io.novafoundation.nova.common.resources.ResourceManager import io.novafoundation.nova.common.sequrity.SafeModeService +import io.novafoundation.nova.common.utils.sequrity.AutomaticInteractionGate +import io.novafoundation.nova.common.utils.coroutines.RootScope import io.novafoundation.nova.common.utils.sequrity.BackgroundAccessObserver import io.novafoundation.nova.feature_crowdloan_api.domain.contributions.ContributionsInteractor import io.novafoundation.nova.feature_currency_api.domain.CurrencyInteractor import io.novafoundation.nova.feature_versions_api.domain.UpdateNotificationsInteractor +import io.novafoundation.nova.feature_wallet_connect_api.domain.sessions.WalletConnectSessionsUseCase import io.novafoundation.nova.feature_wallet_connect_api.presentation.WalletConnectService import io.novafoundation.nova.runtime.multiNetwork.connection.ChainConnection import kotlinx.coroutines.flow.MutableStateFlow @Module( includes = [ - ViewModelModule::class + ViewModelModule::class, + DeepLinkModule::class ] ) class RootActivityModule { @@ -45,6 +49,9 @@ class RootActivityModule { safeModeService: SafeModeService, updateNotificationsInteractor: UpdateNotificationsInteractor, walletConnectService: WalletConnectService, + walletConnectSessionsUseCase: WalletConnectSessionsUseCase, + deepLinkHandler: RootDeepLinkHandler, + automaticInteractionGate: AutomaticInteractionGate, rootScope: RootScope ): ViewModel { return RootViewModel( @@ -59,6 +66,9 @@ class RootActivityModule { safeModeService, updateNotificationsInteractor, walletConnectService, + walletConnectSessionsUseCase, + deepLinkHandler, + automaticInteractionGate, rootScope ) } diff --git a/app/src/main/java/io/novafoundation/nova/app/root/presentation/main/MainFragment.kt b/app/src/main/java/io/novafoundation/nova/app/root/presentation/main/MainFragment.kt index af4541766d..1f45f1fc9f 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/presentation/main/MainFragment.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/presentation/main/MainFragment.kt @@ -65,8 +65,7 @@ class MainFragment : BaseFragment() { insets } - val nestedNavHostFragment = - childFragmentManager.findFragmentById(R.id.bottomNavHost) as NavHostFragment + val nestedNavHostFragment = childFragmentManager.findFragmentById(R.id.bottomNavHost) as NavHostFragment navController = nestedNavHostFragment.navController stakingDashboardNavigator.setStakingTabNavController(navController!!) diff --git a/app/src/main/res/navigation/dapp_browser_graph.xml b/app/src/main/res/navigation/dapp_browser_graph.xml index 64d0e23a37..5388845118 100644 --- a/app/src/main/res/navigation/dapp_browser_graph.xml +++ b/app/src/main/res/navigation/dapp_browser_graph.xml @@ -11,6 +11,16 @@ android:label="DAppBrowserFragment" tools:layout="@layout/fragment_dapp_browser"> + + + android:id="@+id/import_nav_graph" + app:startDestination="@id/importAccountFragment"> @@ -33,9 +33,9 @@ + android:label="AdvancedEncryptionFragment" + tools:layout="@layout/fragment_advanced_encryption" /> - - - - - - + app:popUpTo="@id/selectSendFragment" + app:popUpToInclusive="true" /> + android:label="AssetSwapFlowFragment" /> diff --git a/app/src/main/res/navigation/onboarding_nav_graph.xml b/app/src/main/res/navigation/onboarding_nav_graph.xml index f73d6e8b0f..e6a88ef0f9 100644 --- a/app/src/main/res/navigation/onboarding_nav_graph.xml +++ b/app/src/main/res/navigation/onboarding_nav_graph.xml @@ -19,16 +19,6 @@ app:popEnterAnim="@anim/fragment_close_enter" app:popExitAnim="@anim/fragment_close_exit" /> - - - - + + + + + + + + + + + + .setupConfirmationDialog(@StyleRes style: Int, awaitableMixin: ConfirmationAwaitable) { +fun BaseFragment<*>.setupConfirmationDialog( + @StyleRes style: Int, + awaitableMixin: ConfirmationAwaitable +) { awaitableMixin.awaitableActionLiveData.observeEvent { action -> dialog(requireContext(), style) { setTitle(action.payload.title) - setMessage(action.payload.message) + action.payload.message?.let { setMessage(action.payload.message) } setPositiveButton(action.payload.positiveButton) { _, _ -> action.onSuccess(Unit) } - setNegativeButton(action.payload.negativeButton) { _, _ -> action.onCancel() } + + if (action.payload.negativeButton != null) { + setNegativeButton(action.payload.negativeButton) { _, _ -> action.onCancel() } + } + setOnCancelListener { action.onCancel() } } } @@ -26,9 +38,13 @@ fun BaseFragment<*>.setupConfirmationOrDenyDialog(@StyleRes style: Int, awaitabl awaitableMixin.awaitableActionLiveData.observeEvent { action -> dialog(requireContext(), style) { setTitle(action.payload.title) - setMessage(action.payload.message) + action.payload.message?.let { setMessage(action.payload.message) } setPositiveButton(action.payload.positiveButton) { _, _ -> action.onSuccess(true) } - setNegativeButton(action.payload.negativeButton) { _, _ -> action.onSuccess(false) } + + if (action.payload.negativeButton != null) { + setNegativeButton(action.payload.negativeButton) { _, _ -> action.onSuccess(false) } + } + setOnCancelListener { action.onCancel() } } } diff --git a/common/src/main/java/io/novafoundation/nova/common/mixin/hints/HintsUi.kt b/common/src/main/java/io/novafoundation/nova/common/mixin/hints/HintsUi.kt index c5f81873d0..e1c460354d 100644 --- a/common/src/main/java/io/novafoundation/nova/common/mixin/hints/HintsUi.kt +++ b/common/src/main/java/io/novafoundation/nova/common/mixin/hints/HintsUi.kt @@ -31,7 +31,7 @@ class HintsView @JvmOverloads constructor( TextView(context).apply { setTextAppearance(R.style.TextAppearance_NovaFoundation_Regular_Caption1) - setTextColorRes(R.color.text_tertiary) + setTextColorRes(R.color.text_secondary) setDrawableStart(R.drawable.ic_nova, widthInDp = 16, paddingInDp = 8, tint = R.color.icon_secondary) text = hint diff --git a/common/src/main/java/io/novafoundation/nova/common/resources/ResourceManager.kt b/common/src/main/java/io/novafoundation/nova/common/resources/ResourceManager.kt index 16bf9fecf2..4e48df1a88 100644 --- a/common/src/main/java/io/novafoundation/nova/common/resources/ResourceManager.kt +++ b/common/src/main/java/io/novafoundation/nova/common/resources/ResourceManager.kt @@ -1,7 +1,9 @@ package io.novafoundation.nova.common.resources +import android.graphics.Typeface import android.graphics.drawable.Drawable import androidx.annotation.DrawableRes +import androidx.annotation.FontRes import androidx.annotation.RawRes import androidx.annotation.StringRes import io.novafoundation.nova.common.R @@ -36,6 +38,8 @@ interface ResourceManager { fun getDrawable(@DrawableRes id: Int): Drawable fun getDimensionPixelSize(id: Int): Int + + fun getFont(@FontRes fontRes: Int): Typeface } fun ResourceManager.formatTimeLeft(elapsedTimeInMillis: Long): String { diff --git a/common/src/main/java/io/novafoundation/nova/common/resources/ResourceManagerImpl.kt b/common/src/main/java/io/novafoundation/nova/common/resources/ResourceManagerImpl.kt index 366679e43b..5a906061ac 100644 --- a/common/src/main/java/io/novafoundation/nova/common/resources/ResourceManagerImpl.kt +++ b/common/src/main/java/io/novafoundation/nova/common/resources/ResourceManagerImpl.kt @@ -1,5 +1,6 @@ package io.novafoundation.nova.common.resources +import android.graphics.Typeface import android.graphics.drawable.Drawable import android.text.format.DateUtils import androidx.core.content.ContextCompat @@ -107,4 +108,8 @@ class ResourceManagerImpl( override fun getDimensionPixelSize(id: Int): Int { return contextManager.getApplicationContext().resources.getDimensionPixelSize(id) } + + override fun getFont(fontRes: Int): Typeface { + return contextManager.getApplicationContext().resources.getFont(fontRes) + } } diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/FearlessLibExt.kt b/common/src/main/java/io/novafoundation/nova/common/utils/FearlessLibExt.kt index 39b6793ba7..ea6ad0c683 100644 --- a/common/src/main/java/io/novafoundation/nova/common/utils/FearlessLibExt.kt +++ b/common/src/main/java/io/novafoundation/nova/common/utils/FearlessLibExt.kt @@ -273,6 +273,8 @@ fun GenericCall.Instance.instanceOf(functionCandidate: MetadataFunction): Boolea fun GenericCall.Instance.instanceOf(moduleName: String, callName: String): Boolean = moduleName == module.name && callName == function.name +fun GenericCall.Instance.instanceOf(moduleName: String, vararg callNames: String): Boolean = moduleName == module.name && function.name in callNames + fun GenericEvent.Instance.instanceOf(moduleName: String, eventName: String): Boolean = moduleName == module.name && eventName == event.name fun structOf(vararg pairs: Pair) = Struct.Instance(mapOf(*pairs)) @@ -340,4 +342,6 @@ object Modules { const val TRANSACTION_PAYMENT = "TransactionPayment" const val ASSET_TX_PAYMENT = "AssetTxPayment" + + const val UTILITY = "Utility" } diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/JsonExt.kt b/common/src/main/java/io/novafoundation/nova/common/utils/JsonExt.kt index 5c2c7c3f82..0c7995b048 100644 --- a/common/src/main/java/io/novafoundation/nova/common/utils/JsonExt.kt +++ b/common/src/main/java/io/novafoundation/nova/common/utils/JsonExt.kt @@ -19,6 +19,12 @@ fun Any?.asGsonParsedLongOrNull(): Long? = when (this) { else -> null } +fun Any?.asGsonParsedIntOrNull(): Int? = when (this) { + is Number -> toInt() + is String -> toIntOrNull() + else -> null +} + fun Any?.asGsonParsedNumber(): BigInteger = asGsonParsedNumberOrNull() ?: throw IllegalArgumentException("Failed to convert gson-parsed object to number") 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 a94a0a177c..c26f57fbab 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 @@ -1,5 +1,7 @@ package io.novafoundation.nova.common.utils +import android.content.Context +import android.graphics.Typeface import android.graphics.drawable.Drawable import android.text.Spannable import android.text.SpannableStringBuilder @@ -8,8 +10,11 @@ import android.text.TextPaint import android.text.style.ClickableSpan import android.text.style.ForegroundColorSpan import android.text.style.ImageSpan +import android.text.style.TypefaceSpan import android.view.View +import androidx.annotation.FontRes import androidx.core.text.toSpannable +import io.novafoundation.nova.common.resources.ResourceManager import io.novafoundation.nova.common.utils.formatting.spannable.SpannableFormatter fun CharSequence.toSpannable(span: Any): Spannable { @@ -41,6 +46,12 @@ fun clickableSpan(onClick: () -> Unit) = object : ClickableSpan() { fun colorSpan(color: Int) = ForegroundColorSpan(color) +fun fontSpan(resourceManager: ResourceManager, @FontRes fontRes: Int) = fontSpan(resourceManager.getFont(fontRes)) + +fun fontSpan(context: Context, @FontRes fontRes: Int) = fontSpan(context.resources.getFont(fontRes)) + +fun fontSpan(typeface: Typeface) = TypefaceSpan(typeface) + fun drawableSpan(drawable: Drawable) = ImageSpan(drawable) fun CharSequence.formatAsSpannable(vararg args: Any): SpannedString { diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/selectionStore/ComputationalCacheSelectionStoreProvider.kt b/common/src/main/java/io/novafoundation/nova/common/utils/selectionStore/ComputationalCacheSelectionStoreProvider.kt new file mode 100644 index 0000000000..aff798bad0 --- /dev/null +++ b/common/src/main/java/io/novafoundation/nova/common/utils/selectionStore/ComputationalCacheSelectionStoreProvider.kt @@ -0,0 +1,18 @@ +package io.novafoundation.nova.common.utils.selectionStore + +import io.novafoundation.nova.common.data.memory.ComputationalCache +import kotlinx.coroutines.CoroutineScope + +abstract class ComputationalCacheSelectionStoreProvider>( + private val computationalCache: ComputationalCache, + private val key: String +) : SelectionStoreProvider { + + override suspend fun getSelectionStore(scope: CoroutineScope): T { + return computationalCache.useCache(key, scope) { + initSelectionStore() + } + } + + protected abstract fun initSelectionStore(): T +} diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/selectionStore/MutableSelectionStore.kt b/common/src/main/java/io/novafoundation/nova/common/utils/selectionStore/MutableSelectionStore.kt new file mode 100644 index 0000000000..64df841414 --- /dev/null +++ b/common/src/main/java/io/novafoundation/nova/common/utils/selectionStore/MutableSelectionStore.kt @@ -0,0 +1,16 @@ +package io.novafoundation.nova.common.utils.selectionStore + +import kotlinx.coroutines.flow.MutableStateFlow + +abstract class MutableSelectionStore : SelectionStore { + + override val currentSelectionFlow = MutableStateFlow(null) + + override fun getCurrentSelection(): T? { + return currentSelectionFlow.value + } + + fun updateSelection(selection: T) { + currentSelectionFlow.value = selection + } +} diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/selectionStore/SelectionStore.kt b/common/src/main/java/io/novafoundation/nova/common/utils/selectionStore/SelectionStore.kt new file mode 100644 index 0000000000..b0fe8cbd4a --- /dev/null +++ b/common/src/main/java/io/novafoundation/nova/common/utils/selectionStore/SelectionStore.kt @@ -0,0 +1,16 @@ +package io.novafoundation.nova.common.utils.selectionStore + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow + +interface SelectionStore { + + val currentSelectionFlow: Flow + + fun getCurrentSelection(): T? +} + +interface SelectionStoreProvider> { + + suspend fun getSelectionStore(scope: CoroutineScope): T +} diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/sequrity/AutomaticInteractionGate.kt b/common/src/main/java/io/novafoundation/nova/common/utils/sequrity/AutomaticInteractionGate.kt index 5c22cec545..03365cd945 100644 --- a/common/src/main/java/io/novafoundation/nova/common/utils/sequrity/AutomaticInteractionGate.kt +++ b/common/src/main/java/io/novafoundation/nova/common/utils/sequrity/AutomaticInteractionGate.kt @@ -34,6 +34,7 @@ internal class RealAutomaticInteractionGate : AutomaticInteractionGate, Coroutin initialCheck && backgroundCheck } .stateIn(this, SharingStarted.Eagerly, initialValue = false) + override fun initialPinPassed() { initialPinPassed.value = true } diff --git a/common/src/main/java/io/novafoundation/nova/common/view/AddressView.kt b/common/src/main/java/io/novafoundation/nova/common/view/AddressView.kt index 2b387c9767..6b6abedfde 100644 --- a/common/src/main/java/io/novafoundation/nova/common/view/AddressView.kt +++ b/common/src/main/java/io/novafoundation/nova/common/view/AddressView.kt @@ -33,7 +33,7 @@ class AddressView @JvmOverloads constructor( private fun applyStyleAttrs(attrs: AttributeSet) { val typedArray = context.obtainStyledAttributes(attrs, R.styleable.AddressView) - val textColorRes = typedArray.getResourceId(R.styleable.AddressView_android_textColor, R.color.text_tertiary) + val textColorRes = typedArray.getResourceId(R.styleable.AddressView_android_textColor, R.color.text_secondary) addressValue.setTextColorRes(textColorRes) typedArray.recycle() diff --git a/common/src/main/java/io/novafoundation/nova/common/view/PlaceholderView.kt b/common/src/main/java/io/novafoundation/nova/common/view/PlaceholderView.kt index 79d17fb49c..49d3b63262 100644 --- a/common/src/main/java/io/novafoundation/nova/common/view/PlaceholderView.kt +++ b/common/src/main/java/io/novafoundation/nova/common/view/PlaceholderView.kt @@ -27,9 +27,9 @@ class PlaceholderView @JvmOverloads constructor( ) : LinearLayout(context, attrs, defStyleAttr) { enum class Style(val showBackground: Boolean, val backgroundColorRes: Int?, val textColorRes: Int) { - BACKGROUND_PRIMARY(true, R.color.block_background, R.color.text_tertiary), - BACKGROUND_SECONDARY(true, R.color.block_background, R.color.text_tertiary), - NO_BACKGROUND(false, null, R.color.text_tertiary) + BACKGROUND_PRIMARY(true, R.color.block_background, R.color.text_secondary), + BACKGROUND_SECONDARY(true, R.color.block_background, R.color.text_secondary), + NO_BACKGROUND(false, null, R.color.text_secondary) } init { diff --git a/common/src/main/java/io/novafoundation/nova/common/view/TableCellView.kt b/common/src/main/java/io/novafoundation/nova/common/view/TableCellView.kt index 50a4f5aba5..384456962c 100644 --- a/common/src/main/java/io/novafoundation/nova/common/view/TableCellView.kt +++ b/common/src/main/java/io/novafoundation/nova/common/view/TableCellView.kt @@ -61,8 +61,8 @@ open class TableCellView @JvmOverloads constructor( return TableCellView(context).apply { layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT) - valueSecondary.setTextColorRes(R.color.text_tertiary) - title.setTextColorRes(R.color.text_tertiary) + valueSecondary.setTextColorRes(R.color.text_secondary) + title.setTextColorRes(R.color.text_secondary) } } } @@ -182,7 +182,7 @@ open class TableCellView @JvmOverloads constructor( } FieldStyle.SECONDARY -> { - valuePrimary.setTextColorRes(R.color.text_tertiary) + valuePrimary.setTextColorRes(R.color.text_secondary) } } } diff --git a/common/src/main/res/color/tab_item_text_color.xml b/common/src/main/res/color/tab_item_text_color.xml index 6e56fc9f9c..d623121b68 100644 --- a/common/src/main/res/color/tab_item_text_color.xml +++ b/common/src/main/res/color/tab_item_text_color.xml @@ -1,5 +1,5 @@ - + \ No newline at end of file diff --git a/common/src/main/res/drawable/ic_nova_wiki.xml b/common/src/main/res/drawable/ic_nova_wiki.xml new file mode 100644 index 0000000000..23ad3a6a5a --- /dev/null +++ b/common/src/main/res/drawable/ic_nova_wiki.xml @@ -0,0 +1,10 @@ + + + diff --git a/common/src/main/res/layout/bottom_sheeet_copier.xml b/common/src/main/res/layout/bottom_sheeet_copier.xml index 5d60c62c20..680a97ef28 100644 --- a/common/src/main/res/layout/bottom_sheeet_copier.xml +++ b/common/src/main/res/layout/bottom_sheeet_copier.xml @@ -22,7 +22,7 @@ android:paddingHorizontal="48dp" android:paddingTop="16dp" android:paddingBottom="8dp" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" android:textSize="13sp" tools:text="value" /> \ No newline at end of file diff --git a/common/src/main/res/layout/bottom_sheet_action_not_allowed.xml b/common/src/main/res/layout/bottom_sheet_action_not_allowed.xml index 08aa6e37cb..f879107f9c 100644 --- a/common/src/main/res/layout/bottom_sheet_action_not_allowed.xml +++ b/common/src/main/res/layout/bottom_sheet_action_not_allowed.xml @@ -36,7 +36,7 @@ android:layout_marginTop="8dp" android:gravity="center" android:text="@string/account_watch_key_missing_description" - android:textColor="@color/text_tertiary" /> + android:textColor="@color/text_secondary" /> \ No newline at end of file diff --git a/common/src/main/res/layout/button_large.xml b/common/src/main/res/layout/button_large.xml index c0e9b5fd07..c2ad60eac0 100644 --- a/common/src/main/res/layout/button_large.xml +++ b/common/src/main/res/layout/button_large.xml @@ -39,7 +39,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="12dp" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" android:visibility="gone" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toEndOf="@id/buttonLargeIcon" diff --git a/common/src/main/res/layout/item_operation_list_item.xml b/common/src/main/res/layout/item_operation_list_item.xml index afcf09a0ca..7f06bfdb39 100644 --- a/common/src/main/res/layout/item_operation_list_item.xml +++ b/common/src/main/res/layout/item_operation_list_item.xml @@ -43,7 +43,7 @@ android:layout_marginEnd="16dp" android:layout_marginBottom="9dp" android:lines="1" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" app:layout_constraintEnd_toStartOf="@+id/itemOperationValueLeftBarrier" app:layout_constraintStart_toStartOf="@+id/itemOperationHeader" app:layout_constraintTop_toBottomOf="@+id/itemOperationHeader" @@ -69,7 +69,7 @@ android:layout_height="wrap_content" android:layout_marginEnd="4dp" android:layout_marginBottom="8dp" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" app:layout_constraintEnd_toStartOf="@id/itemOperationValueRightSpace" app:layout_constraintTop_toBottomOf="@+id/itemOperationValuePrimary" tools:text="22:36" /> diff --git a/common/src/main/res/layout/item_text_header.xml b/common/src/main/res/layout/item_text_header.xml index ef21c9c796..36dfa74e27 100644 --- a/common/src/main/res/layout/item_text_header.xml +++ b/common/src/main/res/layout/item_text_header.xml @@ -6,7 +6,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingStart="16dp" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" tools:background="@color/secondary_screen_background" tools:text="@tools:sample/lorem[2]"> diff --git a/common/src/main/res/layout/section_title_image_content.xml b/common/src/main/res/layout/section_title_image_content.xml index 0d54be38a4..083980ba8f 100644 --- a/common/src/main/res/layout/section_title_image_content.xml +++ b/common/src/main/res/layout/section_title_image_content.xml @@ -14,7 +14,7 @@ android:layout_height="wrap_content" android:layout_marginStart="16dp" android:layout_marginTop="16dp" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" tools:text="Section Title" /> diff --git a/common/src/main/res/layout/view_account_info.xml b/common/src/main/res/layout/view_account_info.xml index 9210b5ffdc..be6efd8169 100644 --- a/common/src/main/res/layout/view_account_info.xml +++ b/common/src/main/res/layout/view_account_info.xml @@ -47,7 +47,7 @@ android:layout_marginEnd="80dp" android:ellipsize="middle" android:singleLine="true" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" app:layout_constraintBottom_toBottomOf="@+id/accountIcon" app:layout_constraintEnd_toStartOf="@+id/accountAction" app:layout_constraintHorizontal_bias="0.5" diff --git a/common/src/main/res/layout/view_address.xml b/common/src/main/res/layout/view_address.xml index 77e3e46620..3d74a1db32 100644 --- a/common/src/main/res/layout/view_address.xml +++ b/common/src/main/res/layout/view_address.xml @@ -24,7 +24,7 @@ android:gravity="center_vertical" android:includeFontPadding="false" android:singleLine="true" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" tools:text="RTTI-5220" /> \ No newline at end of file diff --git a/common/src/main/res/layout/view_advertisement_card.xml b/common/src/main/res/layout/view_advertisement_card.xml index 27ae89c321..8c89bb56eb 100644 --- a/common/src/main/res/layout/view_advertisement_card.xml +++ b/common/src/main/res/layout/view_advertisement_card.xml @@ -48,7 +48,7 @@ android:layout_marginTop="8dp" android:layout_marginEnd="16dp" android:paddingBottom="4dp" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" app:layout_constrainedWidth="true" app:layout_constraintEnd_toStartOf="@id/advertisementCardImage" app:layout_constraintHorizontal_bias="0" diff --git a/common/src/main/res/layout/view_choose_amount_old.xml b/common/src/main/res/layout/view_choose_amount_old.xml index b42a9e312b..53346773c6 100644 --- a/common/src/main/res/layout/view_choose_amount_old.xml +++ b/common/src/main/res/layout/view_choose_amount_old.xml @@ -16,7 +16,7 @@ android:layout_marginTop="8dp" android:includeFontPadding="false" android:text="@string/common_amount" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> @@ -28,7 +28,7 @@ android:layout_marginTop="8dp" android:layout_marginEnd="16dp" android:layout_marginBottom="8dp" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" app:layout_constraintBottom_toTopOf="@+id/stakingAmountInput" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" @@ -88,7 +88,7 @@ android:layout_marginTop="5dp" android:layout_marginBottom="8dp" android:includeFontPadding="false" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/stakingAssetToken" diff --git a/common/src/main/res/layout/view_go_next.xml b/common/src/main/res/layout/view_go_next.xml index 08f1ef113c..2773d50630 100644 --- a/common/src/main/res/layout/view_go_next.xml +++ b/common/src/main/res/layout/view_go_next.xml @@ -41,7 +41,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="8dp" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" android:visibility="gone" app:layout_constraintBaseline_toBaselineOf="@id/goNextTitle" app:layout_constraintBottom_toBottomOf="@+id/goNextTitle" diff --git a/common/src/main/res/layout/view_instruction_step.xml b/common/src/main/res/layout/view_instruction_step.xml index 1bbbac051c..b7417eb6e8 100644 --- a/common/src/main/res/layout/view_instruction_step.xml +++ b/common/src/main/res/layout/view_instruction_step.xml @@ -19,7 +19,7 @@ android:layout_gravity="center_vertical" android:layout_marginStart="16dp" android:layout_weight="1" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" tools:text="@string/account_parity_signer_import_start_step_1" /> \ No newline at end of file diff --git a/common/src/main/res/layout/view_labeled_text.xml b/common/src/main/res/layout/view_labeled_text.xml index 0c207afd1f..bdf358f2fe 100644 --- a/common/src/main/res/layout/view_labeled_text.xml +++ b/common/src/main/res/layout/view_labeled_text.xml @@ -34,7 +34,7 @@ android:layout_height="wrap_content" android:layout_marginStart="12dp" android:layout_marginTop="8dp" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" app:layout_constraintEnd_toStartOf="@id/labeledTextAction" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toEndOf="@id/labeledTextPrimaryIcon" diff --git a/common/src/main/res/layout/view_placeholder.xml b/common/src/main/res/layout/view_placeholder.xml index d32f0a0592..9a3937bd65 100644 --- a/common/src/main/res/layout/view_placeholder.xml +++ b/common/src/main/res/layout/view_placeholder.xml @@ -20,7 +20,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" tools:text="Your assets will appear here.\nMake sure the 'Hide zero balances' filter is turned off" /> \ No newline at end of file diff --git a/common/src/main/res/layout/view_switch.xml b/common/src/main/res/layout/view_switch.xml index 7bdf5f5bf5..88618055de 100644 --- a/common/src/main/res/layout/view_switch.xml +++ b/common/src/main/res/layout/view_switch.xml @@ -30,7 +30,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginEnd="16dp" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" android:ellipsize="end" app:layout_constrainedWidth="true" app:layout_constraintBottom_toBottomOf="parent" diff --git a/common/src/main/res/layout/view_table_cell.xml b/common/src/main/res/layout/view_table_cell.xml index 65578defd8..c491067567 100644 --- a/common/src/main/res/layout/view_table_cell.xml +++ b/common/src/main/res/layout/view_table_cell.xml @@ -33,7 +33,7 @@ android:includeFontPadding="false" android:maxLines="2" android:ellipsize="end" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" @@ -65,7 +65,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" android:visibility="gone" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="@+id/tableCellValuePrimary" diff --git a/common/src/main/res/values-ru/strings.xml b/common/src/main/res/values-ru/strings.xml index 368c236f60..5dc69c1011 100644 --- a/common/src/main/res/values-ru/strings.xml +++ b/common/src/main/res/values-ru/strings.xml @@ -471,6 +471,14 @@ Неподдерживаемый блокчейн с генезис хэшем %s Убедитесь, что операция правильная Не удалось подписать запрошенную операцию + Сеть не найдена + Домен из ссылки %s не в списке разрешённых + Не указан тип управления + Тип управления не поддерживается + Неверный тип криптографии + Неверный формат пути деривации + Неверный формат мнемоники + Неверный формат ссылки Поиск по адресу или имени Недопустимый формат адреса. Убедитесь, что адрес принадлежит правильной сети результаты поиска: %d @@ -660,6 +668,7 @@ Не найдены референдумы\nс таким названием или ID Поиск по заголовку или ID Референдумы + Референдум не найден За: %s Используйте Nova DApp браузер Только автор референдума может редактировать это описание и заголовок. Если у вас есть доступ к аккаунту автора, посетите Polkassembly и заполните информацию о вашем референдуме @@ -801,6 +810,7 @@ Безопасность Поддержка и обратная связь Twitter + Руководство пользователя Youtube Расширенное управление стейкингом Тип стейкинга не может быть изменен diff --git a/common/src/main/res/values/colors.xml b/common/src/main/res/values/colors.xml index efb6345022..bfaea8e646 100644 --- a/common/src/main/res/values/colors.xml +++ b/common/src/main/res/values/colors.xml @@ -13,8 +13,7 @@ #E0FFFFFF - #A3FFFFFF - #7AFFFFFF + #7AFFFFFF #E0FFFFFF #A3FFFFFF #52FFFFFF @@ -33,6 +32,9 @@ #B6F234 #DAC71B #F19B27 + #A3FFFFFF + #A3FFFFFF + #52FFFFFF #BD387F #FF7A00 diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index d99c54e6e7..a5d6d8b1f6 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -1,6 +1,19 @@ + Chain is not found + Governance type is not specified + Governance type is not supported + Invalid url + Domain from the link %s is not allowed + Mnemonic is not valid + Crypto type is invalid + Invalid derivation path + + Referendum not found + + Wiki + You don\'t have enough %s to pay fee and remain above minimal balance Swap diff --git a/common/src/main/res/values/styles.xml b/common/src/main/res/values/styles.xml index 30cf122a27..d4166c5ac1 100644 --- a/common/src/main/res/values/styles.xml +++ b/common/src/main/res/values/styles.xml @@ -124,7 +124,7 @@ \ No newline at end of file diff --git a/feature-governance-api/src/main/java/io/novafoundation/nova/feature_governance_api/data/MutableGovernanceState.kt b/feature-governance-api/src/main/java/io/novafoundation/nova/feature_governance_api/data/MutableGovernanceState.kt new file mode 100644 index 0000000000..269b31273b --- /dev/null +++ b/feature-governance-api/src/main/java/io/novafoundation/nova/feature_governance_api/data/MutableGovernanceState.kt @@ -0,0 +1,7 @@ +package io.novafoundation.nova.feature_governance_api.data + +import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain + +interface MutableGovernanceState { + fun update(chainId: String, assetId: Int, governanceType: Chain.Governance) +} diff --git a/feature-governance-api/src/main/java/io/novafoundation/nova/feature_governance_api/data/repository/OnChainReferendaRepository.kt b/feature-governance-api/src/main/java/io/novafoundation/nova/feature_governance_api/data/repository/OnChainReferendaRepository.kt index b968d08051..85f5818aa5 100644 --- a/feature-governance-api/src/main/java/io/novafoundation/nova/feature_governance_api/data/repository/OnChainReferendaRepository.kt +++ b/feature-governance-api/src/main/java/io/novafoundation/nova/feature_governance_api/data/repository/OnChainReferendaRepository.kt @@ -24,7 +24,7 @@ interface OnChainReferendaRepository { suspend fun getOnChainReferenda(chainId: ChainId, referendaIds: Collection): Map - suspend fun onChainReferendumFlow(chainId: ChainId, referendumId: ReferendumId): Flow + suspend fun onChainReferendumFlow(chainId: ChainId, referendumId: ReferendumId): Flow suspend fun getReferendaExecutionBlocks(chainId: ChainId, approvedReferendaIds: Collection): Map } diff --git a/feature-governance-api/src/main/java/io/novafoundation/nova/feature_governance_api/di/GovernanceFeatureApi.kt b/feature-governance-api/src/main/java/io/novafoundation/nova/feature_governance_api/di/GovernanceFeatureApi.kt index d116521a57..ff7b329285 100644 --- a/feature-governance-api/src/main/java/io/novafoundation/nova/feature_governance_api/di/GovernanceFeatureApi.kt +++ b/feature-governance-api/src/main/java/io/novafoundation/nova/feature_governance_api/di/GovernanceFeatureApi.kt @@ -1,6 +1,7 @@ package io.novafoundation.nova.feature_governance_api.di import io.novafoundation.nova.core.updater.UpdateSystem +import io.novafoundation.nova.feature_governance_api.data.MutableGovernanceState import io.novafoundation.nova.feature_governance_api.data.source.GovernanceSourceRegistry import io.novafoundation.nova.feature_governance_api.domain.delegation.delegate.delegators.DelegateDelegatorsInteractor import io.novafoundation.nova.feature_governance_api.domain.delegation.delegate.details.model.DelegateDetailsInteractor @@ -29,4 +30,6 @@ interface GovernanceFeatureApi { val newDelegationChooseTrackInteractor: ChooseTrackInteractor val delegateDelegatorsInteractor: DelegateDelegatorsInteractor + + val mutableGovernanceState: MutableGovernanceState } diff --git a/feature-governance-api/src/main/java/io/novafoundation/nova/feature_governance_api/domain/referendum/details/ReferendumCall.kt b/feature-governance-api/src/main/java/io/novafoundation/nova/feature_governance_api/domain/referendum/details/ReferendumCall.kt index de2d463e84..f0b2b5d5b4 100644 --- a/feature-governance-api/src/main/java/io/novafoundation/nova/feature_governance_api/domain/referendum/details/ReferendumCall.kt +++ b/feature-governance-api/src/main/java/io/novafoundation/nova/feature_governance_api/domain/referendum/details/ReferendumCall.kt @@ -5,5 +5,16 @@ import jp.co.soramitsu.fearless_utils.runtime.AccountId sealed class ReferendumCall { - data class TreasuryRequest(val amount: Balance, val beneficiary: AccountId) : ReferendumCall() + open fun combineWith(other: ReferendumCall): ReferendumCall = this + + data class TreasuryRequest(val amount: Balance, val beneficiary: AccountId) : ReferendumCall() { + + override fun combineWith(other: ReferendumCall): ReferendumCall { + if (other is TreasuryRequest) { + return copy(amount = amount + other.amount) + } + + return super.combineWith(other) + } + } } diff --git a/feature-governance-api/src/main/java/io/novafoundation/nova/feature_governance_api/domain/referendum/details/ReferendumDetailsInteractor.kt b/feature-governance-api/src/main/java/io/novafoundation/nova/feature_governance_api/domain/referendum/details/ReferendumDetailsInteractor.kt index 41276d8857..5bb026e527 100644 --- a/feature-governance-api/src/main/java/io/novafoundation/nova/feature_governance_api/domain/referendum/details/ReferendumDetailsInteractor.kt +++ b/feature-governance-api/src/main/java/io/novafoundation/nova/feature_governance_api/domain/referendum/details/ReferendumDetailsInteractor.kt @@ -13,7 +13,7 @@ interface ReferendumDetailsInteractor { referendumId: ReferendumId, selectedGovernanceOption: SupportedGovernanceOption, voterAccountId: AccountId?, - ): Flow + ): Flow suspend fun detailsFor( preImage: PreImage, diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/GovernanceSharedState.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/GovernanceSharedState.kt index 36b073a1f5..2ac1e296d8 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/GovernanceSharedState.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/GovernanceSharedState.kt @@ -2,11 +2,13 @@ package io.novafoundation.nova.feature_governance_impl.data import io.novafoundation.nova.common.data.storage.Preferences import io.novafoundation.nova.common.resources.ResourceManager +import io.novafoundation.nova.feature_governance_api.data.MutableGovernanceState import io.novafoundation.nova.feature_governance_api.data.source.GovernanceAdditionalState import io.novafoundation.nova.feature_governance_impl.R import io.novafoundation.nova.runtime.ext.isUtilityAsset import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain +import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId import io.novafoundation.nova.runtime.state.SelectableSingleAssetSharedState private const val GOVERNANCE_SHARED_STATE = "GOVERNANCE_SHARED_STATE" @@ -26,7 +28,13 @@ class GovernanceSharedState( } }, preferencesKey = GOVERNANCE_SHARED_STATE -) +), + MutableGovernanceState { + + override fun update(chainId: ChainId, assetId: Int, governanceType: Chain.Governance) { + update(chainId, assetId, governanceType.name) + } +} class RealGovernanceAdditionalState( override val governanceType: Chain.Governance, diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/repository/v1/GovV1OnChainReferendaRepository.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/repository/v1/GovV1OnChainReferendaRepository.kt index 4bd69d99d4..1b048fe134 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/repository/v1/GovV1OnChainReferendaRepository.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/repository/v1/GovV1OnChainReferendaRepository.kt @@ -111,13 +111,13 @@ class GovV1OnChainReferendaRepository( }.filterNotNull() } - override suspend fun onChainReferendumFlow(chainId: ChainId, referendumId: ReferendumId): Flow { + override suspend fun onChainReferendumFlow(chainId: ChainId, referendumId: ReferendumId): Flow { return remoteStorageSource.subscribe(chainId) { val votingPeriod = runtime.votingPeriod() runtime.metadata.democracy().storage("ReferendumInfoOf").observe( referendumId.value, - binding = { bindReferendum(it, referendumId, votingPeriod, runtime)!! } + binding = { bindReferendum(it, referendumId, votingPeriod, runtime) } ) } } @@ -185,6 +185,7 @@ class GovV1OnChainReferendaRepository( threshold = bindThreshold(status["threshold"]) ) } + "Finished" -> { val status = asDictEnum.value.castToStruct() val approved = bindBoolean(status["approved"]) @@ -196,6 +197,7 @@ class GovV1OnChainReferendaRepository( OnChainReferendumStatus.Rejected(end) } } + else -> throw IllegalArgumentException("Unsupported referendum status") } 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 b250ba044b..8c14eac156 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 @@ -123,13 +123,13 @@ class GovV2OnChainReferendaRepository( }.filterNotNull() } - override suspend fun onChainReferendumFlow(chainId: ChainId, referendumId: ReferendumId): Flow { + override suspend fun onChainReferendumFlow(chainId: ChainId, referendumId: ReferendumId): Flow { return remoteStorageSource.subscribe(chainId) { val allTracks = getTracksById(chainId) runtime.metadata.referenda().storage("ReferendumInfoFor").observe( referendumId.value, - binding = { bindReferendum(it, referendumId, allTracks, runtime)!! } + binding = { bindReferendum(it, referendumId, allTracks, runtime) } ) } } @@ -182,6 +182,7 @@ class GovV2OnChainReferendaRepository( threshold = Gov2VotingThreshold(tracksById.getValue(trackId)) ) } + "Approved" -> OnChainReferendumStatus.Approved(bindCompletedReferendumSince(asDictEnum.value)) "Rejected" -> OnChainReferendumStatus.Rejected(bindCompletedReferendumSince(asDictEnum.value)) "Cancelled" -> OnChainReferendumStatus.Cancelled(bindCompletedReferendumSince(asDictEnum.value)) @@ -256,6 +257,7 @@ class GovV2OnChainReferendaRepository( yOffset = bindFixedI64(valueStruct["y_offset"]) ) } + "LinearDecreasing" -> { LinearDecreasingCurve( length = bindPerbill(valueStruct["length"]), @@ -263,6 +265,7 @@ class GovV2OnChainReferendaRepository( ceil = bindPerbill(valueStruct["ceil"]) ) } + "SteppedDecreasing" -> { SteppedDecreasingCurve( begin = bindPerbill(valueStruct["begin"]), @@ -271,6 +274,7 @@ class GovV2OnChainReferendaRepository( period = bindPerbill(valueStruct["period"]) ) } + else -> incompatible() } } diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/di/GovernanceFeatureModule.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/di/GovernanceFeatureModule.kt index 0aa0d5857f..d098259142 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/di/GovernanceFeatureModule.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/di/GovernanceFeatureModule.kt @@ -11,6 +11,7 @@ import io.novafoundation.nova.feature_account_api.data.repository.OnChainIdentit import io.novafoundation.nova.feature_account_api.domain.account.identity.IdentityProvider import io.novafoundation.nova.feature_account_api.domain.account.identity.LocalIdentity import io.novafoundation.nova.feature_account_api.domain.account.identity.OnChainIdentity +import io.novafoundation.nova.feature_governance_api.data.MutableGovernanceState import io.novafoundation.nova.feature_governance_api.data.repository.TreasuryRepository import io.novafoundation.nova.feature_governance_api.data.source.GovernanceSource import io.novafoundation.nova.feature_governance_api.data.source.GovernanceSourceRegistry @@ -96,6 +97,12 @@ class GovernanceFeatureModule { preferences: Preferences, ) = GovernanceSharedState(chainRegistry, preferences) + @Provides + @FeatureScope + fun provideGovernanceStateUpdater( + governanceSharedState: GovernanceSharedState + ): MutableGovernanceState = governanceSharedState + @Provides @FeatureScope fun provideSelectableSharedState(governanceSharedState: GovernanceSharedState): SelectableSingleAssetSharedState<*> = governanceSharedState diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/di/modules/screens/ReferendumDetailsModule.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/di/modules/screens/ReferendumDetailsModule.kt index 8faf4d8ec0..7403f77f5c 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/di/modules/screens/ReferendumDetailsModule.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/di/modules/screens/ReferendumDetailsModule.kt @@ -12,9 +12,12 @@ import io.novafoundation.nova.feature_governance_api.domain.referendum.details.R import io.novafoundation.nova.feature_governance_impl.data.preimage.PreImageSizer import io.novafoundation.nova.feature_governance_impl.domain.referendum.common.ReferendaConstructor import io.novafoundation.nova.feature_governance_impl.domain.referendum.details.RealReferendumDetailsInteractor -import io.novafoundation.nova.feature_governance_impl.domain.referendum.details.call.ReferendumCallParser -import io.novafoundation.nova.feature_governance_impl.domain.referendum.details.call.treasury.TreasuryApproveProposalParser -import io.novafoundation.nova.feature_governance_impl.domain.referendum.details.call.treasury.TreasurySpendParser +import io.novafoundation.nova.feature_governance_impl.domain.referendum.details.call.RealReferendumPreImageParser +import io.novafoundation.nova.feature_governance_impl.domain.referendum.details.call.ReferendumCallAdapter +import io.novafoundation.nova.feature_governance_impl.domain.referendum.details.call.ReferendumPreImageParser +import io.novafoundation.nova.feature_governance_impl.domain.referendum.details.call.batch.BatchAdapter +import io.novafoundation.nova.feature_governance_impl.domain.referendum.details.call.treasury.TreasuryApproveProposalAdapter +import io.novafoundation.nova.feature_governance_impl.domain.referendum.details.call.treasury.TreasurySpendAdapter import io.novafoundation.nova.runtime.di.ExtrinsicSerialization import io.novafoundation.nova.runtime.repository.ChainStateRepository @@ -26,17 +29,30 @@ class ReferendumDetailsModule { @IntoSet fun provideTreasuryApproveParser( treasuryRepository: TreasuryRepository - ): ReferendumCallParser = TreasuryApproveProposalParser(treasuryRepository) + ): ReferendumCallAdapter = TreasuryApproveProposalAdapter(treasuryRepository) @Provides @FeatureScope @IntoSet - fun provideTreasurySpendParser(): ReferendumCallParser = TreasurySpendParser() + fun provideTreasurySpendParser(): ReferendumCallAdapter = TreasurySpendAdapter() + + @Provides + @FeatureScope + @IntoSet + fun provideBatchAdapter(): ReferendumCallAdapter = BatchAdapter() + + @Provides + @FeatureScope + fun providePreImageParser( + callAdapters: Set<@JvmSuppressWildcards ReferendumCallAdapter> + ): ReferendumPreImageParser { + return RealReferendumPreImageParser(callAdapters) + } @Provides @FeatureScope fun provideReferendumDetailsInteractor( - callParsers: Set<@JvmSuppressWildcards ReferendumCallParser>, + preImageParser: ReferendumPreImageParser, governanceSourceRegistry: GovernanceSourceRegistry, chainStateRepository: ChainStateRepository, referendaConstructor: ReferendaConstructor, @@ -44,7 +60,7 @@ class ReferendumDetailsModule { @ExtrinsicSerialization callFormatter: Gson, identityRepository: OnChainIdentityRepository, ): ReferendumDetailsInteractor = RealReferendumDetailsInteractor( - preImageParsers = callParsers, + preImageParser = preImageParser, governanceSourceRegistry = governanceSourceRegistry, chainStateRepository = chainStateRepository, referendaConstructor = referendaConstructor, diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/delegation/delegation/create/chooseAmount/RealDelegateAssistant.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/delegation/delegation/create/chooseAmount/RealDelegateAssistant.kt index bac3e0984c..01fc4b8859 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/delegation/delegation/create/chooseAmount/RealDelegateAssistant.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/delegation/delegation/create/chooseAmount/RealDelegateAssistant.kt @@ -13,6 +13,7 @@ import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Ba import io.novafoundation.nova.feature_wallet_api.domain.model.Asset import io.novafoundation.nova.feature_wallet_api.domain.model.BalanceLock import io.novafoundation.nova.feature_wallet_api.domain.model.maxLockReplacing +import io.novafoundation.nova.feature_wallet_api.domain.model.transferableReplacingFrozen import io.novafoundation.nova.runtime.multiNetwork.runtime.types.custom.vote.Conviction import io.novafoundation.nova.runtime.util.BlockDurationEstimator @@ -35,7 +36,7 @@ class RealDelegateAssistant( val currentTransferablePlanks = asset.transferableInPlanks val newLocked = otherMaxLocked.max(newGovernanceLocked) - val newTransferablePlanks = asset.freeInPlanks - newLocked + val newTransferablePlanks = asset.transferableReplacingFrozen(newLocked) return LocksChange( lockedAmountChange = Change( diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/details/RealReferendumDetailsInteractor.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/details/RealReferendumDetailsInteractor.kt index 08e6b927e7..5c6716f744 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/details/RealReferendumDetailsInteractor.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/details/RealReferendumDetailsInteractor.kt @@ -39,19 +39,18 @@ import io.novafoundation.nova.feature_governance_api.domain.referendum.list.Refe import io.novafoundation.nova.feature_governance_impl.data.preimage.PreImageSizer import io.novafoundation.nova.feature_governance_impl.domain.referendum.common.ReferendaConstructor import io.novafoundation.nova.feature_governance_impl.domain.referendum.common.constructReferendumStatus -import io.novafoundation.nova.feature_governance_impl.domain.referendum.details.call.ReferendumCallParser +import io.novafoundation.nova.feature_governance_impl.domain.referendum.details.call.ReferendumPreImageParser import io.novafoundation.nova.feature_governance_impl.domain.track.mapTrackInfoToTrack import io.novafoundation.nova.runtime.ext.accountIdOrNull import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId import io.novafoundation.nova.runtime.repository.ChainStateRepository -import jp.co.soramitsu.fearless_utils.extensions.tryFindNonNull import jp.co.soramitsu.fearless_utils.runtime.AccountId import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine class RealReferendumDetailsInteractor( - private val preImageParsers: Collection, + private val preImageParser: ReferendumPreImageParser, private val governanceSourceRegistry: GovernanceSourceRegistry, private val chainStateRepository: ChainStateRepository, private val referendaConstructor: ReferendaConstructor, @@ -64,14 +63,12 @@ class RealReferendumDetailsInteractor( referendumId: ReferendumId, selectedGovernanceOption: SupportedGovernanceOption, voterAccountId: AccountId?, - ): Flow { + ): Flow { return flowOfAll { referendumDetailsFlowSuspend(referendumId, selectedGovernanceOption, voterAccountId) } } override suspend fun detailsFor(preImage: PreImage, chain: Chain): ReferendumCall? { - return preImageParsers.tryFindNonNull { parser -> - parser.parse(preImage, chain.id) - } + return preImageParser.parse(preImage, chain.id) } override suspend fun previewFor(preImage: PreImage): PreimagePreview { @@ -93,7 +90,7 @@ class RealReferendumDetailsInteractor( referendumId: ReferendumId, selectedGovernanceOption: SupportedGovernanceOption, voterAccountId: AccountId?, - ): Flow { + ): Flow { val chain = selectedGovernanceOption.assetWithChain.chain val governanceSource = governanceSourceRegistry.sourceFor(selectedGovernanceOption) @@ -106,6 +103,7 @@ class RealReferendumDetailsInteractor( governanceSource.referenda.onChainReferendumFlow(chain.id, referendumId), chainStateRepository.currentBlockNumberFlow(chain.id) ) { onChainReferendum, currentBlockNumber -> + if (onChainReferendum == null) return@combine null val preImage = governanceSource.preImageRepository.preImageOf(onChainReferendum.proposal(), chain.id) val track = onChainReferendum.track()?.let(tracksById::get) @@ -251,14 +249,17 @@ private suspend fun PreImageRepository.preImageOf( is Proposal.Inline -> { PreImage(encodedCall = proposal.encodedCall, call = proposal.call) } + is Proposal.Legacy -> { val request = PreImageRequest(proposal.hash, knownSize = null, fetchIf = ALWAYS) getPreimageFor(request, chainId) } + is Proposal.Lookup -> { val request = PreImageRequest(proposal.hash, knownSize = proposal.callLength, fetchIf = ALWAYS) getPreimageFor(request, chainId) } + null -> null } } diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/details/call/ReferendumCallParser.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/details/call/ReferendumCallParser.kt deleted file mode 100644 index 1c8e34f1e3..0000000000 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/details/call/ReferendumCallParser.kt +++ /dev/null @@ -1,10 +0,0 @@ -package io.novafoundation.nova.feature_governance_impl.domain.referendum.details.call - -import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.PreImage -import io.novafoundation.nova.feature_governance_api.domain.referendum.details.ReferendumCall -import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId - -interface ReferendumCallParser { - - suspend fun parse(preImage: PreImage, chainId: ChainId): ReferendumCall? -} diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/details/call/ReferendumPreImageParser.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/details/call/ReferendumPreImageParser.kt new file mode 100644 index 0000000000..ee9663e03a --- /dev/null +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/details/call/ReferendumPreImageParser.kt @@ -0,0 +1,55 @@ +package io.novafoundation.nova.feature_governance_impl.domain.referendum.details.call + +import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.PreImage +import io.novafoundation.nova.feature_governance_api.domain.referendum.details.ReferendumCall +import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId +import jp.co.soramitsu.fearless_utils.extensions.tryFindNonNull +import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.GenericCall +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +interface ReferendumPreImageParser { + + suspend fun parse(preImage: PreImage, chainId: ChainId): ReferendumCall? +} + +interface ReferendumCallAdapter { + + suspend fun fromCall(call: GenericCall.Instance, context: ReferendumCallParseContext): ReferendumCall? +} + +interface ReferendumCallParseContext { + + val chainId: ChainId + + /** + * Can be used to resolve nested calls in compound calls like batch or proxy calls + * Do not pass the same call you're processing otherwise you'll get a stack overflow + */ + suspend fun parse(call: GenericCall.Instance): ReferendumCall? +} + +class RealReferendumPreImageParser( + private val knownAdapters: Collection, +) : ReferendumPreImageParser { + + override suspend fun parse(preImage: PreImage, chainId: ChainId): ReferendumCall? { + val context = RealReferendumCallParseContext(chainId, knownAdapters) + + return withContext(Dispatchers.IO) { + context.parse(preImage.call) + } + } + + private inner class RealReferendumCallParseContext( + override val chainId: ChainId, + private val knownAdapters: Collection, + ) : ReferendumCallParseContext { + + override suspend fun parse(call: GenericCall.Instance): ReferendumCall? { + return knownAdapters.tryFindNonNull { adapter -> + runCatching { adapter.fromCall(call, context = this) }.getOrNull() + } + } + } +} diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/details/call/batch/BatchAdapter.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/details/call/batch/BatchAdapter.kt new file mode 100644 index 0000000000..06d8b7b029 --- /dev/null +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/details/call/batch/BatchAdapter.kt @@ -0,0 +1,21 @@ +package io.novafoundation.nova.feature_governance_impl.domain.referendum.details.call.batch + +import io.novafoundation.nova.common.data.network.runtime.binding.cast +import io.novafoundation.nova.common.utils.Modules +import io.novafoundation.nova.common.utils.instanceOf +import io.novafoundation.nova.feature_governance_api.domain.referendum.details.ReferendumCall +import io.novafoundation.nova.feature_governance_impl.domain.referendum.details.call.ReferendumCallAdapter +import io.novafoundation.nova.feature_governance_impl.domain.referendum.details.call.ReferendumCallParseContext +import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.GenericCall + +class BatchAdapter : ReferendumCallAdapter { + + override suspend fun fromCall(call: GenericCall.Instance, context: ReferendumCallParseContext): ReferendumCall? { + if (!call.instanceOf(Modules.UTILITY, "batch", "batch_all", "force_batch")) return null + + val innerCalls = call.arguments["calls"].cast>() + + return innerCalls.mapNotNull { context.parse(it) } + .reduceOrNull { acc, referendumCall -> acc.combineWith(referendumCall) } + } +} diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/details/call/treasury/TreasuryApproveProposalParser.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/details/call/treasury/TreasuryApproveProposalAdapter.kt similarity index 55% rename from feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/details/call/treasury/TreasuryApproveProposalParser.kt rename to feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/details/call/treasury/TreasuryApproveProposalAdapter.kt index 449afbffce..569b8a263e 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/details/call/treasury/TreasuryApproveProposalParser.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/details/call/treasury/TreasuryApproveProposalAdapter.kt @@ -3,26 +3,24 @@ package io.novafoundation.nova.feature_governance_impl.domain.referendum.details import io.novafoundation.nova.common.data.network.runtime.binding.bindNumber import io.novafoundation.nova.common.utils.Modules import io.novafoundation.nova.common.utils.instanceOf -import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.PreImage import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.TreasuryProposal import io.novafoundation.nova.feature_governance_api.data.repository.TreasuryRepository import io.novafoundation.nova.feature_governance_api.domain.referendum.details.ReferendumCall -import io.novafoundation.nova.feature_governance_impl.domain.referendum.details.call.ReferendumCallParser -import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId +import io.novafoundation.nova.feature_governance_impl.domain.referendum.details.call.ReferendumCallAdapter +import io.novafoundation.nova.feature_governance_impl.domain.referendum.details.call.ReferendumCallParseContext +import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.GenericCall -class TreasuryApproveProposalParser( +class TreasuryApproveProposalAdapter( private val treasuryRepository: TreasuryRepository -) : ReferendumCallParser { - - override suspend fun parse(preImage: PreImage, chainId: ChainId): ReferendumCall? = runCatching { - val call = preImage.call +) : ReferendumCallAdapter { + override suspend fun fromCall(call: GenericCall.Instance, context: ReferendumCallParseContext): ReferendumCall? { if (!call.instanceOf(Modules.TREASURY, "approve_proposal")) return null val id = bindNumber(call.arguments["proposal_id"]) - val proposal = treasuryRepository.getTreasuryProposal(chainId, TreasuryProposal.Id(id)) ?: return null + val proposal = treasuryRepository.getTreasuryProposal(context.chainId, TreasuryProposal.Id(id)) ?: return null - ReferendumCall.TreasuryRequest(amount = proposal.amount, beneficiary = proposal.beneficiary) - }.getOrNull() + return ReferendumCall.TreasuryRequest(amount = proposal.amount, beneficiary = proposal.beneficiary) + } } diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/details/call/treasury/TreasurySpendParser.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/details/call/treasury/TreasurySpendAdapter.kt similarity index 59% rename from feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/details/call/treasury/TreasurySpendParser.kt rename to feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/details/call/treasury/TreasurySpendAdapter.kt index 0310fb0bb6..9dfd11ba9e 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/details/call/treasury/TreasurySpendParser.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/details/call/treasury/TreasurySpendAdapter.kt @@ -4,21 +4,22 @@ import io.novafoundation.nova.common.data.network.runtime.binding.bindAccountIde import io.novafoundation.nova.common.data.network.runtime.binding.bindNonce import io.novafoundation.nova.common.utils.Modules import io.novafoundation.nova.common.utils.instanceOf -import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.PreImage import io.novafoundation.nova.feature_governance_api.domain.referendum.details.ReferendumCall -import io.novafoundation.nova.feature_governance_impl.domain.referendum.details.call.ReferendumCallParser -import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId +import io.novafoundation.nova.feature_governance_impl.domain.referendum.details.call.ReferendumCallAdapter +import io.novafoundation.nova.feature_governance_impl.domain.referendum.details.call.ReferendumCallParseContext +import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.GenericCall -class TreasurySpendParser : ReferendumCallParser { - - override suspend fun parse(preImage: PreImage, chainId: ChainId): ReferendumCall? = runCatching { - val call = preImage.call +class TreasurySpendAdapter : ReferendumCallAdapter { + override suspend fun fromCall( + call: GenericCall.Instance, + context: ReferendumCallParseContext + ): ReferendumCall? { if (!call.instanceOf(Modules.TREASURY, "spend")) return null val amount = bindNonce(call.arguments["amount"]) val beneficiary = bindAccountIdentifier(call.arguments["beneficiary"]) - ReferendumCall.TreasuryRequest(amount, beneficiary) - }.getOrNull() + return ReferendumCall.TreasuryRequest(amount, beneficiary) + } } diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/unlock/GovernanceUnlockInteractor.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/unlock/GovernanceUnlockInteractor.kt index 26ff292da9..dca9f662de 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/unlock/GovernanceUnlockInteractor.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/unlock/GovernanceUnlockInteractor.kt @@ -29,6 +29,7 @@ import io.novafoundation.nova.feature_wallet_api.data.repository.BalanceLocksRep import io.novafoundation.nova.feature_wallet_api.domain.model.Asset import io.novafoundation.nova.feature_wallet_api.domain.model.BalanceLock import io.novafoundation.nova.feature_wallet_api.domain.model.maxLockReplacing +import io.novafoundation.nova.feature_wallet_api.domain.model.transferableReplacingFrozen import io.novafoundation.nova.runtime.ext.fullId import io.novafoundation.nova.runtime.extrinsic.ExtrinsicStatus import io.novafoundation.nova.runtime.repository.ChainStateRepository @@ -209,7 +210,7 @@ class RealGovernanceUnlockInteractor( val transferableCurrent = asset.transferableInPlanks val newTotalLocked = balanceLocks.maxLockReplacing(convictionVoting.voteLockId, replaceWith = newGovernanceLock) - val newTransferable = asset.freeInPlanks - newTotalLocked + val newTransferable = asset.transferableReplacingFrozen(newTotalLocked) val governanceLockChange = claimable.amount val transferableChange = (newTransferable - transferableCurrent).abs() diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/vote/RealVoteReferendumInteractor.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/vote/RealVoteReferendumInteractor.kt index a2dd4ab542..30ce631b43 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/vote/RealVoteReferendumInteractor.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/vote/RealVoteReferendumInteractor.kt @@ -31,6 +31,7 @@ import io.novafoundation.nova.feature_wallet_api.data.repository.BalanceLocksRep import io.novafoundation.nova.feature_wallet_api.domain.model.Asset import io.novafoundation.nova.feature_wallet_api.domain.model.BalanceLock import io.novafoundation.nova.feature_wallet_api.domain.model.maxLockReplacing +import io.novafoundation.nova.feature_wallet_api.domain.model.transferableReplacingFrozen import io.novafoundation.nova.runtime.ext.fullId import io.novafoundation.nova.runtime.multiNetwork.runtime.types.custom.vote.Conviction import io.novafoundation.nova.runtime.repository.ChainStateRepository @@ -41,6 +42,7 @@ import jp.co.soramitsu.fearless_utils.runtime.AccountId import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.map private const val VOTE_ASSISTANT_CACHE_KEY = "RealVoteReferendumInteractor.VoteAssistant" @@ -116,6 +118,8 @@ class RealVoteReferendumInteractor( } val selectedReferendumFlow = governanceSource.referenda.onChainReferendumFlow(chain.id, referendumId) + .filterNotNull() + val balanceLocksFlow = locksRepository.observeBalanceLocks(chain, chainAsset) return combine(votingInformation, selectedReferendumFlow, balanceLocksFlow) { (locksByTrack, voting, votedReferenda), selectedReferendum, locks -> @@ -188,7 +192,7 @@ private class RealGovernanceLocksEstimator( val currentTransferablePlanks = asset.transferableInPlanks val newLocked = otherMaxLocked.max(newGovernanceLocked) - val newTransferablePlanks = asset.freeInPlanks - newLocked + val newTransferablePlanks = asset.transferableReplacingFrozen(newLocked) return LocksChange( lockedAmountChange = Change( diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/common/ReferendumFormatter.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/common/ReferendumFormatter.kt index 03ba9f724a..15672c1159 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/common/ReferendumFormatter.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/common/ReferendumFormatter.kt @@ -144,7 +144,7 @@ class RealReferendumFormatter( ReferendumStatusModel( name = resourceManager.getString(titleRes), - colorRes = R.color.text_tertiary + colorRes = R.color.text_secondary ) } is ReferendumStatus.Ongoing.InQueue -> ReferendumStatusModel( @@ -153,11 +153,11 @@ class RealReferendumFormatter( status.position.index, status.position.maxSize ), - colorRes = R.color.text_tertiary + colorRes = R.color.text_secondary ) is ReferendumStatus.Ongoing.Deciding -> ReferendumStatusModel( name = resourceManager.getString(R.string.referendum_status_deciding), - colorRes = R.color.text_tertiary + colorRes = R.color.text_secondary ) is ReferendumStatus.Ongoing.Confirming -> ReferendumStatusModel( name = resourceManager.getString(R.string.referendum_status_passing), @@ -177,11 +177,11 @@ class RealReferendumFormatter( ) ReferendumStatus.NotExecuted.Cancelled -> ReferendumStatusModel( name = resourceManager.getString(R.string.referendum_status_cancelled), - colorRes = R.color.text_tertiary + colorRes = R.color.text_secondary ) ReferendumStatus.NotExecuted.TimedOut -> ReferendumStatusModel( name = resourceManager.getString(R.string.referendum_status_timeout), - colorRes = R.color.text_tertiary + colorRes = R.color.text_secondary ) ReferendumStatus.NotExecuted.Killed -> ReferendumStatusModel( name = resourceManager.getString(R.string.referendum_status_killed), @@ -348,7 +348,7 @@ class RealReferendumFormatter( private fun ReferendumTimeEstimation.TextStyle.Companion.regular() = ReferendumTimeEstimation.TextStyle( iconRes = R.drawable.ic_time_16, - textColorRes = R.color.text_tertiary, + textColorRes = R.color.text_secondary, iconColorRes = R.color.icon_secondary, ) @@ -409,7 +409,7 @@ class RealReferendumFormatter( return when (voteDirection) { VoteType.AYE -> VoteDirectionModel(resourceManager.getString(R.string.referendum_vote_aye), R.color.text_positive) VoteType.NAY -> VoteDirectionModel(resourceManager.getString(R.string.referendum_vote_nay), R.color.text_negative) - VoteType.ABSTAIN -> VoteDirectionModel(resourceManager.getString(R.string.referendum_vote_abstain), R.color.text_tertiary) + VoteType.ABSTAIN -> VoteDirectionModel(resourceManager.getString(R.string.referendum_vote_abstain), R.color.text_secondary) } } } diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/details/ReferendumDetailsFragment.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/details/ReferendumDetailsFragment.kt index f7cce39268..5f35532fa5 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/details/ReferendumDetailsFragment.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/details/ReferendumDetailsFragment.kt @@ -7,6 +7,7 @@ import android.view.View import android.view.ViewGroup import io.novafoundation.nova.common.base.BaseFragment import io.novafoundation.nova.common.di.FeatureUtils +import io.novafoundation.nova.common.mixin.actionAwaitable.setupConfirmationDialog import io.novafoundation.nova.common.mixin.impl.observeValidations import io.novafoundation.nova.common.presentation.LoadingState import io.novafoundation.nova.common.utils.WithContextExtensions @@ -124,12 +125,14 @@ class ReferendumDetailsFragment : BaseFragment(), Wi override fun subscribe(viewModel: ReferendumDetailsViewModel) { setupExternalActions(viewModel) observeValidations(viewModel) + setupConfirmationDialog(R.style.AccentNegativeAlertDialogTheme_Reversed, viewModel.referendumNotAwaitableAction) viewModel.referendumDetailsModelFlow.observeWhenVisible { when (it) { is LoadingState.Loading -> { setContentVisible(false) } + is LoadingState.Loaded -> { setContentVisible(true) @@ -175,6 +178,7 @@ class ReferendumDetailsFragment : BaseFragment(), Wi referendumDetailsRequestedAmount.text = model.amount.token referendumDetailsRequestedAmountFiat.text = model.amount.fiat } + null -> { referendumDetailsRequestedAmountContainer.makeGone() } diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/details/ReferendumDetailsViewModel.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/details/ReferendumDetailsViewModel.kt index 819f060e51..ffcaa3e641 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/details/ReferendumDetailsViewModel.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/details/ReferendumDetailsViewModel.kt @@ -3,6 +3,9 @@ package io.novafoundation.nova.feature_governance_impl.presentation.referenda.de import io.noties.markwon.Markwon import io.novafoundation.nova.common.address.AddressIconGenerator import io.novafoundation.nova.common.base.BaseViewModel +import io.novafoundation.nova.common.mixin.actionAwaitable.ActionAwaitableMixin +import io.novafoundation.nova.common.mixin.actionAwaitable.ConfirmationDialogInfo +import io.novafoundation.nova.common.mixin.actionAwaitable.confirmingAction import io.novafoundation.nova.common.mixin.api.Validatable import io.novafoundation.nova.common.presentation.DescriptiveButtonState import io.novafoundation.nova.common.resources.ResourceManager @@ -16,6 +19,7 @@ import io.novafoundation.nova.common.utils.mapNullable import io.novafoundation.nova.common.utils.withLoading import io.novafoundation.nova.common.validation.TransformedFailure 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_account_api.domain.model.accountIdIn import io.novafoundation.nova.feature_account_api.domain.validation.handleChainAccountNotFound @@ -65,8 +69,11 @@ import io.novafoundation.nova.runtime.state.chainAndAsset import io.novafoundation.nova.runtime.state.selectedChainFlow import io.novafoundation.nova.runtime.state.selectedOption import kotlinx.coroutines.Dispatchers +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 import kotlinx.coroutines.withContext @@ -86,6 +93,8 @@ class ReferendumDetailsViewModel( val markwon: Markwon, private val validationSystem: ReferendumPreVoteValidationSystem, private val validationExecutor: ValidationExecutor, + private val updateSystem: UpdateSystem, + private val actionAwaitableMixinFactory: ActionAwaitableMixin.Factory, ) : BaseViewModel(), ExternalActions by externalActions, Validatable by validationExecutor { @@ -93,16 +102,19 @@ class ReferendumDetailsViewModel( private val selectedAccount = selectedAccountUseCase.selectedMetaAccountFlow() private val selectedChainFlow = selectedAssetSharedState.selectedChainFlow() - private val referendumDetailsFlow = flowOfAll { + private val optionalReferendumDetailsFlow = flowOfAll { val account = selectedAccount.first() val selectedGovernanceOption = selectedAssetSharedState.selectedOption() val voterAccountId = account.accountIdIn(selectedGovernanceOption.assetWithChain.chain) interactor.referendumDetailsFlow(payload.toReferendumId(), selectedGovernanceOption, voterAccountId) - } - .inBackground() + }.inBackground() .shareWhileSubscribed() + private val referendumDetailsFlow = optionalReferendumDetailsFlow + .filterNotNull() + .shareInBackground() + private val proposerFlow = referendumDetailsFlow.map { it.proposer } private val proposerIdentityProvider = governanceIdentityProviderFactory.proposerProvider(proposerFlow) @@ -157,6 +169,20 @@ class ReferendumDetailsViewModel( .mapList(::mapGovernanceDAppToUi) .shareInBackground() + val referendumNotAwaitableAction = actionAwaitableMixinFactory.confirmingAction() + + init { + optionalReferendumDetailsFlow + .onEach { + if (it == null) { + showErrorAndCloseScreen() + } + }.launchIn(this) + + updateSystem.start() + .launchIn(this) + } + fun backClicked() { router.back() } @@ -267,6 +293,7 @@ class ReferendumDetailsViewModel( PreparingReason.WaitingForDeposit -> R.string.referendum_timeline_state_waiting_deposit } } + is ReferendumStatus.Ongoing.Confirming -> R.string.referendum_timeline_state_passing is ReferendumStatus.Ongoing.Deciding -> R.string.referendum_timeline_state_deciding is ReferendumStatus.Ongoing.InQueue -> R.string.referendum_timeline_state_in_queue @@ -311,11 +338,13 @@ class ReferendumDetailsViewModel( voteTypeRes = R.string.referendum_vote_aye, votesValue = formatVotesAmount(voting.approval.ayeVotes.amount, chainAsset) ) + VoteType.NAY -> VotersModel( voteTypeColorRes = R.color.nay_indicator, voteTypeRes = R.string.referendum_vote_nay, votesValue = formatVotesAmount(voting.approval.nayVotes.amount, chainAsset) ) + VoteType.ABSTAIN -> null } } @@ -419,4 +448,13 @@ class ReferendumDetailsViewModel( ) } } + + private suspend fun showErrorAndCloseScreen() { + val confirmationInfo = ConfirmationDialogInfo.titleAndButton( + title = R.string.referendim_details_not_found_title, + button = R.string.common_ok, + ) + referendumNotAwaitableAction.awaitAction(confirmationInfo) + router.back() + } } diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/details/di/ReferendumDetailsModule.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/details/di/ReferendumDetailsModule.kt index 6b473cc778..7fdfecbc81 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/details/di/ReferendumDetailsModule.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/details/di/ReferendumDetailsModule.kt @@ -12,9 +12,11 @@ import io.novafoundation.nova.common.di.modules.shared.MarkdownShortModule 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.validation.ValidationExecutor import io.novafoundation.nova.common.validation.ValidationSystem +import io.novafoundation.nova.core.updater.UpdateSystem import io.novafoundation.nova.feature_account_api.domain.interfaces.SelectedAccountUseCase import io.novafoundation.nova.feature_account_api.presenatation.actions.ExternalActions import io.novafoundation.nova.feature_governance_api.domain.referendum.details.ReferendumDetailsInteractor @@ -55,6 +57,8 @@ class ReferendumDetailsModule { governanceDAppsInteractor: GovernanceDAppsInteractor, validationSystem: ReferendumPreVoteValidationSystem, validationExecutor: ValidationExecutor, + updateSystem: UpdateSystem, + actionAwaitableMixinFactory: ActionAwaitableMixin.Factory ): ViewModel { return ReferendumDetailsViewModel( router = router, @@ -71,7 +75,9 @@ class ReferendumDetailsModule { markwon = markwon, governanceDAppsInteractor = governanceDAppsInteractor, validationExecutor = validationExecutor, - validationSystem = validationSystem + validationSystem = validationSystem, + updateSystem = updateSystem, + actionAwaitableMixinFactory = actionAwaitableMixinFactory ) } diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/unlock/confirm/hints/ConfirmGovernanceUnlockHintsMixin.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/unlock/confirm/hints/ConfirmGovernanceUnlockHintsMixin.kt index dd4e386541..c817b4f9b9 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/unlock/confirm/hints/ConfirmGovernanceUnlockHintsMixin.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/unlock/confirm/hints/ConfirmGovernanceUnlockHintsMixin.kt @@ -35,7 +35,7 @@ private class ConfirmGovernanceUnlockHintsMixin( ) : HintsMixin, WithCoroutineScopeExtensions by WithCoroutineScopeExtensions(scope) { override val hintsFlow: Flow> = remainsLockedInfoFlow.map { remainsLockedInfo -> - if (remainsLockedInfo != null) { + if (remainsLockedInfo != null && remainsLockedInfo.lockedInIds.isNotEmpty()) { listOf(remainsLockedHint(assetFlow.first(), remainsLockedInfo)) } else { emptyList() @@ -57,7 +57,7 @@ private class ConfirmGovernanceUnlockHintsMixin( append(" ") val rest = resourceManager.getString(R.string.referendum_unlock_remains_locked_format, lockedIdsPart) - appendColored(rest, R.color.text_tertiary) + appendColored(rest, R.color.text_secondary) } } } diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/unlock/list/GovernanceLocksOverviewViewModel.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/unlock/list/GovernanceLocksOverviewViewModel.kt index 002e45570f..cd683cb257 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/unlock/list/GovernanceLocksOverviewViewModel.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/unlock/list/GovernanceLocksOverviewViewModel.kt @@ -68,7 +68,7 @@ class GovernanceLocksOverviewViewModel( index = index, amount = mapAmountToAmountModel(lock.amount, token).token, status = StatusContent.Timer(lock.claimTime.timer), - statusColorRes = R.color.text_tertiary, + statusColorRes = R.color.text_secondary, statusIconRes = R.drawable.ic_time_16, statusIconColorRes = R.color.icon_secondary ) @@ -77,7 +77,7 @@ class GovernanceLocksOverviewViewModel( index = index, amount = mapAmountToAmountModel(lock.amount, token).token, status = StatusContent.Text(resourceManager.getString(R.string.delegation_your_delegation)), - statusColorRes = R.color.text_tertiary, + statusColorRes = R.color.text_secondary, statusIconRes = null, statusIconColorRes = null ) diff --git a/feature-governance-impl/src/main/res/layout/bottom_remove_votes_suggestion.xml b/feature-governance-impl/src/main/res/layout/bottom_remove_votes_suggestion.xml index f8f60936d7..d38116a01d 100644 --- a/feature-governance-impl/src/main/res/layout/bottom_remove_votes_suggestion.xml +++ b/feature-governance-impl/src/main/res/layout/bottom_remove_votes_suggestion.xml @@ -28,7 +28,7 @@ android:layout_height="wrap_content" android:layout_marginTop="10dp" tools:text="@string/remove_votes_suggestion_description" - android:textColor="@color/text_tertiary" /> + android:textColor="@color/text_secondary" /> diff --git a/feature-governance-impl/src/main/res/layout/fragment_referendum_details.xml b/feature-governance-impl/src/main/res/layout/fragment_referendum_details.xml index 4772319a33..c5727fbf03 100644 --- a/feature-governance-impl/src/main/res/layout/fragment_referendum_details.xml +++ b/feature-governance-impl/src/main/res/layout/fragment_referendum_details.xml @@ -89,7 +89,7 @@ style="@style/TextAppearance.NovaFoundation.Regular.SubHeadline" android:layout_width="match_parent" android:layout_height="wrap_content" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" tools:text="Sovereign Nature Initiative (SNI) is a non-profit foundation that has brought together multiple partners and engineers from the Kusama ecosystem including Kodadot, Unique Network, Kilt Protocol, Momentum, and Ocean Protocol, to support the building of Web3 capacities for wildlife" /> + android:textColor="@color/text_secondary" /> @@ -174,7 +174,7 @@ android:layout_height="wrap_content" android:layout_marginBottom="8dp" android:text="@string/referendum_details_timeline" - android:textColor="@color/text_tertiary" /> + android:textColor="@color/text_secondary" /> + android:textColor="@color/text_secondary" /> diff --git a/feature-governance-impl/src/main/res/layout/item_delegate.xml b/feature-governance-impl/src/main/res/layout/item_delegate.xml index e65b1033ff..9cf7896800 100644 --- a/feature-governance-impl/src/main/res/layout/item_delegate.xml +++ b/feature-governance-impl/src/main/res/layout/item_delegate.xml @@ -265,7 +265,7 @@ style="@style/TextAppearance.NovaFoundation.Regular.Caption1" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:textColor="@color/text_tertiary" /> + android:textColor="@color/text_secondary" /> diff --git a/feature-governance-impl/src/main/res/layout/item_delegate_shimmering.xml b/feature-governance-impl/src/main/res/layout/item_delegate_shimmering.xml index 20c4675faa..bd9166866a 100644 --- a/feature-governance-impl/src/main/res/layout/item_delegate_shimmering.xml +++ b/feature-governance-impl/src/main/res/layout/item_delegate_shimmering.xml @@ -61,7 +61,7 @@ android:layout_height="6dp" android:layout_marginTop="4dp" android:background="@drawable/bg_shimmering" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/itemDelegateStatsShimmering" /> @@ -71,7 +71,7 @@ android:layout_height="10dp" android:layout_marginTop="8dp" android:background="@drawable/bg_shimmering" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/itemDelegateFirstStatTitleShimmering" /> @@ -95,7 +95,7 @@ android:layout_height="6dp" android:layout_marginTop="4dp" android:background="@drawable/bg_shimmering" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" android:layout_marginStart="12dp" app:layout_constraintStart_toEndOf="@id/itemDelegateFirstStatShimmeringDivider" app:layout_constraintTop_toBottomOf="@+id/itemDelegateStatsShimmering" /> @@ -106,7 +106,7 @@ android:layout_height="10dp" android:layout_marginTop="8dp" android:background="@drawable/bg_shimmering" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" android:layout_marginStart="12dp" app:layout_constraintStart_toEndOf="@id/itemDelegateFirstStatShimmeringDivider" app:layout_constraintTop_toBottomOf="@+id/itemDelegateSecondStatTitleShimmering" /> @@ -128,7 +128,7 @@ android:layout_height="6dp" android:layout_marginTop="4dp" android:background="@drawable/bg_shimmering" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" android:layout_marginStart="12dp" app:layout_constraintStart_toEndOf="@id/itemDelegateSecondStatShimmeringDivider" app:layout_constraintTop_toBottomOf="@+id/itemDelegateStatsShimmering" /> @@ -139,7 +139,7 @@ android:layout_height="10dp" android:layout_marginTop="8dp" android:background="@drawable/bg_shimmering" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" android:layout_marginStart="12dp" app:layout_constraintStart_toEndOf="@id/itemDelegateSecondStatShimmeringDivider" app:layout_constraintTop_toBottomOf="@+id/itemDelegateSecondStatTitleShimmering" /> diff --git a/feature-governance-impl/src/main/res/layout/item_delegation_tracks_header.xml b/feature-governance-impl/src/main/res/layout/item_delegation_tracks_header.xml index b1771ce9ae..a8a732580c 100644 --- a/feature-governance-impl/src/main/res/layout/item_delegation_tracks_header.xml +++ b/feature-governance-impl/src/main/res/layout/item_delegation_tracks_header.xml @@ -25,7 +25,7 @@ android:layout_height="wrap_content" android:layout_marginHorizontal="16dp" android:text="@string/select_delegation_tracks_description" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" app:layout_constraintTop_toBottomOf="@id/selectDelegationTracksTitle" /> diff --git a/feature-governance-impl/src/main/res/layout/item_delegations_header.xml b/feature-governance-impl/src/main/res/layout/item_delegations_header.xml index e6a9cd3164..025e2af911 100644 --- a/feature-governance-impl/src/main/res/layout/item_delegations_header.xml +++ b/feature-governance-impl/src/main/res/layout/item_delegations_header.xml @@ -40,7 +40,7 @@ android:layout_marginTop="8dp" android:paddingBottom="4dp" android:text="@string/delegation_list_banner_description" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" app:layout_constrainedWidth="true" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0" diff --git a/feature-governance-impl/src/main/res/layout/item_delegations_search_result_count.xml b/feature-governance-impl/src/main/res/layout/item_delegations_search_result_count.xml index 4eeb6cc9ba..30d8ff92fd 100644 --- a/feature-governance-impl/src/main/res/layout/item_delegations_search_result_count.xml +++ b/feature-governance-impl/src/main/res/layout/item_delegations_search_result_count.xml @@ -8,5 +8,5 @@ android:layout_marginHorizontal="16dp" android:paddingVertical="8dp" android:textAllCaps="true" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" tools:text="@string/delegate_search_result_count" /> \ No newline at end of file diff --git a/feature-governance-impl/src/main/res/layout/item_delegator.xml b/feature-governance-impl/src/main/res/layout/item_delegator.xml index 8e14a2bdb2..7fa9e7cbdd 100644 --- a/feature-governance-impl/src/main/res/layout/item_delegator.xml +++ b/feature-governance-impl/src/main/res/layout/item_delegator.xml @@ -78,7 +78,7 @@ android:layout_height="wrap_content" android:layout_gravity="end" android:includeFontPadding="false" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" tools:text="3,000 KSM × 0.1x" /> diff --git a/feature-governance-impl/src/main/res/layout/item_governance_lock.xml b/feature-governance-impl/src/main/res/layout/item_governance_lock.xml index f3d7c412b2..9b27213430 100644 --- a/feature-governance-impl/src/main/res/layout/item_governance_lock.xml +++ b/feature-governance-impl/src/main/res/layout/item_governance_lock.xml @@ -27,7 +27,7 @@ android:layout_marginEnd="16dp" android:gravity="center_vertical" android:includeFontPadding="false" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" tools:text="40 days left" /> \ No newline at end of file diff --git a/feature-governance-impl/src/main/res/layout/item_referendum.xml b/feature-governance-impl/src/main/res/layout/item_referendum.xml index 15b5e17cd1..e9cead75ea 100644 --- a/feature-governance-impl/src/main/res/layout/item_referendum.xml +++ b/feature-governance-impl/src/main/res/layout/item_referendum.xml @@ -37,7 +37,7 @@ android:ellipsize="end" android:gravity="end" android:includeFontPadding="false" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" tools:text="Approve in 3:59:59" /> diff --git a/feature-governance-impl/src/main/res/layout/item_referendum_shimmering.xml b/feature-governance-impl/src/main/res/layout/item_referendum_shimmering.xml index 533011055a..5a1953aec3 100644 --- a/feature-governance-impl/src/main/res/layout/item_referendum_shimmering.xml +++ b/feature-governance-impl/src/main/res/layout/item_referendum_shimmering.xml @@ -61,7 +61,7 @@ android:layout_height="8dp" android:layout_marginTop="21dp" android:background="@drawable/bg_shimmering" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/itemReferendumShimmeringTrack" /> diff --git a/feature-governance-impl/src/main/res/layout/item_referendum_voter.xml b/feature-governance-impl/src/main/res/layout/item_referendum_voter.xml index 5c810c9405..9912fa0914 100644 --- a/feature-governance-impl/src/main/res/layout/item_referendum_voter.xml +++ b/feature-governance-impl/src/main/res/layout/item_referendum_voter.xml @@ -95,7 +95,7 @@ android:layout_height="wrap_content" android:layout_gravity="end" android:includeFontPadding="false" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" tools:text="3,000 KSM × 0.1x" /> diff --git a/feature-governance-impl/src/main/res/layout/item_timeline_default_item.xml b/feature-governance-impl/src/main/res/layout/item_timeline_default_item.xml index e2e39d1ec3..9fe3b04d90 100644 --- a/feature-governance-impl/src/main/res/layout/item_timeline_default_item.xml +++ b/feature-governance-impl/src/main/res/layout/item_timeline_default_item.xml @@ -28,6 +28,6 @@ android:gravity="center_vertical" android:includeFontPadding="false" android:minHeight="16dp" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" tools:text="Sept 1, 2022 04:44:31" /> \ No newline at end of file diff --git a/feature-governance-impl/src/main/res/layout/item_track_delegation.xml b/feature-governance-impl/src/main/res/layout/item_track_delegation.xml index e7def25f9e..3e6becc84b 100644 --- a/feature-governance-impl/src/main/res/layout/item_track_delegation.xml +++ b/feature-governance-impl/src/main/res/layout/item_track_delegation.xml @@ -48,7 +48,7 @@ android:layout_height="wrap_content" android:layout_gravity="end" android:includeFontPadding="false" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" tools:text="3,000 KSM × 0.1x" /> diff --git a/feature-governance-impl/src/main/res/layout/item_unavailable_tracks_group.xml b/feature-governance-impl/src/main/res/layout/item_unavailable_tracks_group.xml index a6cd6bdaee..d9af23c28a 100644 --- a/feature-governance-impl/src/main/res/layout/item_unavailable_tracks_group.xml +++ b/feature-governance-impl/src/main/res/layout/item_unavailable_tracks_group.xml @@ -18,7 +18,7 @@ android:ellipsize="end" android:includeFontPadding="false" android:singleLine="true" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" app:layout_constrainedWidth="true" app:layout_constraintBottom_toTopOf="@+id/itemDelegateType" app:layout_constraintEnd_toEndOf="parent" diff --git a/feature-governance-impl/src/main/res/layout/view_amount_changes.xml b/feature-governance-impl/src/main/res/layout/view_amount_changes.xml index 2b1c2a1ba5..0f282c6d43 100644 --- a/feature-governance-impl/src/main/res/layout/view_amount_changes.xml +++ b/feature-governance-impl/src/main/res/layout/view_amount_changes.xml @@ -25,7 +25,7 @@ android:layout_marginTop="13dp" android:layout_marginBottom="13dp" android:includeFontPadding="false" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" app:layout_constrainedWidth="true" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="@id/valueBarrier" @@ -42,7 +42,7 @@ android:layout_height="wrap_content" android:layout_marginEnd="4dp" android:includeFontPadding="false" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" app:layout_constraintBaseline_toBaselineOf="@id/valueChangesTo" app:layout_constraintEnd_toStartOf="@+id/valueChangesArrow" tools:text="71" /> @@ -78,7 +78,7 @@ android:layout_height="wrap_content" android:gravity="center_vertical" android:includeFontPadding="false" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" app:drawableTint="@color/icon_accent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" diff --git a/feature-governance-impl/src/main/res/layout/view_referendum_dapp_list.xml b/feature-governance-impl/src/main/res/layout/view_referendum_dapp_list.xml index 276911951a..a43c2c46d5 100644 --- a/feature-governance-impl/src/main/res/layout/view_referendum_dapp_list.xml +++ b/feature-governance-impl/src/main/res/layout/view_referendum_dapp_list.xml @@ -17,6 +17,6 @@ android:layout_marginHorizontal="16dp" android:layout_marginBottom="8dp" android:text="@string/referendum_dapp_list_title" - android:textColor="@color/text_tertiary" /> + android:textColor="@color/text_secondary" /> \ No newline at end of file diff --git a/feature-governance-impl/src/main/res/layout/view_vote_power.xml b/feature-governance-impl/src/main/res/layout/view_vote_power.xml index c7d626451c..f751a94b60 100644 --- a/feature-governance-impl/src/main/res/layout/view_vote_power.xml +++ b/feature-governance-impl/src/main/res/layout/view_vote_power.xml @@ -13,7 +13,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/referendum_vote_select_conviction_title" - android:textColor="@color/text_tertiary" /> + android:textColor="@color/text_secondary" /> @@ -36,7 +36,7 @@ android:gravity="end" android:includeFontPadding="false" android:maxLines="2" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@+id/votingStatusTitle" app:layout_constraintTop_toTopOf="parent" diff --git a/feature-governance-impl/src/main/res/layout/view_voting_threshold.xml b/feature-governance-impl/src/main/res/layout/view_voting_threshold.xml index 715edf8f7a..e050843fa1 100644 --- a/feature-governance-impl/src/main/res/layout/view_voting_threshold.xml +++ b/feature-governance-impl/src/main/res/layout/view_voting_threshold.xml @@ -14,7 +14,7 @@ android:layout_height="wrap_content" android:gravity="center_vertical" android:includeFontPadding="false" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" tools:text="Threshold: 16,492 of 15,392.5 KSM " /> diff --git a/feature-governance-impl/src/main/res/layout/view_your_vote.xml b/feature-governance-impl/src/main/res/layout/view_your_vote.xml index 6758741ae2..4bf80b771c 100644 --- a/feature-governance-impl/src/main/res/layout/view_your_vote.xml +++ b/feature-governance-impl/src/main/res/layout/view_your_vote.xml @@ -19,7 +19,7 @@ android:ellipsize="end" android:includeFontPadding="false" android:maxLines="2" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" app:layout_constrainedWidth="true" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/linearLayout" @@ -73,7 +73,7 @@ style="@style/TextAppearance.NovaFoundation.Regular" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" android:textSize="12sp" tools:text="10 KSM × 6x" /> diff --git a/feature-governance-impl/src/main/res/layout/view_your_vote_preview.xml b/feature-governance-impl/src/main/res/layout/view_your_vote_preview.xml index ad1c76762b..b6c44861f0 100644 --- a/feature-governance-impl/src/main/res/layout/view_your_vote_preview.xml +++ b/feature-governance-impl/src/main/res/layout/view_your_vote_preview.xml @@ -27,7 +27,7 @@ android:layout_gravity="center_vertical" android:layout_marginStart="6dp" android:includeFontPadding="false" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" tools:text="To pass: 50%" /> \ No newline at end of file diff --git a/feature-governance-impl/src/main/res/values/styles.xml b/feature-governance-impl/src/main/res/values/styles.xml index c9b1217754..f0281071e2 100644 --- a/feature-governance-impl/src/main/res/values/styles.xml +++ b/feature-governance-impl/src/main/res/values/styles.xml @@ -25,7 +25,7 @@ 3dp 6dp 8dp - @color/text_tertiary + @color/text_secondary \ No newline at end of file diff --git a/feature-onboarding-impl/src/main/java/io/novafoundation/nova/feature_onboarding_impl/presentation/welcome/WelcomeViewModel.kt b/feature-onboarding-impl/src/main/java/io/novafoundation/nova/feature_onboarding_impl/presentation/welcome/WelcomeViewModel.kt index b1fa309d11..430f68a4a1 100644 --- a/feature-onboarding-impl/src/main/java/io/novafoundation/nova/feature_onboarding_impl/presentation/welcome/WelcomeViewModel.kt +++ b/feature-onboarding-impl/src/main/java/io/novafoundation/nova/feature_onboarding_impl/presentation/welcome/WelcomeViewModel.kt @@ -12,6 +12,7 @@ import io.novafoundation.nova.common.utils.Event import io.novafoundation.nova.feature_account_api.domain.model.PolkadotVaultVariant import io.novafoundation.nova.feature_account_api.presenatation.account.add.AddAccountPayload import io.novafoundation.nova.feature_account_api.presenatation.account.add.ImportAccountPayload +import io.novafoundation.nova.feature_account_api.presenatation.account.add.asImportType import io.novafoundation.nova.feature_account_api.presenatation.mixin.importType.ImportTypeChooserMixin import io.novafoundation.nova.feature_onboarding_impl.OnboardingRouter import io.novafoundation.nova.feature_onboarding_impl.presentation.welcome.model.HardwareWalletModel @@ -49,7 +50,7 @@ class WelcomeViewModel( fun importAccountClicked() { val payload = ImportTypeChooserMixin.Payload( - onChosen = { router.openImportAccountScreen(ImportAccountPayload(it, addAccountPayload)) } + onChosen = { router.openImportAccountScreen(ImportAccountPayload(it.asImportType(), addAccountPayload)) } ) importTypeChooserMixin.showChooser(payload) diff --git a/feature-onboarding-impl/src/main/res/layout/fragment_welcome.xml b/feature-onboarding-impl/src/main/res/layout/fragment_welcome.xml index 39121bd22f..a3ba0072e6 100644 --- a/feature-onboarding-impl/src/main/res/layout/fragment_welcome.xml +++ b/feature-onboarding-impl/src/main/res/layout/fragment_welcome.xml @@ -96,7 +96,7 @@ android:layout_marginBottom="24dp" android:gravity="center" android:text="@string/onboarding_terms_and_conditions_1_v2_2_0" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" android:textColorLink="@color/text_primary" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" diff --git a/feature-settings-impl/src/main/java/io/novafoundation/nova/feature_settings_impl/presentation/settings/SettingsFragment.kt b/feature-settings-impl/src/main/java/io/novafoundation/nova/feature_settings_impl/presentation/settings/SettingsFragment.kt index e1b7396424..c5fd86c86b 100644 --- a/feature-settings-impl/src/main/java/io/novafoundation/nova/feature_settings_impl/presentation/settings/SettingsFragment.kt +++ b/feature-settings-impl/src/main/java/io/novafoundation/nova/feature_settings_impl/presentation/settings/SettingsFragment.kt @@ -37,6 +37,7 @@ import kotlinx.android.synthetic.main.fragment_settings.settingsTwitter import kotlinx.android.synthetic.main.fragment_settings.settingsWalletConnect import kotlinx.android.synthetic.main.fragment_settings.settingsWallets import kotlinx.android.synthetic.main.fragment_settings.settingsWebsite +import kotlinx.android.synthetic.main.fragment_settings.settingsWiki import kotlinx.android.synthetic.main.fragment_settings.settingsYoutube class SettingsFragment : BaseFragment() { @@ -68,8 +69,9 @@ class SettingsFragment : BaseFragment() { settingsTerms.setOnClickListener { viewModel.termsClicked() } settingsPrivacy.setOnClickListener { viewModel.privacyClicked() } - settingsEmail.setOnClickListener { viewModel.emailClicked() } settingsRateUs.setOnClickListener { viewModel.rateUsClicked() } + settingsWiki.setOnClickListener { viewModel.wikiClicked() } + settingsEmail.setOnClickListener { viewModel.emailClicked() } settingsBiometricAuth.setOnClickListener { viewModel.changeBiometricAuth() } settingsPinCodeVerification.setOnClickListener { viewModel.changePincodeVerification() } diff --git a/feature-settings-impl/src/main/java/io/novafoundation/nova/feature_settings_impl/presentation/settings/SettingsViewModel.kt b/feature-settings-impl/src/main/java/io/novafoundation/nova/feature_settings_impl/presentation/settings/SettingsViewModel.kt index b80bb510c8..91200bea58 100644 --- a/feature-settings-impl/src/main/java/io/novafoundation/nova/feature_settings_impl/presentation/settings/SettingsViewModel.kt +++ b/feature-settings-impl/src/main/java/io/novafoundation/nova/feature_settings_impl/presentation/settings/SettingsViewModel.kt @@ -10,26 +10,26 @@ import io.novafoundation.nova.common.mixin.actionAwaitable.confirmingAction import io.novafoundation.nova.common.mixin.api.Browserable import io.novafoundation.nova.common.resources.AppVersionProvider import io.novafoundation.nova.common.resources.ResourceManager -import io.novafoundation.nova.common.sequrity.biometry.BiometricResponse -import io.novafoundation.nova.common.sequrity.biometry.BiometricService import io.novafoundation.nova.common.sequrity.SafeModeService import io.novafoundation.nova.common.sequrity.TwoFactorVerificationResult import io.novafoundation.nova.common.sequrity.TwoFactorVerificationService +import io.novafoundation.nova.common.sequrity.biometry.BiometricResponse +import io.novafoundation.nova.common.sequrity.biometry.BiometricService +import io.novafoundation.nova.common.sequrity.biometry.mapBiometricErrors import io.novafoundation.nova.common.utils.Event import io.novafoundation.nova.common.utils.event import io.novafoundation.nova.common.utils.flowOf import io.novafoundation.nova.common.utils.inBackground import io.novafoundation.nova.feature_account_api.domain.interfaces.SelectedAccountUseCase import io.novafoundation.nova.feature_account_api.presenatation.language.LanguageUseCase -import io.novafoundation.nova.common.sequrity.biometry.mapBiometricErrors import io.novafoundation.nova.feature_currency_api.domain.CurrencyInteractor import io.novafoundation.nova.feature_currency_api.presentation.mapper.mapCurrencyToUI import io.novafoundation.nova.feature_settings_impl.R import io.novafoundation.nova.feature_settings_impl.SettingsRouter import io.novafoundation.nova.feature_wallet_connect_api.domain.sessions.WalletConnectSessionsUseCase import io.novafoundation.nova.feature_wallet_connect_api.presentation.mapNumberOfActiveSessionsToUi -import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.filterIsInstance +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull @@ -101,7 +101,6 @@ class SettingsViewModel( .asLiveData() init { - syncWalletConnectSessions() setupBiometric() } @@ -176,6 +175,10 @@ class SettingsViewModel( openLink(appLinksProvider.rateApp) } + fun wikiClicked() { + openLink(appLinksProvider.wikiBase) + } + fun websiteClicked() { openLink(appLinksProvider.website) } @@ -222,10 +225,6 @@ class SettingsViewModel( openBrowserEvent.value = link.event() } - private fun syncWalletConnectSessions() = launch { - walletConnectSessionsUseCase.syncActiveSessions() - } - private fun setupBiometric() { biometricService.biometryServiceResponseFlow .filterIsInstance() 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 dcdf1380bf..38d88a9215 100644 --- a/feature-settings-impl/src/main/res/layout/fragment_settings.xml +++ b/feature-settings-impl/src/main/res/layout/fragment_settings.xml @@ -182,6 +182,13 @@ app:icon="@drawable/ic_rate_us" app:title="@string/about_rate_app" /> + + diff --git a/feature-settings-impl/src/main/res/layout/view_settings_item.xml b/feature-settings-impl/src/main/res/layout/view_settings_item.xml index 93e51efd9e..9cbe9f9cc1 100644 --- a/feature-settings-impl/src/main/res/layout/view_settings_item.xml +++ b/feature-settings-impl/src/main/res/layout/view_settings_item.xml @@ -42,7 +42,7 @@ android:layout_gravity="center_vertical" android:layout_marginEnd="8dp" android:includeFontPadding="false" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" tools:text="Value" /> 12dp 24dp - @color/text_tertiary + @color/text_secondary \ No newline at end of file diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/repository/BagListRepository.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/repository/BagListRepository.kt index b7d66a47b9..827c218022 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/repository/BagListRepository.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/repository/BagListRepository.kt @@ -10,8 +10,7 @@ import io.novafoundation.nova.common.utils.numberConstantOrNull import io.novafoundation.nova.common.utils.voterListOrNull import io.novafoundation.nova.feature_staking_impl.domain.bagList.BagListLocator import io.novafoundation.nova.feature_staking_impl.domain.model.BagListNode -import io.novafoundation.nova.runtime.ext.Geneses -import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain +import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId import io.novafoundation.nova.runtime.network.binding.collectionOf import io.novafoundation.nova.runtime.storage.source.StorageDataSource @@ -36,14 +35,10 @@ suspend fun BagListRepository.bagListLocatorOrNull(chainId: ChainId): BagListLoc suspend fun BagListRepository.bagListLocatorOrThrow(chainId: ChainId): BagListLocator = requireNotNull(bagListLocatorOrNull(chainId)) class LocalBagListRepository( - private val localStorage: StorageDataSource + private val localStorage: StorageDataSource, + private val chainRegistry: ChainRegistry ) : BagListRepository { - private val knownMaxElectingVoters = mapOf( - Chain.Geneses.KUSAMA to 12500, - Chain.Geneses.POLKADOT to 22500, - ) - override suspend fun bagThresholds(chainId: ChainId): List? { return localStorage.query(chainId) { runtime.metadata.voterListOrNull()?.constant("BagThresholds")?.getAs(collectionOf(::score)) @@ -59,7 +54,7 @@ class LocalBagListRepository( override suspend fun maxElectingVotes(chainId: ChainId): BigInteger? { return localStorage.query(chainId) { runtime.metadata.electionProviderMultiPhaseOrNull()?.numberConstantOrNull("MaxElectingVoters", runtime) - ?: knownMaxElectingVoters[chainId]?.toBigInteger() + ?: knownMaxElectingVoters(chainId) } } @@ -83,4 +78,10 @@ class LocalBagListRepository( }.getOrNull() private fun score(decoded: Any?): BagListNode.Score = BagListNode.Score(bindNumber(decoded)) + + private suspend fun knownMaxElectingVoters(chainId: ChainId): BigInteger? { + val chain = chainRegistry.getChain(chainId) + + return chain.additional?.stakingMaxElectingVoters?.toBigInteger() + } } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/repository/StakingConstantsRepository.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/repository/StakingConstantsRepository.kt index e938bec8f4..e2acbae816 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/repository/StakingConstantsRepository.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/repository/StakingConstantsRepository.kt @@ -1,8 +1,11 @@ package io.novafoundation.nova.feature_staking_impl.data.repository +import io.novafoundation.nova.common.data.network.runtime.binding.bindNumber import io.novafoundation.nova.common.utils.numberConstant import io.novafoundation.nova.common.utils.numberConstantOrNull import io.novafoundation.nova.common.utils.staking +import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance +import io.novafoundation.nova.runtime.call.MultiChainRuntimeCallsApi import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId import io.novafoundation.nova.runtime.multiNetwork.getRuntime @@ -13,6 +16,7 @@ private const val MAX_UNLOCK_CHUNKS_FALLBACK = 32 class StakingConstantsRepository( private val chainRegistry: ChainRegistry, + private val runtimeCallsApi: MultiChainRuntimeCallsApi ) { suspend fun maxUnlockingChunks(chainId: ChainId): BigInteger { @@ -24,11 +28,24 @@ class StakingConstantsRepository( suspend fun lockupPeriodInEras(chainId: ChainId): BigInteger = getNumberConstant(chainId, "BondingDuration") - suspend fun maxValidatorsPerNominator(chainId: ChainId): Int { + suspend fun maxValidatorsPerNominator(chainId: ChainId, stake: Balance): Int { return getOptionalNumberConstant(chainId, "MaxNominations")?.toInt() + ?: getMaxNominationsQuota(chainId, stake)?.toInt() ?: MAX_NOMINATIONS_FALLBACK } + private suspend fun getMaxNominationsQuota(chainId: ChainId, stake: Balance): BigInteger? = runCatching { + val runtimeCallApi = runtimeCallsApi.forChain(chainId) + + runtimeCallApi.call( + section = "StakingApi", + method = "nominations_quota", + arguments = listOf(stake to "Balance"), + returnType = "u32", + returnBinding = ::bindNumber + ) + }.getOrNull() + private suspend fun getNumberConstant(chainId: ChainId, constantName: String): BigInteger { val runtime = chainRegistry.getRuntime(chainId) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/StakingFeatureModule.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/StakingFeatureModule.kt index 7ebf756648..bc0b262540 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/StakingFeatureModule.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/StakingFeatureModule.kt @@ -20,14 +20,14 @@ import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepos import io.novafoundation.nova.feature_account_api.domain.updaters.AccountUpdateScope import io.novafoundation.nova.feature_account_api.presenatation.account.AddressDisplayUseCase import io.novafoundation.nova.feature_staking_api.data.network.blockhain.updaters.PooledBalanceUpdaterFactory +import io.novafoundation.nova.feature_staking_api.data.nominationPools.pool.PoolAccountDerivation import io.novafoundation.nova.feature_staking_api.domain.api.StakingRepository import io.novafoundation.nova.feature_staking_api.presentation.nominationPools.display.PoolDisplayUseCase import io.novafoundation.nova.feature_staking_impl.data.StakingSharedState +import io.novafoundation.nova.feature_staking_impl.data.dashboard.repository.StakingDashboardRepository import io.novafoundation.nova.feature_staking_impl.data.network.subquery.StakingApi import io.novafoundation.nova.feature_staking_impl.data.network.subquery.SubQueryValidatorSetFetcher import io.novafoundation.nova.feature_staking_impl.data.nominationPools.network.blockhain.updater.RealPooledBalanceUpdaterFactory -import io.novafoundation.nova.feature_staking_api.data.nominationPools.pool.PoolAccountDerivation -import io.novafoundation.nova.feature_staking_impl.data.dashboard.repository.StakingDashboardRepository import io.novafoundation.nova.feature_staking_impl.data.nominationPools.repository.NominationPoolStateRepository import io.novafoundation.nova.feature_staking_impl.data.parachainStaking.RoundDurationEstimator import io.novafoundation.nova.feature_staking_impl.data.repository.BagListRepository @@ -99,6 +99,7 @@ import io.novafoundation.nova.feature_wallet_api.domain.interfaces.WalletConstan import io.novafoundation.nova.feature_wallet_api.domain.interfaces.WalletRepository import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeLoaderMixin import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.create +import io.novafoundation.nova.runtime.call.MultiChainRuntimeCallsApi import io.novafoundation.nova.runtime.di.LOCAL_STORAGE_SOURCE import io.novafoundation.nova.runtime.di.REMOTE_STORAGE_SOURCE import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry @@ -189,7 +190,8 @@ class StakingFeatureModule { @FeatureScope fun provideBagListRepository( @Named(LOCAL_STORAGE_SOURCE) localStorageSource: StorageDataSource, - ): BagListRepository = LocalBagListRepository(localStorageSource) + chainRegistry: ChainRegistry + ): BagListRepository = LocalBagListRepository(localStorageSource, chainRegistry) @Provides @FeatureScope @@ -321,7 +323,8 @@ class StakingFeatureModule { @FeatureScope fun provideStakingConstantsRepository( chainRegistry: ChainRegistry, - ) = StakingConstantsRepository(chainRegistry) + runtimeCallsApi: MultiChainRuntimeCallsApi + ) = StakingConstantsRepository(chainRegistry, runtimeCallsApi) @Provides @FeatureScope diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/staking/startMultiStaking/StartMultiStakingModule.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/staking/startMultiStaking/StartMultiStakingModule.kt index 290e0557f7..606ff7aa75 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/staking/startMultiStaking/StartMultiStakingModule.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/staking/startMultiStaking/StartMultiStakingModule.kt @@ -10,17 +10,17 @@ import io.novafoundation.nova.common.resources.ResourceManager import io.novafoundation.nova.feature_staking_api.domain.api.StakingRepository import io.novafoundation.nova.feature_staking_impl.data.nominationPools.pool.KnownNovaPools import io.novafoundation.nova.feature_staking_impl.data.nominationPools.repository.NominationPoolGlobalsRepository +import io.novafoundation.nova.feature_staking_impl.data.repository.StakingConstantsRepository import io.novafoundation.nova.feature_staking_impl.domain.common.StakingSharedComputation import io.novafoundation.nova.feature_staking_impl.domain.nominationPools.common.NominationPoolSharedComputation +import io.novafoundation.nova.feature_staking_impl.domain.nominationPools.common.validations.PoolAvailableBalanceValidationFactory import io.novafoundation.nova.feature_staking_impl.domain.nominationPools.pools.NominationPoolProvider import io.novafoundation.nova.feature_staking_impl.domain.nominationPools.pools.recommendation.NominationPoolRecommenderFactory import io.novafoundation.nova.feature_staking_impl.domain.nominationPools.selecting.SearchNominationPoolInteractor import io.novafoundation.nova.feature_staking_impl.domain.recommendations.ValidatorRecommenderFactory -import io.novafoundation.nova.feature_staking_impl.domain.nominationPools.common.validations.PoolAvailableBalanceValidationFactory import io.novafoundation.nova.feature_staking_impl.domain.recommendations.settings.RecommendationSettingsProviderFactory import io.novafoundation.nova.feature_staking_impl.domain.staking.start.common.NominationPoolsAvailableBalanceResolver import io.novafoundation.nova.feature_staking_impl.domain.staking.start.common.RealNominationPoolsAvailableBalanceResolver -import io.novafoundation.nova.feature_staking_impl.domain.staking.start.common.selection.store.RealStartMultiStakingSelectionStoreProvider import io.novafoundation.nova.feature_staking_impl.domain.staking.start.common.selection.store.StartMultiStakingSelectionStoreProvider import io.novafoundation.nova.feature_staking_impl.domain.staking.start.common.types.CompoundStakingTypeDetailsProvidersFactory import io.novafoundation.nova.feature_staking_impl.domain.staking.start.common.types.StakingTypeDetailsProviderFactory @@ -138,7 +138,7 @@ class StartMultiStakingModule { fun provideStartMultiStakingSelectionStoreProvider( computationalCache: ComputationalCache ): StartMultiStakingSelectionStoreProvider { - return RealStartMultiStakingSelectionStoreProvider(computationalCache, "MultiStakingSelection") + return StartMultiStakingSelectionStoreProvider(computationalCache, "MultiStakingSelection") } @Provides @@ -147,7 +147,7 @@ class StartMultiStakingModule { fun provideStakingTypeEditingSelectionStoreProvider( computationalCache: ComputationalCache ): StartMultiStakingSelectionStoreProvider { - return RealStartMultiStakingSelectionStoreProvider(computationalCache, "StakingTypeEditing") + return StartMultiStakingSelectionStoreProvider(computationalCache, "StakingTypeEditing") } @Provides @@ -164,13 +164,15 @@ class StartMultiStakingModule { validatorRecommenderFactory: ValidatorRecommenderFactory, recommendationSettingsProviderFactory: RecommendationSettingsProviderFactory, stakingSharedComputation: StakingSharedComputation, - stakingRepository: StakingRepository + stakingRepository: StakingRepository, + stakingConstantsRepository: StakingConstantsRepository, ): SingleStakingPropertiesFactory { return DirectStakingPropertiesFactory( validatorRecommenderFactory = validatorRecommenderFactory, recommendationSettingsProviderFactory = recommendationSettingsProviderFactory, stakingSharedComputation = stakingSharedComputation, - stakingRepository = stakingRepository + stakingRepository = stakingRepository, + stakingConstantsRepository = stakingConstantsRepository ) } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/StakingInteractor.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/StakingInteractor.kt index 841cb1bdfa..25677c65ed 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/StakingInteractor.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/StakingInteractor.kt @@ -75,6 +75,7 @@ class StakingInteractor( private val assetUseCase: AssetUseCase, private val stakingSharedComputation: StakingSharedComputation, ) { + suspend fun calculatePendingPayouts(scope: CoroutineScope): Result = withContext(Dispatchers.Default) { runCatching { val currentStakingState = selectedAccountStakingStateFlow(scope).first() @@ -214,10 +215,6 @@ class StakingInteractor( getLockupDuration(stakingSharedState.chainId(), sharedComputationScope) } - suspend fun getEraDuration(sharedComputationScope: CoroutineScope) = withContext(Dispatchers.Default) { - getEraTimeCalculator(sharedComputationScope).eraDuration() - } - fun selectedAccountStakingStateFlow(scope: CoroutineScope) = flowOfAll { val assetWithChain = stakingSharedState.chainAndAsset() @@ -261,8 +258,8 @@ class StakingInteractor( stakingRepository.getRewardDestination(accountStakingState) } - suspend fun maxValidatorsPerNominator(): Int = withContext(Dispatchers.Default) { - stakingConstantsRepository.maxValidatorsPerNominator(stakingSharedState.chainId()) + suspend fun maxValidatorsPerNominator(stake: Balance): Int = withContext(Dispatchers.Default) { + stakingConstantsRepository.maxValidatorsPerNominator(stakingSharedState.chainId(), stake) } suspend fun maxRewardedNominators(): Int = withContext(Dispatchers.Default) { diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/claimRewards/validations/Declarations.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/claimRewards/validations/Declarations.kt index 38b52d0c6d..abfd1ab8f9 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/claimRewards/validations/Declarations.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/claimRewards/validations/Declarations.kt @@ -3,6 +3,7 @@ package io.novafoundation.nova.feature_staking_impl.domain.nominationPools.claim import io.novafoundation.nova.common.validation.ValidationSystem import io.novafoundation.nova.common.validation.ValidationSystemBuilder import io.novafoundation.nova.feature_staking_impl.domain.common.validation.profitableAction +import io.novafoundation.nova.feature_wallet_api.domain.model.balanceCountedTowardsED import io.novafoundation.nova.feature_wallet_api.domain.validation.EnoughTotalToStayAboveEDValidationFactory import io.novafoundation.nova.feature_wallet_api.domain.validation.sufficientBalance import io.novafoundation.nova.feature_wallet_api.domain.validation.validate @@ -30,7 +31,7 @@ private fun NominationPoolsClaimRewardsValidationSystemBuilder.sufficientCommiss ) { enoughTotalToStayAboveEDValidationFactory.validate( fee = { it.fee }, - balance = { it.asset.total }, + balance = { it.asset.balanceCountedTowardsED() }, chainWithAsset = { ChainWithAsset(it.chain, it.chain.utilityAsset) }, error = { payload, _ -> NominationPoolsClaimRewardsValidationFailure.ToStayAboveED(payload.chain.utilityAsset) } ) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/common/poolState/PoolState.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/common/poolState/PoolState.kt index faecce423e..72172d93b1 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/common/poolState/PoolState.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/common/poolState/PoolState.kt @@ -11,6 +11,14 @@ fun AccountIdMap.isPoolStaking(poolStash: AccountId, poolNominations: // we still mark it as Inactive to warn user preemptively if (poolNominations == null) return false + // fast path - we don't need to traverse the entire exposure map if pool is staking with some of its current validators + if (isPoolStakingWithCurrentNominations(poolStash, poolNominations)) return true + + // slow path - despite pool is not staking with its current nomination it might still stake with a validator if it changed nominations recently + return isPoolStakingWithAnyValidator(poolStash) +} + +private fun AccountIdMap.isPoolStakingWithCurrentNominations(poolStash: AccountId, poolNominations: Nominations): Boolean { return poolNominations.targets.any { validator -> val accountIdHex = validator.toHexString() val exposure = get(accountIdHex) ?: return@any false @@ -18,3 +26,9 @@ fun AccountIdMap.isPoolStaking(poolStash: AccountId, poolNominations: exposure.others.any { it.who.contentEquals(poolStash) } } } + +private fun AccountIdMap.isPoolStakingWithAnyValidator(poolStash: AccountId): Boolean { + return any { (_, exposure) -> + exposure.others.any { it.who.contentEquals(poolStash) } + } +} diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/redeem/NominationPoolsRedeemInteractor.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/redeem/NominationPoolsRedeemInteractor.kt index 75f2735c77..aa94870dee 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/redeem/NominationPoolsRedeemInteractor.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/redeem/NominationPoolsRedeemInteractor.kt @@ -3,6 +3,8 @@ package io.novafoundation.nova.feature_staking_impl.domain.nominationPools.redee import io.novafoundation.nova.common.utils.flowOfAll import io.novafoundation.nova.common.utils.isZero import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService +import io.novafoundation.nova.feature_staking_api.data.nominationPools.pool.PoolAccountDerivation +import io.novafoundation.nova.feature_staking_api.data.nominationPools.pool.bondedAccountOf import io.novafoundation.nova.feature_staking_api.domain.api.StakingRepository import io.novafoundation.nova.feature_staking_api.domain.model.numberOfSlashingSpans import io.novafoundation.nova.feature_staking_api.domain.model.totalRedeemableIn @@ -12,8 +14,6 @@ import io.novafoundation.nova.feature_staking_impl.data.nominationPools.network. import io.novafoundation.nova.feature_staking_impl.data.nominationPools.network.blockhain.models.PoolMember import io.novafoundation.nova.feature_staking_impl.data.nominationPools.network.blockhain.models.totalPointsAfterRedeemAt import io.novafoundation.nova.feature_staking_impl.data.nominationPools.network.blockhain.models.unlockChunksFor -import io.novafoundation.nova.feature_staking_api.data.nominationPools.pool.PoolAccountDerivation -import io.novafoundation.nova.feature_staking_api.data.nominationPools.pool.bondedAccountOf import io.novafoundation.nova.feature_staking_impl.domain.common.StakingSharedComputation import io.novafoundation.nova.feature_staking_impl.domain.nominationPools.common.NominationPoolSharedComputation import io.novafoundation.nova.feature_staking_impl.domain.staking.redeem.RedeemConsequences @@ -71,7 +71,7 @@ class RealNominationPoolsRedeemInteractor( val chain = stakingSharedState.chain() val activeEra = stakingRepository.getActiveEraIndex(chain.id) - extrinsicService.submitExtrinsicWithSelectedWallet(chain) { + extrinsicService.submitExtrinsicWithSelectedWalletV2(chain) { redeem(poolMember) }.map { val totalAfterRedeem = poolMember.totalPointsAfterRedeemAt(activeEra) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/redeem/validations/Declarations.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/redeem/validations/Declarations.kt index 2de1f90256..c231994800 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/redeem/validations/Declarations.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/redeem/validations/Declarations.kt @@ -2,6 +2,7 @@ package io.novafoundation.nova.feature_staking_impl.domain.nominationPools.redee import io.novafoundation.nova.common.validation.ValidationSystem import io.novafoundation.nova.common.validation.ValidationSystemBuilder +import io.novafoundation.nova.feature_wallet_api.domain.model.balanceCountedTowardsED import io.novafoundation.nova.feature_wallet_api.domain.validation.EnoughTotalToStayAboveEDValidationFactory import io.novafoundation.nova.feature_wallet_api.domain.validation.sufficientBalance import io.novafoundation.nova.feature_wallet_api.domain.validation.validate @@ -37,7 +38,7 @@ private fun NominationPoolsRedeemValidationSystemBuilder.sufficientCommissionBal ) { enoughTotalToStayAboveEDValidationFactory.validate( fee = { it.fee }, - balance = { it.asset.total }, + balance = { it.asset.balanceCountedTowardsED() }, chainWithAsset = { ChainWithAsset(it.chain, it.chain.utilityAsset) }, error = { payload, _ -> NominationPoolsRedeemValidationFailure.ToStayAboveED(payload.chain.utilityAsset) } ) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/unbond/validations/Declarations.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/unbond/validations/Declarations.kt index 9f29ba5514..a35dc438b5 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/unbond/validations/Declarations.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/unbond/validations/Declarations.kt @@ -3,6 +3,7 @@ package io.novafoundation.nova.feature_staking_impl.domain.nominationPools.unbon import io.novafoundation.nova.common.validation.Validation import io.novafoundation.nova.common.validation.ValidationSystem import io.novafoundation.nova.common.validation.ValidationSystemBuilder +import io.novafoundation.nova.feature_wallet_api.domain.model.balanceCountedTowardsED import io.novafoundation.nova.feature_wallet_api.domain.validation.EnoughTotalToStayAboveEDValidationFactory import io.novafoundation.nova.feature_wallet_api.domain.validation.positiveAmount import io.novafoundation.nova.feature_wallet_api.domain.validation.sufficientBalance @@ -69,7 +70,7 @@ private fun NominationPoolsUnbondValidationSystemBuilder.sufficientCommissionBal ) { enoughTotalToStayAboveEDValidationFactory.validate( fee = { it.fee }, - balance = { it.asset.total }, + balance = { it.asset.balanceCountedTowardsED() }, chainWithAsset = { ChainWithAsset(it.chain, it.chain.utilityAsset) }, error = { payload, _ -> NominationPoolsUnbondValidationFailure.ToStayAboveED(payload.chain.utilityAsset) } ) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/recommendations/settings/RecommendationSettingsProvider.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/recommendations/settings/RecommendationSettingsProvider.kt index ce221e4b84..97ddf5f5cb 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/recommendations/settings/RecommendationSettingsProvider.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/recommendations/settings/RecommendationSettingsProvider.kt @@ -14,7 +14,6 @@ import kotlinx.coroutines.flow.MutableStateFlow class RecommendationSettingsProvider( maximumRewardedNominators: Int, private val runtimeSnapshot: RuntimeSnapshot, - val maximumValidatorsPerNominator: Int ) { private val alwaysEnabledFilters = runtimeSnapshot.availableDependents( @@ -58,7 +57,7 @@ class RecommendationSettingsProvider( fun currentSettings() = customSettingsFlow.value - fun defaultSettings(): RecommendationSettings { + fun defaultSettings(maximumValidatorsPerNominator: Int): RecommendationSettings { return RecommendationSettings( alwaysEnabledFilters = alwaysEnabledFilters, customEnabledFilters = customizableFilters, diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/recommendations/settings/RecommendationSettingsProviderFactory.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/recommendations/settings/RecommendationSettingsProviderFactory.kt index 622490034f..0a634b09f8 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/recommendations/settings/RecommendationSettingsProviderFactory.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/recommendations/settings/RecommendationSettingsProviderFactory.kt @@ -22,7 +22,6 @@ class RecommendationSettingsProviderFactory( RecommendationSettingsProvider( maximumRewardedNominators = stakingConstantsRepository.maxRewardedNominatorPerValidator(chainId), - maximumValidatorsPerNominator = stakingConstantsRepository.maxValidatorsPerNominator(chainId), runtimeSnapshot = chainRegistry.getRuntime(chainId) ) } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/redeem/RedeemInteractor.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/redeem/RedeemInteractor.kt index cd6ff8d507..bb5e513c05 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/redeem/RedeemInteractor.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/redeem/RedeemInteractor.kt @@ -1,5 +1,6 @@ package io.novafoundation.nova.feature_staking_impl.domain.staking.redeem +import io.novafoundation.nova.common.utils.isZero import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService import io.novafoundation.nova.feature_staking_api.domain.api.StakingRepository import io.novafoundation.nova.feature_staking_api.domain.model.numberOfSlashingSpans @@ -28,13 +29,15 @@ class RedeemInteractor( extrinsicService.submitExtrinsicWithAnySuitableWallet(stakingState.chain, stakingState.controllerId) { withdrawUnbonded(getSlashingSpansNumber(stakingState)) }.map { - RedeemConsequences( - willKillStash = asset.redeemable == asset.bonded - ) + RedeemConsequences(willKillStash = asset.isRedeemingAll()) } } } + private fun Asset.isRedeemingAll(): Boolean { + return bonded.isZero && unbonding.isZero + } + private suspend fun getSlashingSpansNumber(stakingState: StakingState.Stash): BigInteger { return stakingRepository.getSlashingSpan(stakingState.chain.id, stakingState.stashId).numberOfSlashingSpans() } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/common/NominationPoolsAvailableBalanceResolver.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/common/NominationPoolsAvailableBalanceResolver.kt index 4a51eb9b82..8106d4e91a 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/common/NominationPoolsAvailableBalanceResolver.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/common/NominationPoolsAvailableBalanceResolver.kt @@ -26,7 +26,7 @@ class RealNominationPoolsAvailableBalanceResolver( override suspend fun maximumBalanceToStake(asset: Asset, fee: Balance): MaxBalanceToStake { val existentialDeposit = walletConstants.existentialDeposit(asset.token.configuration.chainId) - val maxToStake = minOf(asset.transferableInPlanks, asset.totalInPlanks - existentialDeposit) - fee + val maxToStake = minOf(asset.transferableInPlanks, asset.balanceCountedTowardsEDInPlanks - existentialDeposit) - fee return MaxBalanceToStake(maxToStake.atLeastZero(), existentialDeposit) } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/common/selection/store/StartMultiStakingSelectionStore.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/common/selection/store/StartMultiStakingSelectionStore.kt index f1a02169d6..9405cbad65 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/common/selection/store/StartMultiStakingSelectionStore.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/common/selection/store/StartMultiStakingSelectionStore.kt @@ -1,38 +1,18 @@ package io.novafoundation.nova.feature_staking_impl.domain.staking.start.common.selection.store +import io.novafoundation.nova.common.utils.selectionStore.MutableSelectionStore import io.novafoundation.nova.feature_staking_api.domain.model.Validator import io.novafoundation.nova.feature_staking_impl.domain.nominationPools.model.NominationPool import io.novafoundation.nova.feature_staking_impl.domain.staking.start.common.selection.RecommendableMultiStakingSelection import io.novafoundation.nova.feature_staking_impl.domain.staking.start.setupAmount.direct.DirectStakingSelection import io.novafoundation.nova.feature_staking_impl.domain.staking.start.setupAmount.pools.NominationPoolSelection import java.math.BigInteger -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -interface StartMultiStakingSelectionStore { +class StartMultiStakingSelectionStore : MutableSelectionStore() { - val currentSelectionFlow: Flow - - val currentSelection: RecommendableMultiStakingSelection? - - fun updateSelection(multiStakingSelection: RecommendableMultiStakingSelection) - - fun updateStake(amount: BigInteger) -} - -class RealStartMultiStakingSelectionStore : StartMultiStakingSelectionStore { - - override val currentSelectionFlow = MutableStateFlow(null) - - override val currentSelection: RecommendableMultiStakingSelection? - get() = currentSelectionFlow.value - - override fun updateSelection(multiStakingSelection: RecommendableMultiStakingSelection) { - currentSelectionFlow.value = multiStakingSelection - } - - override fun updateStake(amount: BigInteger) { - currentSelection?.let { currentSelection -> + fun updateStake(amount: BigInteger) { + val currentSelection = getCurrentSelection() + currentSelection?.let { currentSelectionFlow.value = currentSelection.copy( selection = currentSelection.selection.copyWith(amount) ) @@ -41,7 +21,7 @@ class RealStartMultiStakingSelectionStore : StartMultiStakingSelectionStore { } fun StartMultiStakingSelectionStore.getValidatorsOrEmpty(): List { - val selection = currentSelection?.selection + val selection = getCurrentSelection()?.selection return if (selection is DirectStakingSelection) { selection.validators } else { @@ -50,7 +30,7 @@ fun StartMultiStakingSelectionStore.getValidatorsOrEmpty(): List { } fun StartMultiStakingSelectionStore.getPoolOrNull(): NominationPool? { - val selection = currentSelection?.selection + val selection = getCurrentSelection()?.selection return if (selection is NominationPoolSelection) { selection.pool } else { diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/common/selection/store/StartMultiStakingSelectionStoreProvider.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/common/selection/store/StartMultiStakingSelectionStoreProvider.kt index e7d952e90c..c3f4312e17 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/common/selection/store/StartMultiStakingSelectionStoreProvider.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/common/selection/store/StartMultiStakingSelectionStoreProvider.kt @@ -2,29 +2,23 @@ package io.novafoundation.nova.feature_staking_impl.domain.staking.start.common. import io.novafoundation.nova.common.data.memory.ComputationalCache import io.novafoundation.nova.common.utils.flowOfAll +import io.novafoundation.nova.common.utils.selectionStore.ComputationalCacheSelectionStoreProvider import io.novafoundation.nova.feature_staking_impl.domain.staking.start.common.selection.RecommendableMultiStakingSelection import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow -interface StartMultiStakingSelectionStoreProvider { - - suspend fun getSelectionStore(scope: CoroutineScope): StartMultiStakingSelectionStore -} - fun StartMultiStakingSelectionStoreProvider.currentSelectionFlow(scope: CoroutineScope): Flow { return flowOfAll { getSelectionStore(scope).currentSelectionFlow } } -class RealStartMultiStakingSelectionStoreProvider( - private val computationalCache: ComputationalCache, - private val key: String -) : StartMultiStakingSelectionStoreProvider { +class StartMultiStakingSelectionStoreProvider( + computationalCache: ComputationalCache, + key: String +) : ComputationalCacheSelectionStoreProvider(computationalCache, key) { - override suspend fun getSelectionStore(scope: CoroutineScope): StartMultiStakingSelectionStore { - return computationalCache.useCache(key, scope) { - RealStartMultiStakingSelectionStore() - } + protected override fun initSelectionStore(): StartMultiStakingSelectionStore { + return StartMultiStakingSelectionStore() } } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/setupAmount/direct/DirectStakingProperties.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/setupAmount/direct/DirectStakingProperties.kt index 7f3b92ad80..0ec0861cd7 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/setupAmount/direct/DirectStakingProperties.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/setupAmount/direct/DirectStakingProperties.kt @@ -5,6 +5,7 @@ import io.novafoundation.nova.feature_staking_api.domain.api.StakingRepository import io.novafoundation.nova.feature_staking_impl.data.StakingOption import io.novafoundation.nova.feature_staking_impl.data.asset import io.novafoundation.nova.feature_staking_impl.data.chain +import io.novafoundation.nova.feature_staking_impl.data.repository.StakingConstantsRepository import io.novafoundation.nova.feature_staking_impl.data.stakingType import io.novafoundation.nova.feature_staking_impl.domain.common.StakingSharedComputation import io.novafoundation.nova.feature_staking_impl.domain.common.minStake @@ -31,6 +32,7 @@ class DirectStakingPropertiesFactory( private val recommendationSettingsProviderFactory: RecommendationSettingsProviderFactory, private val stakingSharedComputation: StakingSharedComputation, private val stakingRepository: StakingRepository, + private val stakingConstantsRepository: StakingConstantsRepository, ) : SingleStakingPropertiesFactory { override fun createProperties(scope: CoroutineScope, stakingOption: StakingOption): SingleStakingProperties { @@ -40,7 +42,8 @@ class DirectStakingPropertiesFactory( stakingOption = stakingOption, scope = scope, stakingSharedComputation = stakingSharedComputation, - stakingRepository = stakingRepository + stakingRepository = stakingRepository, + stakingConstantsRepository = stakingConstantsRepository ) } } @@ -52,6 +55,7 @@ private class DirectStakingProperties( private val scope: CoroutineScope, private val stakingSharedComputation: StakingSharedComputation, private val stakingRepository: StakingRepository, + private val stakingConstantsRepository: StakingConstantsRepository, ) : SingleStakingProperties { override val stakingType: Chain.Asset.StakingType = stakingOption.stakingType @@ -65,6 +69,7 @@ private class DirectStakingProperties( } override val recommendation: SingleStakingRecommendation = DirectStakingRecommendation( + stakingConstantsRepository = stakingConstantsRepository, validatorRecommenderFactory = validatorRecommenderFactory, recommendationSettingsProviderFactory = recommendationSettingsProviderFactory, stakingOption = stakingOption, @@ -106,14 +111,14 @@ private class DirectStakingProperties( private fun StartMultiStakingValidationSystemBuilder.enoughAvailableToStake() { sufficientBalance( - fee = { it.fee.decimalAmount }, + fee = { it.fee.networkFeeDecimalAmount }, available = { it.amountOf(availableBalance(it.asset)) }, amount = { it.amountOf(it.selection.stake) }, error = { payload, availableToPayFees -> StartMultiStakingValidationFailure.NotEnoughToPayFees( chainAsset = payload.asset.token.configuration, maxUsable = availableToPayFees, - fee = payload.fee.decimalAmount + fee = payload.fee.networkFeeDecimalAmount ) } ) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/setupAmount/direct/DirectStakingRecommendation.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/setupAmount/direct/DirectStakingRecommendation.kt index 95749df966..9b63c831b1 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/setupAmount/direct/DirectStakingRecommendation.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/setupAmount/direct/DirectStakingRecommendation.kt @@ -1,6 +1,8 @@ package io.novafoundation.nova.feature_staking_impl.domain.staking.start.setupAmount.direct import io.novafoundation.nova.feature_staking_impl.data.StakingOption +import io.novafoundation.nova.feature_staking_impl.data.chain +import io.novafoundation.nova.feature_staking_impl.data.repository.StakingConstantsRepository import io.novafoundation.nova.feature_staking_impl.domain.recommendations.ValidatorRecommenderFactory import io.novafoundation.nova.feature_staking_impl.domain.recommendations.settings.RecommendationSettingsProviderFactory import io.novafoundation.nova.feature_staking_impl.domain.staking.start.common.selection.StartMultiStakingSelection @@ -12,6 +14,7 @@ import kotlinx.coroutines.async class DirectStakingRecommendation( private val validatorRecommenderFactory: ValidatorRecommenderFactory, private val recommendationSettingsProviderFactory: RecommendationSettingsProviderFactory, + private val stakingConstantsRepository: StakingConstantsRepository, private val stakingOption: StakingOption, private val scope: CoroutineScope ) : SingleStakingRecommendation { @@ -26,14 +29,15 @@ class DirectStakingRecommendation( override suspend fun recommendedSelection(stake: Balance): StartMultiStakingSelection { val provider = recommendationSettingsProvider.await() - val recommendationSettings = provider.defaultSettings() + val maximumValidatorsPerNominator = stakingConstantsRepository.maxValidatorsPerNominator(stakingOption.chain.id, stake) + val recommendationSettings = provider.defaultSettings(maximumValidatorsPerNominator) val recommendator = recommendator.await() val recommendedValidators = recommendator.recommendations(recommendationSettings) return DirectStakingSelection( validators = recommendedValidators, - validatorsLimit = provider.maximumValidatorsPerNominator, + validatorsLimit = maximumValidatorsPerNominator, stakingOption = stakingOption, stake = stake ) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/setupStakingType/SetupStakingTypeSelectionMixin.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/setupStakingType/SetupStakingTypeSelectionMixin.kt index 4121fc36a2..41a1f188d9 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/setupStakingType/SetupStakingTypeSelectionMixin.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/setupStakingType/SetupStakingTypeSelectionMixin.kt @@ -46,7 +46,7 @@ class SetupStakingTypeSelectionMixin( .filterNotNull() suspend fun apply() { - val recommendableSelection = editableSelectionStoreProvider.getSelectionStore(scope).currentSelection ?: return + val recommendableSelection = editableSelectionStoreProvider.getSelectionStore(scope).getCurrentSelection() ?: return currentSelectionStoreProvider.getSelectionStore(scope) .updateSelection(recommendableSelection) } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/setupStakingType/pool/RealStakingTypeDetailsProvider.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/setupStakingType/pool/RealStakingTypeDetailsProvider.kt index b004659c5f..5059a1eca1 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/setupStakingType/pool/RealStakingTypeDetailsProvider.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/setupStakingType/pool/RealStakingTypeDetailsProvider.kt @@ -76,7 +76,7 @@ class RealStakingTypeDetailsProvider( override suspend fun getValidationPayload(): EditingStakingTypePayload? { val selectionStore = currentSelectionStoreProvider.getSelectionStore(coroutineScope) - val selectedStake = selectionStore.currentSelection?.selection?.stake ?: return null + val selectedStake = selectionStore.getCurrentSelection()?.selection?.stake ?: return null return EditingStakingTypePayload(selectedStake, stakingType, singleStakingProperties.minStake()) } } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validators/current/CurrentValidatorsInteractor.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validators/current/CurrentValidatorsInteractor.kt index 783acfecfc..7ac96bc1db 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validators/current/CurrentValidatorsInteractor.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validators/current/CurrentValidatorsInteractor.kt @@ -19,6 +19,7 @@ import io.novafoundation.nova.feature_staking_impl.domain.validations.controller import io.novafoundation.nova.feature_staking_impl.domain.validators.ValidatorProvider import io.novafoundation.nova.feature_staking_impl.domain.validators.ValidatorSource import io.novafoundation.nova.feature_staking_impl.domain.validators.getValidators +import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance import io.novafoundation.nova.runtime.state.selectedOption import jp.co.soramitsu.fearless_utils.extensions.toHexString import kotlinx.coroutines.CoroutineScope @@ -38,6 +39,7 @@ class CurrentValidatorsInteractor( suspend fun nominatedValidatorsFlow( nominatorState: StakingState.Stash, + activeStake: Balance, scope: CoroutineScope, ): Flow> { if (nominatorState !is StakingState.Stash.Nominator) { @@ -97,7 +99,7 @@ class CurrentValidatorsInteractor( val electedGroup = Status.Group.Active(totalElectiveCount) val waitingForNextEraGroup = Status.Group.WaitingForNextEra( - maxValidatorsPerNominator = stakingConstantsRepository.maxValidatorsPerNominator(chainId), + maxValidatorsPerNominator = stakingConstantsRepository.maxValidatorsPerNominator(chainId, activeStake), numberOfValidators = groupedByStatusClass.groupSize(Status.WaitingForNextEra::class) ) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/common/SetupStakingSharedState.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/common/SetupStakingSharedState.kt index dad440d2e3..e0b1dea75c 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/common/SetupStakingSharedState.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/common/SetupStakingSharedState.kt @@ -2,25 +2,24 @@ package io.novafoundation.nova.feature_staking_impl.presentation.common import android.util.Log import io.novafoundation.nova.feature_staking_api.domain.model.Validator +import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance import kotlinx.coroutines.flow.MutableStateFlow sealed class SetupStakingProcess { object Initial : SetupStakingProcess() { - fun changeValidatorsFlow() = ChoosingValidators - } - - object ChoosingValidators : SetupStakingProcess() { - - fun previous() = Initial - - fun next(validators: List, selectionMethod: ReadyToSubmit.SelectionMethod): SetupStakingProcess { - return ReadyToSubmit(validators, selectionMethod) + fun next( + activeStake: Balance, + validators: List, + selectionMethod: ReadyToSubmit.SelectionMethod + ): SetupStakingProcess { + return ReadyToSubmit(activeStake, validators, selectionMethod) } } class ReadyToSubmit( + val activeStake: Balance, val validators: List, val selectionMethod: SelectionMethod ) : SetupStakingProcess() { @@ -32,13 +31,13 @@ sealed class SetupStakingProcess { fun changeValidators( newValidators: List, selectionMethod: SelectionMethod - ) = ReadyToSubmit(newValidators, selectionMethod) + ) = ReadyToSubmit(activeStake, newValidators, selectionMethod) - fun previous(): ChoosingValidators { - return ChoosingValidators + fun previous(): Initial { + return Initial } - fun finish() = Initial + fun reset() = Initial } } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/common/currentStakeTargets/model/SelectedStakeTargetStatusModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/common/currentStakeTargets/model/SelectedStakeTargetStatusModel.kt index d4439dc90b..96783b79e0 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/common/currentStakeTargets/model/SelectedStakeTargetStatusModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/common/currentStakeTargets/model/SelectedStakeTargetStatusModel.kt @@ -53,8 +53,8 @@ fun SelectedStakeTargetStatusModel.Companion.Inactive( SelectedStakeTargetStatusModel.TitleConfig( text = resourceManager.getString(R.string.staking_your_not_elected_format, groupSize), iconRes = R.drawable.ic_time_16, - iconTintRes = R.color.text_tertiary, - textColorRes = R.color.text_tertiary, + iconTintRes = R.color.text_secondary, + textColorRes = R.color.text_secondary, ), description = resourceManager.getString(description) ) @@ -75,8 +75,8 @@ fun SelectedStakeTargetStatusModel.Companion.Waiting( SelectedStakeTargetStatusModel.TitleConfig( text = title, iconRes = R.drawable.ic_time_16, - iconTintRes = R.color.text_tertiary, - textColorRes = R.color.text_tertiary, + iconTintRes = R.color.text_secondary, + textColorRes = R.color.text_secondary, ), description = resourceManager.getString(description) ) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/common/selectStakeTarget/ChooseStakedStakeTargetsBottomSheet.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/common/selectStakeTarget/ChooseStakedStakeTargetsBottomSheet.kt index d4203ac3f1..3185e66f18 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/common/selectStakeTarget/ChooseStakedStakeTargetsBottomSheet.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/common/selectStakeTarget/ChooseStakedStakeTargetsBottomSheet.kt @@ -104,7 +104,7 @@ private class ViewHolder( itemSelectStakedCollatorCollator.bindSelectedCollator(item) itemSelectStakedCollatorCheck.isChecked = isSelected - val primaryTextColor = if (item.active) R.color.text_primary else R.color.text_tertiary + val primaryTextColor = if (item.active) R.color.text_primary else R.color.text_secondary with(itemSelectStakedCollatorCollator) { itemStakingTargetName.setTextColorRes(primaryTextColor) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/parachainStaking/common/selectCollators/Mappers.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/parachainStaking/common/selectCollators/Mappers.kt index 0cf6f5977d..698ff13827 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/parachainStaking/common/selectCollators/Mappers.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/parachainStaking/common/selectCollators/Mappers.kt @@ -109,12 +109,12 @@ fun ResourceManager.labeledAmountSubtitle( return if (selectionActive) { buildSpannable(this) { - appendColored(labelText, R.color.text_tertiary) + appendColored(labelText, R.color.text_secondary) appendColored(amount.token, R.color.text_primary) } } else { buildSpannable(this) { - appendColored(labelText + amount.token, R.color.text_tertiary) + appendColored(labelText + amount.token, R.color.text_secondary) } } } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/parachainStaking/yieldBoost/setup/SetupYieldBoostViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/parachainStaking/yieldBoost/setup/SetupYieldBoostViewModel.kt index 146af5bfb7..00b82be1f4 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/parachainStaking/yieldBoost/setup/SetupYieldBoostViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/parachainStaking/yieldBoost/setup/SetupYieldBoostViewModel.kt @@ -416,7 +416,7 @@ class SetupYieldBoostViewModel( if (hasActiveYieldBoost) { appendColored(", ", R.color.text_positive) - appendColored(resourceManager.getString(R.string.yiled_boost_yield_boosted), R.color.text_tertiary) + appendColored(resourceManager.getString(R.string.yiled_boost_yield_boosted), R.color.text_secondary) } } } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/payouts/list/PayoutsListViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/payouts/list/PayoutsListViewModel.kt index dc858dcc64..e9134a2097 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/payouts/list/PayoutsListViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/payouts/list/PayoutsListViewModel.kt @@ -121,7 +121,7 @@ class PayoutsListViewModel( validatorTitle = validatorInfo.identityName ?: validatorInfo.address, timeLeft = timeLeft, createdAt = timeLeftCalculatedAt, - daysLeftColor = if (closeToExpire) R.color.text_negative else R.color.text_tertiary, + daysLeftColor = if (closeToExpire) R.color.text_negative else R.color.text_secondary, amount = amount.formatTokenChange(token.configuration, isIncome = true), amountFiat = token.amountToFiat(amount).formatAsCurrency(token.currency) ) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/main/components/stakeSummary/StakeSummaryView.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/main/components/stakeSummary/StakeSummaryView.kt index f642b71a26..52a54f057b 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/main/components/stakeSummary/StakeSummaryView.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/main/components/stakeSummary/StakeSummaryView.kt @@ -39,7 +39,7 @@ class StakeSummaryView @JvmOverloads constructor( class Waiting( val timeLeft: Long, @StringRes customMessageFormat: Int - ) : Status(customMessageFormat, R.color.text_tertiary, R.drawable.ic_indicator_inactive_pulse) + ) : Status(customMessageFormat, R.color.text_secondary, R.drawable.ic_indicator_inactive_pulse) } init { diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/main/components/unbonding/UnbondingsAdapter.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/main/components/unbonding/UnbondingsAdapter.kt index e91dfb254e..5275e403f1 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/main/components/unbonding/UnbondingsAdapter.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/main/components/unbonding/UnbondingsAdapter.kt @@ -67,7 +67,7 @@ class UnbondingsHolder(override val containerView: View) : RecyclerView.ViewHold itemUnbondStatus.setText(R.string.wallet_balance_redeemable) } is Unbonding.Status.Unbonding -> { - itemUnbondStatus.setTextColorRes(R.color.text_tertiary) + itemUnbondStatus.setTextColorRes(R.color.text_secondary) itemUnbondStatus.setDrawableEnd(R.drawable.ic_time_16, paddingInDp = 4, tint = R.color.icon_secondary) itemUnbondStatus.startTimer(status.timer) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/start/common/MultiStakingTargetSelectionFormatter.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/start/common/MultiStakingTargetSelectionFormatter.kt index 6a713c2177..2fd9cbd2ef 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/start/common/MultiStakingTargetSelectionFormatter.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/start/common/MultiStakingTargetSelectionFormatter.kt @@ -93,7 +93,7 @@ class RealMultiStakingTargetSelectionFormatter( notRecommendedText()?.let { ColoredText( text = it, - colorRes = R.color.text_tertiary + colorRes = R.color.text_secondary ) } } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/start/confirm/types/DirectConfirmMultiStakingType.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/start/confirm/types/DirectConfirmMultiStakingType.kt index 7ab85f57fb..a2c833da62 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/start/confirm/types/DirectConfirmMultiStakingType.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/start/confirm/types/DirectConfirmMultiStakingType.kt @@ -34,7 +34,8 @@ class DirectConfirmMultiStakingType( // act as an adapter between new flow and legacy logic val reviewValidatorsState = ReadyToSubmit( validators = selection.validators, - selectionMethod = ReadyToSubmit.SelectionMethod.RECOMMENDED + selectionMethod = ReadyToSubmit.SelectionMethod.RECOMMENDED, + activeStake = selection.stake, ) setupStakingSharedState.set(reviewValidatorsState) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/start/landing/StartStakingLandingFooterAdapter.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/start/landing/StartStakingLandingFooterAdapter.kt index 1346c9d6e3..c8545c2e21 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/start/landing/StartStakingLandingFooterAdapter.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/start/landing/StartStakingLandingFooterAdapter.kt @@ -11,6 +11,7 @@ import io.novafoundation.nova.common.utils.clickableSpan import io.novafoundation.nova.common.utils.colorSpan import io.novafoundation.nova.common.utils.dp import io.novafoundation.nova.common.utils.drawableSpan +import io.novafoundation.nova.common.utils.fontSpan import io.novafoundation.nova.common.utils.inflateChild import io.novafoundation.nova.common.utils.setEndSpan import io.novafoundation.nova.common.utils.setFullSpan @@ -49,7 +50,7 @@ class StartStakingLandingFooterViewHolder( init { with(itemView) { val iconColor = context.getColor(R.color.chip_icon) - val clickablePartColor = context.getColor(R.color.text_secondary) + val clickablePartColor = context.getColor(R.color.link_text) val chevronSize = 20.dp(context) val chevronRight = ContextCompat.getDrawable(context, R.drawable.ic_chevron_right) ?.apply { @@ -59,6 +60,7 @@ class StartStakingLandingFooterViewHolder( val termsClickablePart = resources.getText(R.string.start_staking_fragment_terms_of_use_clicable_part) .toSpannable(colorSpan(clickablePartColor)) .setFullSpan(clickableSpan { clickHandler.onTermsOfUseClicked() }) + .setFullSpan(fontSpan(context, R.font.public_sans_semi_bold)) .setEndSpan(drawableSpan(chevronRight!!)) itemStakingLandingFooterTermsOfUse.text = SpannableFormatter.format( diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/start/landing/StartStakingLandingViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/start/landing/StartStakingLandingViewModel.kt index b4816cf661..82c49ef7a2 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/start/landing/StartStakingLandingViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/start/landing/StartStakingLandingViewModel.kt @@ -20,6 +20,7 @@ import io.novafoundation.nova.common.utils.clickableSpan import io.novafoundation.nova.common.utils.colorSpan import io.novafoundation.nova.common.utils.drawableSpan import io.novafoundation.nova.common.utils.flowOf +import io.novafoundation.nova.common.utils.fontSpan import io.novafoundation.nova.common.utils.formatAsSpannable import io.novafoundation.nova.common.utils.formatting.duration.BoundedDurationFormatter import io.novafoundation.nova.common.utils.formatting.duration.DayAndHourDurationFormatter @@ -59,6 +60,8 @@ import io.novafoundation.nova.feature_wallet_api.presentation.formatters.formatP import io.novafoundation.nova.feature_wallet_api.presentation.model.mapAmountToAmountModel import io.novafoundation.nova.runtime.ext.StakingTypeGroup import io.novafoundation.nova.runtime.ext.group +import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry +import io.novafoundation.nova.runtime.multiNetwork.asset import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine @@ -91,6 +94,7 @@ class StartStakingLandingViewModel( private val selectedMetaAccountUseCase: SelectedAccountUseCase, private val actionAwaitableMixinFactory: ActionAwaitableMixin.Factory, private val stakingStartedDetectionService: StakingStartedDetectionService, + private val chainRegistry: ChainRegistry, private val contextManager: ContextManager ) : BaseViewModel(), Browserable, @@ -147,9 +151,7 @@ class StartStakingLandingViewModel( override val openBrowserEvent = MutableLiveData>() init { - updateSystemFactory.create(availableStakingOptionsPayload.chainId, availableStakingOptionsPayload.stakingTypes) - .start() - .launchIn(this) + launchSync() closeOnStakingStarted() } @@ -189,6 +191,16 @@ class StartStakingLandingViewModel( openBrowserEvent.value = Event(appLinksProvider.termsUrl) } + private fun launchSync() { + launch { + // Start syncing for all staking type since we need to show it on select staking type screen + val asset = chainRegistry.asset(availableStakingOptionsPayload.chainId, availableStakingOptionsPayload.assetId) + updateSystemFactory.create(availableStakingOptionsPayload.chainId, asset.staking) + .start() + .launchIn(this) + } + } + private fun closeOnStakingStarted() = launch { val stakingStartedChain = stakingStartedDetectionService.awaitStakingStarted( stakingOptionIds = stakingOptionIds, @@ -226,7 +238,7 @@ class StartStakingLandingViewModel( private fun createMoreInfoText(chain: Chain): CharSequence { val iconColor = resourceManager.getColor(R.color.chip_icon) - val clickableTextColor = resourceManager.getColor(R.color.text_secondary) + val clickableTextColor = resourceManager.getColor(R.color.link_text) val chevronSize = resourceManager.measureInPx(20) val chevronRight = resourceManager.getDrawable(R.drawable.ic_chevron_right).apply { setBounds(0, 0, chevronSize, chevronSize) @@ -235,6 +247,7 @@ class StartStakingLandingViewModel( val clickablePart = resourceManager.getString(R.string.start_staking_fragment_more_info_clicable_part) .toSpannable(colorSpan(clickableTextColor)) .setFullSpan(clickableSpan { novaWikiClicked(chain.additional?.stakingWiki) }) + .setFullSpan(fontSpan(resourceManager, R.font.public_sans_semi_bold)) .setEndSpan(drawableSpan(chevronRight)) return SpannableFormatter.format( diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/start/landing/di/StartStakingLandingModule.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/start/landing/di/StartStakingLandingModule.kt index 81605a7ec6..3f75272543 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/start/landing/di/StartStakingLandingModule.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/start/landing/di/StartStakingLandingModule.kt @@ -54,6 +54,7 @@ class StartStakingLandingModule { selectedMetaAccountUseCase: SelectedAccountUseCase, actionAwaitableMixinFactory: ActionAwaitableMixin.Factory, stakingStartedDetectionService: StakingStartedDetectionService, + chainRegistry: ChainRegistry, contextManager: ContextManager ): ViewModel { return StartStakingLandingViewModel( @@ -67,6 +68,7 @@ class StartStakingLandingModule { selectedMetaAccountUseCase = selectedMetaAccountUseCase, actionAwaitableMixinFactory = actionAwaitableMixinFactory, stakingStartedDetectionService = stakingStartedDetectionService, + chainRegistry = chainRegistry, contextManager = contextManager ) } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/start/setupAmount/SetupAmountMultiStakingViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/start/setupAmount/SetupAmountMultiStakingViewModel.kt index c3cd315d6a..fdaedd4bb7 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/start/setupAmount/SetupAmountMultiStakingViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/start/setupAmount/SetupAmountMultiStakingViewModel.kt @@ -5,8 +5,8 @@ import io.novafoundation.nova.common.base.BaseViewModel import io.novafoundation.nova.common.mixin.api.Validatable import io.novafoundation.nova.common.presentation.DescriptiveButtonState import io.novafoundation.nova.common.resources.ResourceManager +import io.novafoundation.nova.common.utils.combineToPair import io.novafoundation.nova.common.utils.formatting.format -import io.novafoundation.nova.common.utils.inBackground import io.novafoundation.nova.common.utils.orZero import io.novafoundation.nova.common.validation.ValidationExecutor import io.novafoundation.nova.common.validation.progressConsumer @@ -32,13 +32,14 @@ import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.awaitDec import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.connectWithV2 import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.create import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.mapFeeToParcel +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import kotlin.time.Duration.Companion.milliseconds @@ -199,17 +200,17 @@ class SetupAmountMultiStakingViewModel( } private fun runSelectionUpdates() { - combine( - multiStakingSelectionTypeFlow, - amountChooserMixin.amountInput - ) { multiStakingSelectionType, amountInput -> - val amount = amountInput.toBigDecimalOrNull() ?: return@combine - val asset = currentAssetFlow.first() - val planks = asset.token.planksFromAmount(amount) - - multiStakingSelectionType.updateSelectionFor(planks) + launch(Dispatchers.Default) { + combineToPair( + multiStakingSelectionTypeFlow, + amountChooserMixin.amountState + ).collectLatest { (multiStakingSelectionType, amountInput) -> + val amount = amountInput.value ?: return@collectLatest + val asset = currentAssetFlow.first() + val planks = asset.token.planksFromAmount(amount) + + multiStakingSelectionType.updateSelectionFor(planks) + } } - .inBackground() - .launchIn(viewModelScope) } } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/start/setupStakingType/EditableStakingTypeItemFormatter.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/start/setupStakingType/EditableStakingTypeItemFormatter.kt index f7d2a98406..3308067679 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/start/setupStakingType/EditableStakingTypeItemFormatter.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/start/setupStakingType/EditableStakingTypeItemFormatter.kt @@ -34,9 +34,11 @@ class EditableStakingTypeItemFormatter( else -> return null } + val isSelected = selectedStakingType == stakingType + return EditableStakingTypeRVItem( - isSelected = selectedStakingType == stakingType, - isSelectable = validatedStakingType.isAvailable, + isSelected = isSelected, + isSelectable = validatedStakingType.isAvailable || isSelected, title = resourceManager.getString(titleRes), imageRes = imageRes, conditions = mapConditions(asset, validatedStakingType.stakingTypeDetails), diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/start/setupStakingType/SetupStakingTypeFlowExecutor.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/start/setupStakingType/SetupStakingTypeFlowExecutor.kt index 2f6e248309..46e88457fc 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/start/setupStakingType/SetupStakingTypeFlowExecutor.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/start/setupStakingType/SetupStakingTypeFlowExecutor.kt @@ -1,5 +1,6 @@ package io.novafoundation.nova.feature_staking_impl.presentation.staking.start.setupStakingType +import io.novafoundation.nova.common.utils.orZero import io.novafoundation.nova.feature_staking_impl.domain.staking.start.common.selection.store.StartMultiStakingSelectionStoreProvider import io.novafoundation.nova.feature_staking_impl.domain.staking.start.common.selection.store.getValidatorsOrEmpty import io.novafoundation.nova.feature_staking_impl.presentation.StakingRouter @@ -53,8 +54,9 @@ class SetupDirectStakingFlowExecutor( val selectionStore = editableSelectionStoreProvider.getSelectionStore(coroutineScope) setupStakingSharedState.set( SetupStakingProcess.ReadyToSubmit( - selectionStore.getValidatorsOrEmpty(), - SetupStakingProcess.ReadyToSubmit.SelectionMethod.CUSTOM + activeStake = selectionStore.getCurrentSelection()?.selection?.stake.orZero(), + validators = selectionStore.getValidatorsOrEmpty(), + selectionMethod = SetupStakingProcess.ReadyToSubmit.SelectionMethod.CUSTOM ) ) router.openSelectCustomValidators() diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/start/setupStakingType/SetupStakingTypeViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/start/setupStakingType/SetupStakingTypeViewModel.kt index 12a8246661..dd939a2a15 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/start/setupStakingType/SetupStakingTypeViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/start/setupStakingType/SetupStakingTypeViewModel.kt @@ -199,7 +199,7 @@ class SetupStakingTypeViewModel( private suspend fun getEnteredAmount(): BigInteger? { return currentSelectionStoreProvider.getSelectionStore(viewModelScope) - .currentSelection + .getCurrentSelection() ?.selection ?.stake } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/StakeTargetAdapter.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/StakeTargetAdapter.kt index 3bad25afa8..78253b0f46 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/StakeTargetAdapter.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/StakeTargetAdapter.kt @@ -145,7 +145,7 @@ class StakingTargetViewHolder(override val containerView: View) : RecyclerVie } is StakeTargetModel.Scoring.OneField -> { - itemStakingTargetScoringPrimary.setTextColorRes(R.color.text_tertiary) + itemStakingTargetScoringPrimary.setTextColorRes(R.color.text_secondary) itemStakingTargetScoringPrimary.makeVisible() itemStakingTargetScoringSecondary.makeGone() itemStakingTargetScoringPrimary.setColoredText(scoring.field) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/StateTransitions.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/StateTransitions.kt index 341986acdf..8b6ae434b2 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/StateTransitions.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/StateTransitions.kt @@ -4,11 +4,11 @@ import io.novafoundation.nova.feature_staking_api.domain.model.Validator import io.novafoundation.nova.feature_staking_impl.presentation.common.SetupStakingProcess import io.novafoundation.nova.feature_staking_impl.presentation.common.SetupStakingProcess.ReadyToSubmit.SelectionMethod import io.novafoundation.nova.feature_staking_impl.presentation.common.SetupStakingSharedState +import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance -fun SetupStakingSharedState.retractValidators() = mutate { +fun SetupStakingSharedState.reset() = mutate { when (it) { - is SetupStakingProcess.ReadyToSubmit -> it.previous().previous() - is SetupStakingProcess.ChoosingValidators -> it.previous() + is SetupStakingProcess.ReadyToSubmit -> it.reset() else -> throw IllegalArgumentException("Cannot retract validators from $it state") } } @@ -21,9 +21,15 @@ fun SetupStakingSharedState.setRecommendedValidators( validators: List ) = setValidators(validators, SelectionMethod.RECOMMENDED) +fun SetupStakingSharedState.activeStake(): Balance { + return when (val state = setupStakingProcess.value) { + is SetupStakingProcess.ReadyToSubmit -> state.activeStake + else -> throw IllegalArgumentException("Cannot get active stake from $state state") + } +} + fun SetupStakingSharedState.getSelectedValidators(): List { return when (val process = setupStakingProcess.value) { - is SetupStakingProcess.ChoosingValidators -> emptyList() is SetupStakingProcess.ReadyToSubmit -> process.validators else -> throw IllegalArgumentException("Cannot get validators from $process state") } @@ -34,7 +40,6 @@ private fun SetupStakingSharedState.setValidators( selectionMethod: SelectionMethod ) = mutate { when (it) { - is SetupStakingProcess.ChoosingValidators -> it.next(validators, selectionMethod) is SetupStakingProcess.ReadyToSubmit -> it.changeValidators(validators, selectionMethod) else -> throw IllegalArgumentException("Cannot set validators from $it state") } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/confirm/ConfirmChangeValidatorsViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/confirm/ConfirmChangeValidatorsViewModel.kt index 4cb9f1ffc1..f5bfabcad3 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/confirm/ConfirmChangeValidatorsViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/confirm/ConfirmChangeValidatorsViewModel.kt @@ -12,6 +12,8 @@ import io.novafoundation.nova.common.mixin.api.Validatable import io.novafoundation.nova.common.resources.ResourceManager import io.novafoundation.nova.common.utils.flowOf import io.novafoundation.nova.common.utils.inBackground +import io.novafoundation.nova.common.utils.invoke +import io.novafoundation.nova.common.utils.lazyAsync import io.novafoundation.nova.common.utils.requireException import io.novafoundation.nova.common.validation.ValidationExecutor import io.novafoundation.nova.common.validation.ValidationSystem @@ -29,6 +31,7 @@ import io.novafoundation.nova.feature_staking_impl.presentation.StakingRouter import io.novafoundation.nova.feature_staking_impl.presentation.common.SetupStakingProcess import io.novafoundation.nova.feature_staking_impl.presentation.common.SetupStakingSharedState import io.novafoundation.nova.feature_staking_impl.presentation.common.validation.stakingValidationFailure +import io.novafoundation.nova.feature_staking_impl.presentation.validators.change.activeStake import io.novafoundation.nova.feature_staking_impl.presentation.validators.change.confirm.hints.ConfirmStakeHintsMixinFactory import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeLoaderMixin import io.novafoundation.nova.runtime.state.AnySelectedAssetOptionSharedState @@ -60,6 +63,10 @@ class ConfirmChangeValidatorsViewModel( FeeLoaderMixin by feeLoaderMixin, ExternalActions by externalActions { + private val maxValidatorsPerNominator by lazyAsync { + interactor.maxValidatorsPerNominator(setupStakingSharedState.activeStake()) + } + private val currentProcessState = setupStakingSharedState.get() val hintsMixin = hintsMixinFactory.create(coroutineScope = this) @@ -88,7 +95,7 @@ class ConfirmChangeValidatorsViewModel( val nominationsFlow = flowOf { val selectedCount = currentProcessState.validators.size - val maxValidatorsPerNominator = interactor.maxValidatorsPerNominator() + val maxValidatorsPerNominator = maxValidatorsPerNominator() resourceManager.getString(R.string.staking_confirm_nominations, selectedCount, maxValidatorsPerNominator) } @@ -157,7 +164,7 @@ class ConfirmChangeValidatorsViewModel( if (setupResult.isSuccess) { showMessage(resourceManager.getString(R.string.common_transaction_submitted)) - setupStakingSharedState.set(currentProcessState.finish()) + setupStakingSharedState.set(currentProcessState.reset()) router.returnToCurrentValidators() } else { diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/custom/review/ReviewCustomValidatorsViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/custom/review/ReviewCustomValidatorsViewModel.kt index d789492a51..62b56801b3 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/custom/review/ReviewCustomValidatorsViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/custom/review/ReviewCustomValidatorsViewModel.kt @@ -52,7 +52,8 @@ class ReviewCustomValidatorsViewModel( .share() private val maxValidatorsPerNominatorFlow = flowOf { - interactor.maxValidatorsPerNominator() + val activeStake = confirmSetupState.first().activeStake + interactor.maxValidatorsPerNominator(activeStake) }.share() val selectionStateFlow = combine( diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/custom/select/SelectCustomValidatorsViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/custom/select/SelectCustomValidatorsViewModel.kt index 67adf552a9..3c6eac6312 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/custom/select/SelectCustomValidatorsViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/custom/select/SelectCustomValidatorsViewModel.kt @@ -26,6 +26,7 @@ import io.novafoundation.nova.feature_staking_impl.presentation.common.SetupStak import io.novafoundation.nova.feature_staking_impl.presentation.mappers.mapValidatorToValidatorDetailsParcelModel import io.novafoundation.nova.feature_staking_impl.presentation.mappers.mapValidatorToValidatorModel import io.novafoundation.nova.feature_staking_impl.presentation.validators.change.ValidatorStakeTargetModel +import io.novafoundation.nova.feature_staking_impl.presentation.validators.change.activeStake import io.novafoundation.nova.feature_staking_impl.presentation.validators.change.custom.common.CustomValidatorsPayload import io.novafoundation.nova.feature_staking_impl.presentation.validators.change.custom.select.model.ContinueButtonState import io.novafoundation.nova.feature_staking_impl.presentation.validators.change.setCustomValidators @@ -45,6 +46,7 @@ import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.launch class SelectCustomValidatorsViewModel( @@ -91,8 +93,8 @@ class SelectCustomValidatorsViewModel( private val selectedValidators = MutableStateFlow(emptySet>()) private val maxSelectedValidatorsFlow = flowOf { - interactor.maxValidatorsPerNominator() - }.share() + interactor.maxValidatorsPerNominator(setupStakingSharedState.activeStake()) + }.shareInBackground() val validatorModelsFlow = combine( shownValidators, @@ -136,6 +138,7 @@ class SelectCustomValidatorsViewModel( }.inBackground().share() val fillWithRecommendedEnabled = selectedValidators.map { it.size < maxSelectedValidatorsFlow.first() } + .onStart { emit(false) } .share() val clearFiltersEnabled = recommendationSettingsFlow.map { it.customEnabledFilters.isNotEmpty() || it.postProcessors.isNotEmpty() } @@ -205,7 +208,9 @@ class SelectCustomValidatorsViewModel( fun fillRestWithRecommended() { mutateSelected { selected -> - val recommended = recommendator().recommendations(recommendationSettingsProvider().defaultSettings()) + val maxValidatorsPerNominator = maxSelectedValidatorsFlow.first() + val defaultSettings = recommendationSettingsProvider().defaultSettings(maxValidatorsPerNominator) + val recommended = recommendator().recommendations(defaultSettings) val missingFromRecommended = recommended.asSetItems() - selected val neededToFill = maxSelectedValidatorsFlow.first() - selected.size diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/recommended/RecommendedValidatorsViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/recommended/RecommendedValidatorsViewModel.kt index 670e573ec1..00f48fa65f 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/recommended/RecommendedValidatorsViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/recommended/RecommendedValidatorsViewModel.kt @@ -19,6 +19,7 @@ import io.novafoundation.nova.feature_staking_impl.presentation.common.SetupStak import io.novafoundation.nova.feature_staking_impl.presentation.mappers.mapValidatorToValidatorDetailsParcelModel import io.novafoundation.nova.feature_staking_impl.presentation.mappers.mapValidatorToValidatorModel import io.novafoundation.nova.feature_staking_impl.presentation.validators.change.ValidatorStakeTargetModel +import io.novafoundation.nova.feature_staking_impl.presentation.validators.change.activeStake import io.novafoundation.nova.feature_staking_impl.presentation.validators.change.setRecommendedValidators import io.novafoundation.nova.feature_staking_impl.presentation.validators.details.StakeTargetDetailsPayload import io.novafoundation.nova.feature_staking_impl.presentation.validators.details.relaychain @@ -43,8 +44,12 @@ class RecommendedValidatorsViewModel( private val selectedAssetState: AnySelectedAssetOptionSharedState ) : BaseViewModel() { + private val maxValidatorsPerNominator by lazyAsync { + interactor.maxValidatorsPerNominator(sharedStateSetup.activeStake()) + } + private val recommendedSettings by lazyAsync { - recommendationSettingsProviderFactory.create(scope = viewModelScope).defaultSettings() + recommendationSettingsProviderFactory.create(scope = viewModelScope).defaultSettings(maxValidatorsPerNominator()) } private val recommendedValidators = flow { @@ -59,7 +64,7 @@ class RecommendedValidatorsViewModel( }.inBackground().share() val selectedTitle = recommendedValidators.map { - val maxValidators = interactor.maxValidatorsPerNominator() + val maxValidators = maxValidatorsPerNominator() resourceManager.getString(R.string.staking_custom_header_validators_title, it.size, maxValidators) }.inBackground().share() diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/start/StartChangeValidatorsViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/start/StartChangeValidatorsViewModel.kt index 72fd28573d..04fccfb448 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/start/StartChangeValidatorsViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/start/StartChangeValidatorsViewModel.kt @@ -15,7 +15,8 @@ import io.novafoundation.nova.feature_staking_impl.domain.recommendations.Valida import io.novafoundation.nova.feature_staking_impl.presentation.StakingRouter import io.novafoundation.nova.feature_staking_impl.presentation.common.SetupStakingProcess import io.novafoundation.nova.feature_staking_impl.presentation.common.SetupStakingSharedState -import io.novafoundation.nova.feature_staking_impl.presentation.validators.change.retractValidators +import io.novafoundation.nova.feature_staking_impl.presentation.validators.change.activeStake +import io.novafoundation.nova.feature_staking_impl.presentation.validators.change.reset import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.transform @@ -39,7 +40,7 @@ class StartChangeValidatorsViewModel( override val openBrowserEvent = MutableLiveData>() private val maxValidatorsPerNominator = flowOf { - interactor.maxValidatorsPerNominator() + interactor.maxValidatorsPerNominator(setupStakingSharedState.activeStake()) }.share() val validatorsLoading = MutableStateFlow(true) @@ -84,7 +85,7 @@ class StartChangeValidatorsViewModel( } fun backClicked() { - setupStakingSharedState.retractValidators() + setupStakingSharedState.reset() router.back() } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/current/CurrentValidatorsViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/current/CurrentValidatorsViewModel.kt index cacb4bb5c9..89b55eeb24 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/current/CurrentValidatorsViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/current/CurrentValidatorsViewModel.kt @@ -6,6 +6,7 @@ import io.novafoundation.nova.common.list.toListWithHeaders import io.novafoundation.nova.common.list.toValueList import io.novafoundation.nova.common.mixin.api.Validatable import io.novafoundation.nova.common.resources.ResourceManager +import io.novafoundation.nova.common.utils.combineToPair import io.novafoundation.nova.common.utils.flowOf import io.novafoundation.nova.common.utils.inBackground import io.novafoundation.nova.common.utils.toHexAccountId @@ -20,6 +21,7 @@ import io.novafoundation.nova.feature_staking_impl.domain.validations.controller import io.novafoundation.nova.feature_staking_impl.domain.validators.current.CurrentValidatorsInteractor import io.novafoundation.nova.feature_staking_impl.presentation.StakingRouter import io.novafoundation.nova.feature_staking_impl.presentation.common.SetupStakingProcess +import io.novafoundation.nova.feature_staking_impl.presentation.common.SetupStakingProcess.ReadyToSubmit.SelectionMethod import io.novafoundation.nova.feature_staking_impl.presentation.common.SetupStakingSharedState import io.novafoundation.nova.feature_staking_impl.presentation.common.currentStakeTargets.CurrentStakeTargetsViewModel import io.novafoundation.nova.feature_staking_impl.presentation.common.currentStakeTargets.model.SelectedStakeTargetModel @@ -29,7 +31,7 @@ import io.novafoundation.nova.feature_staking_impl.presentation.mappers.mapValid import io.novafoundation.nova.feature_staking_impl.presentation.validators.change.mapAddEvmTokensValidationFailureToUI import io.novafoundation.nova.feature_staking_impl.presentation.validators.details.StakeTargetDetailsPayload import io.novafoundation.nova.feature_staking_impl.presentation.validators.details.relaychain -import io.novafoundation.nova.feature_wallet_api.domain.TokenUseCase +import io.novafoundation.nova.feature_wallet_api.domain.AssetUseCase import io.novafoundation.nova.feature_wallet_api.domain.model.Token import io.novafoundation.nova.feature_wallet_api.presentation.model.mapAmountToAmountModel import io.novafoundation.nova.runtime.ext.addressOf @@ -40,6 +42,7 @@ import jp.co.soramitsu.fearless_utils.extensions.fromHex import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flatMapLatest @@ -56,7 +59,7 @@ class CurrentValidatorsViewModel( private val setupStakingSharedState: SetupStakingSharedState, private val selectedAssetState: AnySelectedAssetOptionSharedState, private val validationExecutor: ValidationExecutor, - tokenUseCase: TokenUseCase, + private val assetUseCase: AssetUseCase, ) : CurrentStakeTargetsViewModel(), Validatable by validationExecutor { private val stashFlow = stakingInteractor.selectedAccountStakingStateFlow(viewModelScope) @@ -64,8 +67,15 @@ class CurrentValidatorsViewModel( .inBackground() .share() - private val groupedCurrentValidatorsFlow = stashFlow - .flatMapLatest { currentValidatorsInteractor.nominatedValidatorsFlow(it, viewModelScope) } + private val assetFlow = assetUseCase.currentAssetFlow() + .shareInBackground() + + private val activeStakeFlow = assetFlow + .map { it.bondedInPlanks } + .distinctUntilChanged() + + private val groupedCurrentValidatorsFlow = combineToPair(stashFlow, activeStakeFlow) + .flatMapLatest { (stash, activeStake) -> currentValidatorsInteractor.nominatedValidatorsFlow(stash, activeStake, viewModelScope) } .inBackground() .share() @@ -74,8 +84,8 @@ class CurrentValidatorsViewModel( .inBackground() .share() - private val tokenFlow = tokenUseCase.currentTokenFlow() - .shareInBackground() + private val tokenFlow = assetFlow + .map { it.token } override val currentStakeTargetsFlow = groupedCurrentValidatorsFlow.combine(tokenFlow) { gropedList, token -> val chain = selectedAssetState.chain() @@ -142,8 +152,8 @@ class CurrentValidatorsViewModel( launch { val currentState = setupStakingSharedState.get() val currentValidators = flattenCurrentValidators.first().map(NominatedValidator::validator) - val newState = currentState.changeValidatorsFlow() - .next(currentValidators, SetupStakingProcess.ReadyToSubmit.SelectionMethod.CUSTOM) + val activeStake = activeStakeFlow.first() + val newState = currentState.next(activeStake, currentValidators, SelectionMethod.CUSTOM) setupStakingSharedState.set(newState) router.openStartChangeValidators() } @@ -190,8 +200,8 @@ class CurrentValidatorsViewModel( SelectedStakeTargetStatusModel.TitleConfig( text = resourceManager.getString(R.string.staking_your_not_elected_format, statusGroup.numberOfValidators), iconRes = R.drawable.ic_time_16, - iconTintRes = R.color.text_tertiary, - textColorRes = R.color.text_tertiary, + iconTintRes = R.color.text_secondary, + textColorRes = R.color.text_secondary, ), description = resourceManager.getString(R.string.staking_your_inactive_description_v2_2_0) ) @@ -209,8 +219,8 @@ class CurrentValidatorsViewModel( statusGroup.maxValidatorsPerNominator ), iconRes = R.drawable.ic_time_16, - iconTintRes = R.color.text_tertiary, - textColorRes = R.color.text_tertiary, + iconTintRes = R.color.text_secondary, + textColorRes = R.color.text_secondary, ), description = resourceManager.getString(R.string.staking_your_validators_changing_title) ) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/current/di/CurrentValidatorsModule.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/current/di/CurrentValidatorsModule.kt index 21027054f9..fb44f3bc95 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/current/di/CurrentValidatorsModule.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/current/di/CurrentValidatorsModule.kt @@ -17,7 +17,7 @@ import io.novafoundation.nova.feature_staking_impl.domain.validators.current.Cur import io.novafoundation.nova.feature_staking_impl.presentation.StakingRouter import io.novafoundation.nova.feature_staking_impl.presentation.common.SetupStakingSharedState import io.novafoundation.nova.feature_staking_impl.presentation.validators.current.CurrentValidatorsViewModel -import io.novafoundation.nova.feature_wallet_api.domain.TokenUseCase +import io.novafoundation.nova.feature_wallet_api.domain.AssetUseCase @Module(includes = [ViewModelModule::class]) class CurrentValidatorsModule { @@ -33,7 +33,7 @@ class CurrentValidatorsModule { setupStakingSharedState: SetupStakingSharedState, router: StakingRouter, selectedAssetState: StakingSharedState, - tokenUseCase: TokenUseCase, + assetUseCase: AssetUseCase, validationExecutor: ValidationExecutor ): ViewModel { return CurrentValidatorsViewModel( @@ -44,8 +44,8 @@ class CurrentValidatorsModule { currentValidatorsInteractor = currentValidatorsInteractor, setupStakingSharedState = setupStakingSharedState, selectedAssetState = selectedAssetState, - tokenUseCase = tokenUseCase, - validationExecutor = validationExecutor + validationExecutor = validationExecutor, + assetUseCase = assetUseCase, ) } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/view/stakingType/StakingTypeView.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/view/stakingType/StakingTypeView.kt index ce6bdee6a2..f4edddf665 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/view/stakingType/StakingTypeView.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/view/stakingType/StakingTypeView.kt @@ -64,10 +64,10 @@ class StakingTypeView @JvmOverloads constructor( fun setSelectable(isSelectable: Boolean) { if (isSelectable) { stakingTypeTitle.setTextColorRes(R.color.text_primary) - stakingTypeConditions.setTextColorRes(R.color.text_secondary) + stakingTypeConditions.setTextColorRes(R.color.staking_type_banner_text) } else { - stakingTypeTitle.setTextColorRes(R.color.button_text_inactive) - stakingTypeConditions.setTextColorRes(R.color.button_text_inactive) + stakingTypeTitle.setTextColorRes(R.color.staking_type_banner_text_inactive) + stakingTypeConditions.setTextColorRes(R.color.staking_type_banner_text_inactive) } } diff --git a/feature-staking-impl/src/main/res/layout/fragment_confirm_nominations.xml b/feature-staking-impl/src/main/res/layout/fragment_confirm_nominations.xml index 8936f6307c..71a258ef19 100644 --- a/feature-staking-impl/src/main/res/layout/fragment_confirm_nominations.xml +++ b/feature-staking-impl/src/main/res/layout/fragment_confirm_nominations.xml @@ -30,7 +30,7 @@ android:layout_gravity="start" android:text="@string/account_info_title" android:textAllCaps="true" - android:textColor="@color/text_tertiary" /> + android:textColor="@color/text_secondary" /> + android:textColor="@color/text_secondary" /> + android:textColor="@color/text_secondary" /> + android:textColor="@color/text_secondary" /> + app:tint="@color/text_secondary" /> + android:textColor="@color/text_secondary" /> + android:textColor="@color/text_secondary" /> + android:textColor="@color/text_secondary" /> \ No newline at end of file diff --git a/feature-staking-impl/src/main/res/layout/fragment_start_staking_landing.xml b/feature-staking-impl/src/main/res/layout/fragment_start_staking_landing.xml index 36dd976851..3bde00bbf5 100644 --- a/feature-staking-impl/src/main/res/layout/fragment_start_staking_landing.xml +++ b/feature-staking-impl/src/main/res/layout/fragment_start_staking_landing.xml @@ -57,7 +57,7 @@ android:layout_marginTop="12dp" android:layout_marginBottom="24dp" android:gravity="center" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" tools:text="@string/start_staking_fragment_available_balance" /> diff --git a/feature-staking-impl/src/main/res/layout/fragment_story.xml b/feature-staking-impl/src/main/res/layout/fragment_story.xml index f67cf05071..fb88ff1ed3 100644 --- a/feature-staking-impl/src/main/res/layout/fragment_story.xml +++ b/feature-staking-impl/src/main/res/layout/fragment_story.xml @@ -51,7 +51,7 @@ android:layout_marginStart="16dp" android:layout_marginEnd="16dp" android:layout_weight="1" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" android:includeFontPadding="false" tools:text="Story body" /> diff --git a/feature-staking-impl/src/main/res/layout/fragment_yield_boost_confirm.xml b/feature-staking-impl/src/main/res/layout/fragment_yield_boost_confirm.xml index 0fd7386617..d72a93626d 100644 --- a/feature-staking-impl/src/main/res/layout/fragment_yield_boost_confirm.xml +++ b/feature-staking-impl/src/main/res/layout/fragment_yield_boost_confirm.xml @@ -79,7 +79,7 @@ android:layout_marginEnd="16dp" android:id="@+id/confirmYieldBoostTerms" android:paddingLeft="12dp" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" tools:text="@string/yield_boost_terms" /> \ No newline at end of file diff --git a/feature-staking-impl/src/main/res/layout/item_list_default.xml b/feature-staking-impl/src/main/res/layout/item_list_default.xml index 638cbd4316..501858b0a2 100644 --- a/feature-staking-impl/src/main/res/layout/item_list_default.xml +++ b/feature-staking-impl/src/main/res/layout/item_list_default.xml @@ -19,7 +19,7 @@ app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" - app:tint="@color/text_tertiary" /> + app:tint="@color/text_secondary" /> \ No newline at end of file diff --git a/feature-staking-impl/src/main/res/layout/item_unbonding.xml b/feature-staking-impl/src/main/res/layout/item_unbonding.xml index b136d77414..0c17a1d525 100644 --- a/feature-staking-impl/src/main/res/layout/item_unbonding.xml +++ b/feature-staking-impl/src/main/res/layout/item_unbonding.xml @@ -30,11 +30,11 @@ app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" tools:drawableEnd="@drawable/ic_time_16" tools:drawablePadding="4dp" tools:drawableTint="@color/icon_secondary" tools:text="4 days left" - tools:textColor="@color/text_tertiary" /> + tools:textColor="@color/text_secondary" /> \ No newline at end of file diff --git a/feature-staking-impl/src/main/res/layout/item_validator.xml b/feature-staking-impl/src/main/res/layout/item_validator.xml index 7384f66c87..42afa0bba9 100644 --- a/feature-staking-impl/src/main/res/layout/item_validator.xml +++ b/feature-staking-impl/src/main/res/layout/item_validator.xml @@ -74,7 +74,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingEnd="4dp" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" android:visibility="gone" app:layout_constraintBottom_toBottomOf="@+id/itemStakingTargetSubtitleValue" app:layout_constraintStart_toStartOf="@+id/itemStakingTargetName" @@ -113,7 +113,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical|end" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/itemStakingTargetInfo" app:layout_constraintTop_toBottomOf="@+id/itemStakingTargetScoringPrimary" diff --git a/feature-staking-impl/src/main/res/layout/view_estimate_earning.xml b/feature-staking-impl/src/main/res/layout/view_estimate_earning.xml index 2bee7d6a09..23fec6710c 100644 --- a/feature-staking-impl/src/main/res/layout/view_estimate_earning.xml +++ b/feature-staking-impl/src/main/res/layout/view_estimate_earning.xml @@ -37,7 +37,7 @@ android:layout_marginStart="16dp" android:layout_marginEnd="12dp" android:src="@drawable/ic_info_24" - app:tint="@color/text_tertiary" /> + app:tint="@color/text_secondary" /> diff --git a/feature-staking-impl/src/main/res/layout/view_network_info_cell.xml b/feature-staking-impl/src/main/res/layout/view_network_info_cell.xml index a1751b5964..356af28548 100644 --- a/feature-staking-impl/src/main/res/layout/view_network_info_cell.xml +++ b/feature-staking-impl/src/main/res/layout/view_network_info_cell.xml @@ -14,7 +14,7 @@ android:layout_height="wrap_content" android:includeFontPadding="false" android:singleLine="true" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" tools:text="Title" /> @@ -68,7 +68,7 @@ android:layout_marginTop="2dp" android:singleLine="true" android:visibility="gone" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" tools:text="$8.131" /> \ No newline at end of file diff --git a/feature-staking-impl/src/main/res/layout/view_payout_target.xml b/feature-staking-impl/src/main/res/layout/view_payout_target.xml index 4f2a423d2c..a085a69b13 100644 --- a/feature-staking-impl/src/main/res/layout/view_payout_target.xml +++ b/feature-staking-impl/src/main/res/layout/view_payout_target.xml @@ -74,7 +74,7 @@ android:layout_marginStart="16dp" android:layout_marginTop="3dp" android:singleLine="true" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" app:layout_constrainedWidth="true" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="@+id/payoutTargetAmountToken" diff --git a/feature-staking-impl/src/main/res/layout/view_reward_destination_chooser.xml b/feature-staking-impl/src/main/res/layout/view_reward_destination_chooser.xml index f9871fd138..0c5f759781 100644 --- a/feature-staking-impl/src/main/res/layout/view_reward_destination_chooser.xml +++ b/feature-staking-impl/src/main/res/layout/view_reward_destination_chooser.xml @@ -21,7 +21,7 @@ android:layout_marginEnd="16dp" android:layout_weight="1" android:text="@string/staking_reward_destination" - android:textColor="@color/text_tertiary" /> + android:textColor="@color/text_secondary" /> + android:textColor="@color/text_secondary" /> @@ -45,7 +45,7 @@ android:layout_marginTop="4dp" android:layout_marginEnd="24dp" android:gravity="center" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/stakeSummaryTokenStake" diff --git a/feature-staking-impl/src/main/res/layout/view_staking_info.xml b/feature-staking-impl/src/main/res/layout/view_staking_info.xml index b21a319a9b..b6d4a5deb5 100644 --- a/feature-staking-impl/src/main/res/layout/view_staking_info.xml +++ b/feature-staking-impl/src/main/res/layout/view_staking_info.xml @@ -41,7 +41,7 @@ android:layout_gravity="center_horizontal" android:layout_marginTop="4dp" android:singleLine="true" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" tools:text="Monthly" /> \ No newline at end of file diff --git a/feature-staking-impl/src/main/res/layout/view_staking_target.xml b/feature-staking-impl/src/main/res/layout/view_staking_target.xml index 76beb53ccc..28f9fe85ec 100644 --- a/feature-staking-impl/src/main/res/layout/view_staking_target.xml +++ b/feature-staking-impl/src/main/res/layout/view_staking_target.xml @@ -38,7 +38,7 @@ android:layout_height="24dp" android:layout_marginStart="12dp" android:gravity="center" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" android:visibility="gone" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" diff --git a/feature-staking-impl/src/main/res/layout/view_staking_type.xml b/feature-staking-impl/src/main/res/layout/view_staking_type.xml index 1a10eb53f6..b98ac648cb 100644 --- a/feature-staking-impl/src/main/res/layout/view_staking_type.xml +++ b/feature-staking-impl/src/main/res/layout/view_staking_type.xml @@ -69,7 +69,7 @@ android:includeFontPadding="false" android:lineSpacingExtra="8dp" android:paddingBottom="16dp" - android:textColor="@color/text_secondary" + android:textColor="@color/staking_type_banner_text" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/stakingTypeTitle" tools:text="Minimum stake: 1 DOT\nRewards: Claim manually" /> diff --git a/feature-staking-impl/src/main/res/layout/view_unbondings.xml b/feature-staking-impl/src/main/res/layout/view_unbondings.xml index 5dfd3606e8..11b6a3f06e 100644 --- a/feature-staking-impl/src/main/res/layout/view_unbondings.xml +++ b/feature-staking-impl/src/main/res/layout/view_unbondings.xml @@ -15,7 +15,7 @@ android:layout_marginStart="16dp" android:layout_marginTop="16dp" android:text="@string/wallet_balance_unbonding_v1_9_0" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> diff --git a/feature-staking-impl/src/main/res/layout/view_user_rewards.xml b/feature-staking-impl/src/main/res/layout/view_user_rewards.xml index 1245a6b4de..3371351e82 100644 --- a/feature-staking-impl/src/main/res/layout/view_user_rewards.xml +++ b/feature-staking-impl/src/main/res/layout/view_user_rewards.xml @@ -25,7 +25,7 @@ android:layout_height="wrap_content" android:layout_marginTop="20dp" android:text="@string/staking_your_rewards" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> @@ -72,7 +72,7 @@ android:ellipsize="end" android:includeFontPadding="false" android:singleLine="true" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" app:layout_constraintBottom_toTopOf="@+id/userRewardsPendingContainer" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="@id/userRewardsTokenAmount" diff --git a/feature-swap-api/src/main/res/layout/view_swap_asset.xml b/feature-swap-api/src/main/res/layout/view_swap_asset.xml index 3e265eb692..ab2bbf7b30 100644 --- a/feature-swap-api/src/main/res/layout/view_swap_asset.xml +++ b/feature-swap-api/src/main/res/layout/view_swap_asset.xml @@ -49,7 +49,7 @@ android:layout_marginEnd="16dp" android:ellipsize="middle" android:singleLine="true" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" app:layout_constrainedWidth="true" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" diff --git a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/data/assetExchange/assetConversion/AssetConversionExchange.kt b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/data/assetExchange/assetConversion/AssetConversionExchange.kt index 9e89ff5286..25461c42af 100644 --- a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/data/assetExchange/assetConversion/AssetConversionExchange.kt +++ b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/data/assetExchange/assetConversion/AssetConversionExchange.kt @@ -174,7 +174,7 @@ private class AssetConversionExchange( val minimumBalance = assetBalances.existentialDeposit(chain, nativeChainAsset) // https://github.com/paritytech/polkadot-sdk/blob/39c04fdd9622792ba8478b1c1c300417943a034b/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/payment.rs#L114 - val shouldBuyMinimumBalance = nativeAsset.freeInPlanks < minimumBalance + nativeTokenFee.amount + val shouldBuyMinimumBalance = nativeAsset.balanceCountedTowardsEDInPlanks < minimumBalance + nativeTokenFee.amount val toBuyNativeFee = runtimeCallsApi.quoteFeeConversion(nativeTokenFee.amount, customFeeAsset) diff --git a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/interactor/SwapInteractor.kt b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/interactor/SwapInteractor.kt index 2695632b31..28149e90a5 100644 --- a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/interactor/SwapInteractor.kt +++ b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/interactor/SwapInteractor.kt @@ -5,6 +5,7 @@ import io.novafoundation.nova.common.validation.ValidationSystem import io.novafoundation.nova.core.updater.UpdateSystem import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicSubmission import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository +import io.novafoundation.nova.feature_account_api.domain.model.LightMetaAccount.Type import io.novafoundation.nova.feature_buy_api.domain.BuyTokenRegistry import io.novafoundation.nova.feature_buy_api.domain.hasProvidersFor import io.novafoundation.nova.feature_swap_api.domain.model.SlippageConfig @@ -128,7 +129,9 @@ class SwapInteractor( } private fun receiveAvailable(chainAssetFlow: Flow): Flow { - return chainAssetFlow.map { it != null } + return combine(accountRepository.selectedMetaAccountFlow(), chainAssetFlow) { metaAccout, asset -> + metaAccout.type != Type.WATCH_ONLY && asset != null + } } fun validationSystem(): SwapValidationSystem { diff --git a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/validation/SufficientAmountOutToStayAboveEDValidation.kt b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/validation/SufficientAmountOutToStayAboveEDValidation.kt index 9e84013705..c0418a1edd 100644 --- a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/validation/SufficientAmountOutToStayAboveEDValidation.kt +++ b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/validation/SufficientAmountOutToStayAboveEDValidation.kt @@ -3,7 +3,6 @@ package io.novafoundation.nova.feature_swap_impl.domain.validation import io.novafoundation.nova.common.validation.ValidationStatus import io.novafoundation.nova.common.validation.validOrError import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.AssetSourceRegistry -import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.existentialDeposit import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.existentialDepositInPlanks class SufficientAmountOutToStayAboveEDValidation( @@ -15,7 +14,7 @@ class SufficientAmountOutToStayAboveEDValidation( val assetOut = value.detailedAssetOut.asset val amountOut = value.detailedAssetOut.amountInPlanks val existentialDeposit = assetSourceRegistry.existentialDepositInPlanks(chainOut, assetOut.token.configuration) - return validOrError(assetOut.freeInPlanks + amountOut >= existentialDeposit) { + return validOrError(assetOut.balanceCountedTowardsEDInPlanks + amountOut >= existentialDeposit) { SwapValidationFailure.AmountOutIsTooLowToStayAboveED( assetOut.token.configuration, amountOut, diff --git a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/validation/SufficientBalanceConsideringNonSufficientAssetsValidation.kt b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/validation/SufficientBalanceConsideringNonSufficientAssetsValidation.kt index 68c8f918f4..66dd6bee59 100644 --- a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/validation/SufficientBalanceConsideringNonSufficientAssetsValidation.kt +++ b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/validation/SufficientBalanceConsideringNonSufficientAssetsValidation.kt @@ -23,7 +23,7 @@ class SufficientBalanceConsideringNonSufficientAssetsValidation( val existentialDeposit = assetSourceRegistry.existentialDepositInPlanks(value.detailedAssetIn.chain, assetIn.token.configuration) val fee = value.swapFee.networkFee.amount - return validOrError(assetIn.freeInPlanks - existentialDeposit >= amount + fee) { + return validOrError(assetIn.balanceCountedTowardsEDInPlanks - existentialDeposit >= amount + fee) { SwapValidationFailure.InsufficientBalance.BalanceNotConsiderInsufficientReceiveAsset( assetIn.token.configuration, assetOut.token.configuration, diff --git a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/validation/SwapValidations.kt b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/validation/SwapValidations.kt index 6dad28d636..0a74e1da41 100644 --- a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/validation/SwapValidations.kt +++ b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/validation/SwapValidations.kt @@ -44,7 +44,7 @@ fun SwapValidationSystemBuilder.sufficientBalanceConsideringConsumersValidation( assetSourceRegistry, chainExtractor = { it.detailedAssetIn.chain }, assetExtractor = { it.detailedAssetIn.asset.token.configuration }, - balanceExtractor = { it.detailedAssetIn.asset.freeInPlanks }, + balanceCountedTowardsEdExtractor = { it.detailedAssetIn.asset.balanceCountedTowardsEDInPlanks }, feeExtractor = { it.totalDeductedAmountInFeeToken }, amountExtractor = { it.detailedAssetIn.amountInPlanks }, error = { payload, existentialDeposit -> diff --git a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/validation/validations/EnoughNativeAssetBalanceToPayFeeConsideringEDValidation.kt b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/validation/validations/EnoughNativeAssetBalanceToPayFeeConsideringEDValidation.kt index 0dbf840844..2422a9bded 100644 --- a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/validation/validations/EnoughNativeAssetBalanceToPayFeeConsideringEDValidation.kt +++ b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/validation/validations/EnoughNativeAssetBalanceToPayFeeConsideringEDValidation.kt @@ -24,7 +24,7 @@ class EnoughNativeAssetBalanceToPayFeeConsideringEDValidation( if (feeChainAsset.isCommissionAsset) { val chain = chainRegistry.getChain(feeChainAsset.chainId) val existentialDeposit = assetSourceRegistry.existentialDepositInPlanks(chain, feeChainAsset) - return validOrError(value.feeAsset.freeInPlanks - value.swapFee.networkFee.amount >= existentialDeposit) { + return validOrError(value.feeAsset.balanceCountedTowardsEDInPlanks - value.swapFee.networkFee.amount >= existentialDeposit) { NotEnoughFunds.ToPayFeeAndStayAboveED(value.feeAsset.token.configuration) } } diff --git a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/validation/validations/SwapSmallRemainingBalanceValidation.kt b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/validation/validations/SwapSmallRemainingBalanceValidation.kt index 72202c1445..c166f3313d 100644 --- a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/validation/validations/SwapSmallRemainingBalanceValidation.kt +++ b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/validation/validations/SwapSmallRemainingBalanceValidation.kt @@ -25,11 +25,11 @@ class SwapSmallRemainingBalanceValidation( val chainIn = value.detailedAssetIn.chain val assetBalances = assetSourceRegistry.sourceFor(chainAssetIn).balance - val assetInFreeBalance = value.detailedAssetIn.asset.freeInPlanks + val balanceCountedTowardsEd = value.detailedAssetIn.asset.balanceCountedTowardsEDInPlanks val swapAmount = value.detailedAssetIn.amountInPlanks val assetInExistentialDeposit = assetBalances.existentialDeposit(chainIn, chainAssetIn) val totalDeductedAmount = value.totalDeductedAmountInFeeToken - val remainingBalance = assetInFreeBalance - swapAmount - totalDeductedAmount + val remainingBalance = balanceCountedTowardsEd - swapAmount - totalDeductedAmount if (remainingBalance.isPositive() && remainingBalance < assetInExistentialDeposit) { val toBuyAmountToKeepEDInFeeAsset = value.toBuyAmountToKeepMainEDInFeeAsset diff --git a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/presentation/fieldValidation/SwapReceiveAmountAboveEDFieldValidator.kt b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/presentation/fieldValidation/SwapReceiveAmountAboveEDFieldValidator.kt index 502c063ad1..6b74481868 100644 --- a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/presentation/fieldValidation/SwapReceiveAmountAboveEDFieldValidator.kt +++ b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/presentation/fieldValidation/SwapReceiveAmountAboveEDFieldValidator.kt @@ -7,14 +7,15 @@ import io.novafoundation.nova.feature_swap_impl.R import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.AssetSourceRegistry import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.existentialDeposit import io.novafoundation.nova.feature_wallet_api.domain.model.Asset +import io.novafoundation.nova.feature_wallet_api.domain.model.balanceCountedTowardsED import io.novafoundation.nova.feature_wallet_api.presentation.formatters.formatTokenAmount import io.novafoundation.nova.runtime.ext.fullId import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry -import java.math.BigDecimal import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChangedBy import kotlinx.coroutines.flow.map +import java.math.BigDecimal class SwapReceiveAmountAboveEDFieldValidatorFactory( private val resourceManager: ResourceManager, @@ -41,7 +42,7 @@ class SwapReceiveAmountAboveEDFieldValidator( val existentialDeposit = assetWithExistentialDeposit.second when { - amount >= BigDecimal.ZERO && asset.total + amount < existentialDeposit -> { + amount >= BigDecimal.ZERO && asset.balanceCountedTowardsED() + amount < existentialDeposit -> { val formattedExistentialDeposit = existentialDeposit.formatTokenAmount(asset.token.configuration) FieldValidationResult.Error( resourceManager.getString(R.string.swap_field_validation_to_low_amount_out, formattedExistentialDeposit) diff --git a/feature-swap-impl/src/main/res/layout/view_swap_amount_input.xml b/feature-swap-impl/src/main/res/layout/view_swap_amount_input.xml index 45397391f5..0f70968c2c 100644 --- a/feature-swap-impl/src/main/res/layout/view_swap_amount_input.xml +++ b/feature-swap-impl/src/main/res/layout/view_swap_amount_input.xml @@ -118,7 +118,7 @@ android:layout_marginEnd="16dp" android:layout_marginBottom="12dp" android:singleLine="true" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" app:layout_constrainedWidth="true" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" diff --git a/feature-versions-impl/src/main/res/layout/item_update_notification.xml b/feature-versions-impl/src/main/res/layout/item_update_notification.xml index 81d5306c21..9c89720cee 100644 --- a/feature-versions-impl/src/main/res/layout/item_update_notification.xml +++ b/feature-versions-impl/src/main/res/layout/item_update_notification.xml @@ -60,7 +60,7 @@ android:layout_height="wrap_content" android:layout_marginHorizontal="16dp" android:layout_marginTop="4dp" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/itemNotificationVersion" tools:text="Dec 27, 2022" /> @@ -72,7 +72,7 @@ android:layout_height="wrap_content" android:layout_marginHorizontal="16dp" android:layout_marginTop="12dp" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/itemNotificationDate" diff --git a/feature-versions-impl/src/main/res/layout/item_update_notification_header.xml b/feature-versions-impl/src/main/res/layout/item_update_notification_header.xml index 738e7b4bb4..b34d0b2891 100644 --- a/feature-versions-impl/src/main/res/layout/item_update_notification_header.xml +++ b/feature-versions-impl/src/main/res/layout/item_update_notification_header.xml @@ -34,7 +34,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" tools:text="@string/update_notifications_critical_update_alert_subtitile" /> diff --git a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/data/cache/AssetCacheExt.kt b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/data/cache/AssetCacheExt.kt index 2aca8eaa24..e65ac506dc 100644 --- a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/data/cache/AssetCacheExt.kt +++ b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/data/cache/AssetCacheExt.kt @@ -3,6 +3,8 @@ package io.novafoundation.nova.feature_wallet_api.data.cache import io.novafoundation.nova.common.data.network.runtime.binding.AccountInfo import io.novafoundation.nova.common.data.network.runtime.binding.bindAccountInfo import io.novafoundation.nova.core_db.model.AssetLocal +import io.novafoundation.nova.core_db.model.AssetLocal.EDCountingModeLocal +import io.novafoundation.nova.core_db.model.AssetLocal.TransferableModeLocal import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import jp.co.soramitsu.fearless_utils.runtime.AccountId @@ -26,17 +28,36 @@ suspend fun AssetCache.updateNonLockableAsset( assetBalance: Balance, ) { updateAsset(metaId, chainAsset) { - it.copy(freeInPlanks = assetBalance, frozenInPlanks = Balance.ZERO, reservedInPlanks = Balance.ZERO) + it.copy( + freeInPlanks = assetBalance, + frozenInPlanks = Balance.ZERO, + reservedInPlanks = Balance.ZERO, + transferableMode = TransferableModeLocal.REGULAR, + edCountingMode = EDCountingModeLocal.TOTAL, + ) } } private fun nativeBalanceUpdater(accountInfo: AccountInfo) = { asset: AssetLocal -> val data = accountInfo.data + val transferableMode: TransferableModeLocal + val edCountingMode: EDCountingModeLocal + + if (data.flags.holdsAndFreezesEnabled()) { + transferableMode = TransferableModeLocal.HOLDS_AND_FREEZES + edCountingMode = EDCountingModeLocal.FREE + } else { + transferableMode = TransferableModeLocal.REGULAR + edCountingMode = EDCountingModeLocal.TOTAL + } + asset.copy( freeInPlanks = data.free, frozenInPlanks = data.frozen, - reservedInPlanks = accountInfo.data.reserved + reservedInPlanks = data.reserved, + transferableMode = transferableMode, + edCountingMode = edCountingMode ) } diff --git a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/model/Asset.kt b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/model/Asset.kt index 75dbc20070..173959ed0c 100644 --- a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/model/Asset.kt +++ b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/model/Asset.kt @@ -1,6 +1,11 @@ package io.novafoundation.nova.feature_wallet_api.domain.model -import java.math.BigInteger +import io.novafoundation.nova.common.utils.atLeastZero +import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance +import io.novafoundation.nova.feature_wallet_api.domain.model.Asset.Companion.calculateTransferable +import io.novafoundation.nova.feature_wallet_api.domain.model.Asset.Companion.holdAndFreezesTransferable +import io.novafoundation.nova.feature_wallet_api.domain.model.Asset.Companion.legacyTransferable +import java.math.BigDecimal data class Asset( val token: Token, @@ -8,28 +13,79 @@ data class Asset( // Non-reserved part of the balance. There may still be restrictions on // this, but it is the total pool what may in principle be transferred, // reserved. - val freeInPlanks: BigInteger, + val freeInPlanks: Balance, // Balance which is reserved and may not be used at all. // This balance is a 'reserve' balance that different subsystems use in // order to set aside tokens that are still 'owned' by the account // holder, but which are suspendable - val reservedInPlanks: BigInteger, + val reservedInPlanks: Balance, // / The amount that `free` may not drop below when withdrawing. - val frozenInPlanks: BigInteger, + val frozenInPlanks: Balance, + + val transferableMode: TransferableMode, + val edCountingMode: EDCountingMode, // TODO move to runtime storage - val bondedInPlanks: BigInteger, - val redeemableInPlanks: BigInteger, - val unbondingInPlanks: BigInteger + val bondedInPlanks: Balance, + val redeemableInPlanks: Balance, + val unbondingInPlanks: Balance ) { - // Non-reserved plus reserved - val totalInPlanks = freeInPlanks + reservedInPlanks + companion object { + + fun TransferableMode.calculateTransferable(free: Balance, frozen: Balance, reserved: Balance): Balance { + return when (this) { + TransferableMode.REGULAR -> legacyTransferable(free, frozen) + TransferableMode.HOLDS_AND_FREEZES -> holdAndFreezesTransferable(free, frozen, reserved) + } + } + + fun EDCountingMode.calculateBalanceCountedTowardsEd(free: Balance, reserved: Balance): Balance { + return when (this) { + EDCountingMode.TOTAL -> totalBalance(free, reserved) + EDCountingMode.FREE -> free + } + } + + fun legacyTransferable(free: Balance, frozen: Balance): Balance { + return (free - frozen).atLeastZero() + } + + fun holdAndFreezesTransferable(free: Balance, frozen: Balance, reserved: Balance): Balance { + val freeCannotDropBelow = (frozen - reserved).atLeastZero() + + return (free - freeCannotDropBelow).atLeastZero() + } + + fun totalBalance(free: Balance, reserved: Balance): Balance { + return free + reserved + } + } + + enum class TransferableMode { + REGULAR, HOLDS_AND_FREEZES + } - // Free without its min threshold, represented by frozen - val transferableInPlanks = freeInPlanks - frozenInPlanks + enum class EDCountingMode { + TOTAL, FREE + } + + /** + * Liquid balance that can be transferred from an account + * There are multiple ways it is identified, see [legacyTransferable] and [holdAndFreezesTransferable] + */ + val transferableInPlanks: Balance = transferableMode.calculateTransferable(freeInPlanks, frozenInPlanks, reservedInPlanks) + + /** + * Balance that is counted towards meeting the requirement of Existential Deposit + * When the balance + */ + val balanceCountedTowardsEDInPlanks: Balance = edCountingMode.calculateBalanceCountedTowardsEd(freeInPlanks, reservedInPlanks) + + // Non-reserved plus reserved + val totalInPlanks = totalBalance(freeInPlanks, reservedInPlanks) // balance that cannot be used for transfers (non-transferable) for any reason val lockedInPlanks = totalInPlanks - transferableInPlanks @@ -48,3 +104,11 @@ data class Asset( val redeemable = token.amountFromPlanks(redeemableInPlanks) val unbonding = token.amountFromPlanks(unbondingInPlanks) } + +fun Asset.balanceCountedTowardsED(): BigDecimal { + return token.amountFromPlanks(balanceCountedTowardsEDInPlanks) +} + +fun Asset.transferableReplacingFrozen(newFrozen: Balance): Balance { + return transferableMode.calculateTransferable(freeInPlanks, newFrozen, reservedInPlanks) +} diff --git a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/validation/EnoughBalanceToStayAboveEDValidation.kt b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/validation/EnoughBalanceToStayAboveEDValidation.kt index 5fb2fef2aa..d72d859eba 100644 --- a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/validation/EnoughBalanceToStayAboveEDValidation.kt +++ b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/validation/EnoughBalanceToStayAboveEDValidation.kt @@ -59,21 +59,3 @@ fun EnoughTotalToStayAboveEDValidationFactory.validate( ) { validate(create(fee, balance, chainWithAsset, error)) } - -fun ValidationSystemBuilder.enoughBalanceToStayAboveEDValidation( - assetSourceRegistry: AssetSourceRegistry, - fee: AmountProducer

, - balance: AmountProducer

, - chainWithAsset: (P) -> ChainWithAsset, - error: (P, BigDecimal) -> E -) { - validate( - EnoughBalanceToStayAboveEDValidation( - assetSourceRegistry = assetSourceRegistry, - fee = fee, - balance = balance, - chainWithAsset = chainWithAsset, - error = error - ) - ) -} diff --git a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/validation/ExistentialDepositValidation.kt b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/validation/ExistentialDepositValidation.kt index 0fda3f833c..95eaeef657 100644 --- a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/validation/ExistentialDepositValidation.kt +++ b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/validation/ExistentialDepositValidation.kt @@ -9,7 +9,7 @@ import java.math.BigDecimal typealias ExistentialDepositError = (remainingAmount: BigDecimal, payload: P) -> E class ExistentialDepositValidation( - private val totalBalanceProducer: AmountProducer

, + private val countableTowardsEdBalance: AmountProducer

, private val feeProducer: AmountProducer

, private val extraAmountProducer: AmountProducer

, private val errorProducer: ExistentialDepositError, @@ -19,11 +19,11 @@ class ExistentialDepositValidation( override suspend fun validate(value: P): ValidationStatus { val existentialDeposit = existentialDeposit(value) - val totalBalance = totalBalanceProducer(value) + val countableTowardsEd = countableTowardsEdBalance(value) val fee = feeProducer(value) val extraAmount = extraAmountProducer(value) - val remainingAmount = totalBalance - fee - extraAmount + val remainingAmount = countableTowardsEd - fee - extraAmount return validOrWarning(remainingAmount >= existentialDeposit) { errorProducer(remainingAmount, value) @@ -32,14 +32,14 @@ class ExistentialDepositValidation( } fun ValidationSystemBuilder.doNotCrossExistentialDeposit( - totalBalance: AmountProducer

, + countableTowardsEdBalance: AmountProducer

, fee: AmountProducer

= { BigDecimal.ZERO }, extraAmount: AmountProducer

= { BigDecimal.ZERO }, existentialDeposit: AmountProducer

, error: ExistentialDepositError, ) = validate( ExistentialDepositValidation( - totalBalanceProducer = totalBalance, + countableTowardsEdBalance = countableTowardsEdBalance, feeProducer = fee, extraAmountProducer = extraAmount, errorProducer = error, diff --git a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/validation/SufficientBalanceConsideringConsumersValidation.kt b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/validation/SufficientBalanceConsideringConsumersValidation.kt index 40b0dbd66b..3021796e3c 100644 --- a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/validation/SufficientBalanceConsideringConsumersValidation.kt +++ b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/validation/SufficientBalanceConsideringConsumersValidation.kt @@ -15,7 +15,7 @@ class SufficientBalanceConsideringConsumersValidation( private val assetSourceRegistry: AssetSourceRegistry, private val chainExtractor: (P) -> Chain, private val assetExtractor: (P) -> Chain.Asset, - private val balanceExtractor: (P) -> Balance, + private val balanceCountedTowardsEdExtractor: (P) -> Balance, private val feeExtractor: (P) -> Balance, private val amountExtractor: (P) -> Balance, private val error: (P, existentialDeposit: Balance) -> E @@ -30,7 +30,7 @@ class SufficientBalanceConsideringConsumersValidation( return if (totalCanDropBelowMinimumBalance) { valid() } else { - val balance = balanceExtractor(value) + val balance = balanceCountedTowardsEdExtractor(value) val amount = amountExtractor(value) val fee = feeExtractor(value) @@ -44,7 +44,7 @@ fun ValidationSystemBuilder.sufficientBalanceConsideringConsumersVa assetSourceRegistry: AssetSourceRegistry, chainExtractor: (P) -> Chain, assetExtractor: (P) -> Chain.Asset, - balanceExtractor: (P) -> Balance, + balanceCountedTowardsEdExtractor: (P) -> Balance, feeExtractor: (P) -> Balance, amountExtractor: (P) -> Balance, error: (P, existentialDeposit: Balance) -> E @@ -53,7 +53,7 @@ fun ValidationSystemBuilder.sufficientBalanceConsideringConsumersVa assetSourceRegistry, chainExtractor, assetExtractor, - balanceExtractor, + balanceCountedTowardsEdExtractor, feeExtractor, amountExtractor, error, diff --git a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/presentation/view/BalancesView.kt b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/presentation/view/BalancesView.kt index 5d97cba141..7990928799 100644 --- a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/presentation/view/BalancesView.kt +++ b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/presentation/view/BalancesView.kt @@ -53,8 +53,8 @@ abstract class BalancesView @JvmOverloads constructor( val item = TableCellView(context).apply { layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT) - valueSecondary.setTextColorRes(R.color.text_tertiary) - title.setTextColorRes(R.color.text_tertiary) + valueSecondary.setTextColorRes(R.color.text_secondary) + title.setTextColorRes(R.color.text_secondary) setTitle(titleRes) } diff --git a/feature-wallet-api/src/main/res/layout/item_asset_selector.xml b/feature-wallet-api/src/main/res/layout/item_asset_selector.xml index 8e25af5557..55d9d464a9 100644 --- a/feature-wallet-api/src/main/res/layout/item_asset_selector.xml +++ b/feature-wallet-api/src/main/res/layout/item_asset_selector.xml @@ -50,7 +50,7 @@ android:layout_height="wrap_content" android:layout_marginBottom="8dp" android:includeFontPadding="false" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="@id/itemAssetSelectorTokenName" app:layout_constraintTop_toBottomOf="@id/itemAssetSelectorTokenName" diff --git a/feature-wallet-api/src/main/res/layout/section_price.xml b/feature-wallet-api/src/main/res/layout/section_price.xml index 70201c7780..e9b76a006c 100644 --- a/feature-wallet-api/src/main/res/layout/section_price.xml +++ b/feature-wallet-api/src/main/res/layout/section_price.xml @@ -15,7 +15,7 @@ android:layout_marginStart="16dp" android:layout_marginTop="16dp" android:text="@string/common_price" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> @@ -41,7 +41,7 @@ android:layout_marginBottom="16dp" android:ellipsize="end" android:singleLine="true" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" app:layout_constrainedWidth="true" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" diff --git a/feature-wallet-api/src/main/res/layout/view_asset_selector.xml b/feature-wallet-api/src/main/res/layout/view_asset_selector.xml index bc595959c4..85c0c7ba8f 100644 --- a/feature-wallet-api/src/main/res/layout/view_asset_selector.xml +++ b/feature-wallet-api/src/main/res/layout/view_asset_selector.xml @@ -46,7 +46,7 @@ style="@style/TextAppearance.NovaFoundation.Regular.Footnote" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="@id/assetSelectorTokenName" app:layout_constraintTop_toBottomOf="@id/assetSelectorTokenName" diff --git a/feature-wallet-api/src/main/res/layout/view_balances.xml b/feature-wallet-api/src/main/res/layout/view_balances.xml index 9b2c001576..0e566383b7 100644 --- a/feature-wallet-api/src/main/res/layout/view_balances.xml +++ b/feature-wallet-api/src/main/res/layout/view_balances.xml @@ -11,7 +11,7 @@ style="@style/TextAppearance.NovaFoundation.Regular.SubHeadline" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" android:layout_marginBottom="14dp" android:includeFontPadding="false" app:layout_constraintStart_toStartOf="parent" diff --git a/feature-wallet-api/src/main/res/layout/view_choose_amount.xml b/feature-wallet-api/src/main/res/layout/view_choose_amount.xml index d8db2848bb..5a49a9eb3e 100644 --- a/feature-wallet-api/src/main/res/layout/view_choose_amount.xml +++ b/feature-wallet-api/src/main/res/layout/view_choose_amount.xml @@ -14,7 +14,7 @@ android:layout_height="wrap_content" android:includeFontPadding="false" android:text="@string/common_amount" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> @@ -34,7 +34,7 @@ android:layout_height="wrap_content" android:layout_marginEnd="4dp" android:includeFontPadding="false" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" app:layout_constraintEnd_toStartOf="@+id/chooseAmountBalance" app:layout_constraintTop_toTopOf="parent" tools:text="Transferable:" /> diff --git a/feature-wallet-api/src/main/res/layout/view_choose_amount_input.xml b/feature-wallet-api/src/main/res/layout/view_choose_amount_input.xml index b9837aa665..21f3a8396f 100644 --- a/feature-wallet-api/src/main/res/layout/view_choose_amount_input.xml +++ b/feature-wallet-api/src/main/res/layout/view_choose_amount_input.xml @@ -66,7 +66,7 @@ android:layout_height="wrap_content" android:layout_marginBottom="9dp" android:singleLine="true" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" app:layout_constrainedWidth="true" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="@id/chooseAmountInputField" diff --git a/feature-wallet-api/src/main/res/layout/view_max_amount.xml b/feature-wallet-api/src/main/res/layout/view_max_amount.xml index 6681e89c07..ddaa526f11 100644 --- a/feature-wallet-api/src/main/res/layout/view_max_amount.xml +++ b/feature-wallet-api/src/main/res/layout/view_max_amount.xml @@ -21,7 +21,7 @@ style="@style/TextAppearance.NovaFoundation.Regular.Footnote.Primary" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:textColor="@color/text_secondary" + android:textColor="@color/text_primary" tools:text="105.79 USDT" /> \ No newline at end of file diff --git a/feature-wallet-api/src/main/res/layout/view_primary_amount.xml b/feature-wallet-api/src/main/res/layout/view_primary_amount.xml index 3dc100943c..258d90541c 100644 --- a/feature-wallet-api/src/main/res/layout/view_primary_amount.xml +++ b/feature-wallet-api/src/main/res/layout/view_primary_amount.xml @@ -30,6 +30,6 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="4dp" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" tools:text="$1,524.11" /> \ No newline at end of file diff --git a/feature-wallet-api/src/main/res/layout/view_total_amount.xml b/feature-wallet-api/src/main/res/layout/view_total_amount.xml index 8468a40e9b..af8e6b75ae 100644 --- a/feature-wallet-api/src/main/res/layout/view_total_amount.xml +++ b/feature-wallet-api/src/main/res/layout/view_total_amount.xml @@ -16,7 +16,7 @@ android:layout_marginHorizontal="16dp" android:layout_marginTop="21dp" android:text="@string/common_total" - android:textColor="@color/text_tertiary" /> + android:textColor="@color/text_secondary" /> \ No newline at end of file diff --git a/feature-wallet-connect-impl/src/main/java/io/novafoundation/nova/feature_wallet_connect_impl/data/repository/WalletConnectPairingRepository.kt b/feature-wallet-connect-impl/src/main/java/io/novafoundation/nova/feature_wallet_connect_impl/data/repository/WalletConnectPairingRepository.kt new file mode 100644 index 0000000000..ea28f32c2f --- /dev/null +++ b/feature-wallet-connect-impl/src/main/java/io/novafoundation/nova/feature_wallet_connect_impl/data/repository/WalletConnectPairingRepository.kt @@ -0,0 +1,57 @@ +package io.novafoundation.nova.feature_wallet_connect_impl.data.repository + +import io.novafoundation.nova.common.utils.mapList +import io.novafoundation.nova.core_db.dao.WalletConnectSessionsDao +import io.novafoundation.nova.core_db.model.WalletConnectPairingLocal +import io.novafoundation.nova.feature_wallet_connect_impl.domain.model.WalletConnectPairingAccount +import kotlinx.coroutines.flow.Flow + +interface WalletConnectPairingRepository { + + suspend fun addPairingAccount(pairingAccount: WalletConnectPairingAccount) + + suspend fun getPairingAccount(pairingTopic: String): WalletConnectPairingAccount? + + fun allPairingAccountsFlow(): Flow> + + fun pairingAccountsByMetaIdFlow(metaId: Long): Flow> + + suspend fun removeAllPairingsOtherThan(activePairingTopics: List) +} + +class RealWalletConnectPairingRepository( + private val dao: WalletConnectSessionsDao, +) : WalletConnectPairingRepository { + + override suspend fun addPairingAccount(pairingAccount: WalletConnectPairingAccount) { + dao.insertPairing(mapSessionToLocal(pairingAccount)) + } + + override suspend fun getPairingAccount(pairingTopic: String): WalletConnectPairingAccount? { + return dao.getPairing(pairingTopic)?.let(::mapSessionFromLocal) + } + + override fun allPairingAccountsFlow(): Flow> { + return dao.allPairingsFlow().mapList(::mapSessionFromLocal) + } + + override fun pairingAccountsByMetaIdFlow(metaId: Long): Flow> { + return dao.pairingsByMetaIdFlow(metaId).mapList(::mapSessionFromLocal) + } + + override suspend fun removeAllPairingsOtherThan(activePairingTopics: List) { + dao.removeAllPairingsOtherThan(activePairingTopics) + } + + private fun mapSessionToLocal(session: WalletConnectPairingAccount): WalletConnectPairingLocal { + return with(session) { + WalletConnectPairingLocal(pairingTopic = pairingTopic, metaId = metaId) + } + } + + private fun mapSessionFromLocal(sessionLocal: WalletConnectPairingLocal): WalletConnectPairingAccount { + return with(sessionLocal) { + WalletConnectPairingAccount(pairingTopic = pairingTopic, metaId = metaId) + } + } +} diff --git a/feature-wallet-connect-impl/src/main/java/io/novafoundation/nova/feature_wallet_connect_impl/data/repository/WalletConnectSessionRepository.kt b/feature-wallet-connect-impl/src/main/java/io/novafoundation/nova/feature_wallet_connect_impl/data/repository/WalletConnectSessionRepository.kt index 1659645bf1..aa221b659a 100644 --- a/feature-wallet-connect-impl/src/main/java/io/novafoundation/nova/feature_wallet_connect_impl/data/repository/WalletConnectSessionRepository.kt +++ b/feature-wallet-connect-impl/src/main/java/io/novafoundation/nova/feature_wallet_connect_impl/data/repository/WalletConnectSessionRepository.kt @@ -1,89 +1,81 @@ package io.novafoundation.nova.feature_wallet_connect_impl.data.repository -import io.novafoundation.nova.common.utils.mapList -import io.novafoundation.nova.core_db.dao.WalletConnectSessionsDao -import io.novafoundation.nova.core_db.model.WalletConnectSessionAccountLocal -import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount -import io.novafoundation.nova.feature_wallet_connect_impl.domain.model.WalletConnectSessionAccount +import com.walletconnect.web3.wallet.client.Wallet.Model.Session +import io.novafoundation.nova.common.utils.added +import io.novafoundation.nova.common.utils.removed +import io.novafoundation.nova.common.utils.singleReplaySharedFlow import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map interface WalletConnectSessionRepository { - suspend fun addSessionAccount(sessionAccount: WalletConnectSessionAccount) + suspend fun setSessions(sessions: List) - suspend fun getSessionAccount(sessionTopic: String): WalletConnectSessionAccount? + suspend fun getSession(sessionTopic: String): Session? - suspend fun deleteSessionAccount(sessionTopic: String) + fun allSessionsFlow(): Flow> - fun allSessionAccountsFlow(): Flow> + fun sessionFlow(sessionTopic: String): Flow - fun sessionAccountsByMetaIdFlow(metaId: Long): Flow> + suspend fun addSession(session: Session) - fun numberOfSessionAccountsFlow(): Flow + suspend fun removeSession(sessionTopic: String) - fun numberOfSessionAccountsFlow(metaAccount: MetaAccount): Flow + fun numberOfSessionsFlow(): Flow suspend fun numberOfSessionAccounts(): Int - fun sessionAccountFlow(sessionTopic: String): Flow - - suspend fun removeAllSessionsOtherThan(activeSessionTopics: List) + fun numberOfSessionsFlow(pairingTopics: Set): Flow } -class RealWalletConnectSessionRepository( - private val dao: WalletConnectSessionsDao, -) : WalletConnectSessionRepository { - - override suspend fun addSessionAccount(sessionAccount: WalletConnectSessionAccount) { - dao.insertSession(mapSessionToLocal(sessionAccount)) - } +class InMemoryWalletConnectSessionRepository : WalletConnectSessionRepository { - override suspend fun getSessionAccount(sessionTopic: String): WalletConnectSessionAccount? { - return dao.getSession(sessionTopic)?.let(::mapSessionFromLocal) - } + private val state = singleReplaySharedFlow>() - override suspend fun deleteSessionAccount(sessionTopic: String) { - dao.deleteSession(sessionTopic) + override suspend fun setSessions(sessions: List) { + state.emit(sessions) } - override fun allSessionAccountsFlow(): Flow> { - return dao.allSessionsFlow().mapList(::mapSessionFromLocal) + override suspend fun getSession(sessionTopic: String): Session? { + return state.first().find { it.topic == sessionTopic } } - override fun sessionAccountsByMetaIdFlow(metaId: Long): Flow> { - return dao.sessionsByMetaIdFlow(metaId).mapList(::mapSessionFromLocal) + override fun allSessionsFlow(): Flow> { + return state } - override fun numberOfSessionAccountsFlow(): Flow { - return dao.numberOfSessionsFlow() + override fun sessionFlow(sessionTopic: String): Flow { + return state.map { allSessions -> allSessions.find { it.topic == sessionTopic } } } - override fun numberOfSessionAccountsFlow(metaAccount: MetaAccount): Flow { - return dao.numberOfSessionsFlow(metaAccount.id) + override suspend fun addSession(session: Session) { + modifyState { current -> + current.added(session) + } } - override suspend fun numberOfSessionAccounts(): Int { - return dao.numberOfSessions() + override suspend fun removeSession(sessionTopic: String) { + modifyState { current -> + current.removed { it.topic == sessionTopic } + } } - override fun sessionAccountFlow(sessionTopic: String): Flow { - return dao.sessionFlow(sessionTopic).map { localSession -> localSession?.let(::mapSessionFromLocal) } + override fun numberOfSessionsFlow(): Flow { + return state.map { it.size } } - override suspend fun removeAllSessionsOtherThan(activeSessionTopics: List) { - dao.removeAllSessionsOtherThan(activeSessionTopics) + override fun numberOfSessionsFlow(pairingTopics: Set): Flow { + return state.map { sessions -> + sessions.filter { it.pairingTopic in pairingTopics }.size + } } - private fun mapSessionToLocal(session: WalletConnectSessionAccount): WalletConnectSessionAccountLocal { - return with(session) { - WalletConnectSessionAccountLocal(sessionTopic = sessionTopic, metaId = metaId) - } + override suspend fun numberOfSessionAccounts(): Int { + return state.first().size } - private fun mapSessionFromLocal(sessionLocal: WalletConnectSessionAccountLocal): WalletConnectSessionAccount { - return with(sessionLocal) { - WalletConnectSessionAccount(sessionTopic = sessionTopic, metaId = metaId) - } + private suspend fun modifyState(modify: (List) -> List) { + state.emit(modify(state.first())) } } diff --git a/feature-wallet-connect-impl/src/main/java/io/novafoundation/nova/feature_wallet_connect_impl/di/WalletConnectFeatureModule.kt b/feature-wallet-connect-impl/src/main/java/io/novafoundation/nova/feature_wallet_connect_impl/di/WalletConnectFeatureModule.kt index 6259a445ba..ae7a3a3658 100644 --- a/feature-wallet-connect-impl/src/main/java/io/novafoundation/nova/feature_wallet_connect_impl/di/WalletConnectFeatureModule.kt +++ b/feature-wallet-connect-impl/src/main/java/io/novafoundation/nova/feature_wallet_connect_impl/di/WalletConnectFeatureModule.kt @@ -14,7 +14,9 @@ import io.novafoundation.nova.feature_external_sign_api.domain.sign.evm.EvmTyped import io.novafoundation.nova.feature_external_sign_api.model.ExternalSignCommunicator import io.novafoundation.nova.feature_wallet_connect_api.domain.sessions.WalletConnectSessionsUseCase import io.novafoundation.nova.feature_wallet_connect_api.presentation.WalletConnectService -import io.novafoundation.nova.feature_wallet_connect_impl.data.repository.RealWalletConnectSessionRepository +import io.novafoundation.nova.feature_wallet_connect_impl.data.repository.InMemoryWalletConnectSessionRepository +import io.novafoundation.nova.feature_wallet_connect_impl.data.repository.RealWalletConnectPairingRepository +import io.novafoundation.nova.feature_wallet_connect_impl.data.repository.WalletConnectPairingRepository import io.novafoundation.nova.feature_wallet_connect_impl.data.repository.WalletConnectSessionRepository import io.novafoundation.nova.feature_wallet_connect_impl.domain.session.RealWalletConnectSessionInteractor import io.novafoundation.nova.feature_wallet_connect_impl.domain.session.RealWalletConnectSessionsUseCase @@ -58,22 +60,28 @@ class WalletConnectFeatureModule { @Provides @FeatureScope - fun provideSessionRepository(dao: WalletConnectSessionsDao): WalletConnectSessionRepository = RealWalletConnectSessionRepository(dao) + fun providePairingRepository(dao: WalletConnectSessionsDao): WalletConnectPairingRepository = RealWalletConnectPairingRepository(dao) + + @Provides + @FeatureScope + fun provideSessionRepository(): WalletConnectSessionRepository = InMemoryWalletConnectSessionRepository() @Provides @FeatureScope fun provideInteractor( caip2Resolver: Caip2Resolver, requestFactory: WalletConnectRequest.Factory, + walletConnectPairingRepository: WalletConnectPairingRepository, walletConnectSessionRepository: WalletConnectSessionRepository, accountRepository: AccountRepository, caip2Parser: Caip2Parser ): WalletConnectSessionInteractor = RealWalletConnectSessionInteractor( caip2Resolver = caip2Resolver, walletConnectRequestFactory = requestFactory, - walletConnectSessionRepository = walletConnectSessionRepository, + walletConnectPairingRepository = walletConnectPairingRepository, accountRepository = accountRepository, - caip2Parser = caip2Parser + caip2Parser = caip2Parser, + walletConnectSessionRepository = walletConnectSessionRepository ) @Provides @@ -100,7 +108,10 @@ class WalletConnectFeatureModule { @Provides @FeatureScope - fun provideSessionUseCase(repository: WalletConnectSessionRepository): WalletConnectSessionsUseCase { - return RealWalletConnectSessionsUseCase(repository) + fun provideSessionUseCase( + pairingRepository: WalletConnectPairingRepository, + sessionRepository: WalletConnectSessionRepository + ): WalletConnectSessionsUseCase { + return RealWalletConnectSessionsUseCase(pairingRepository, sessionRepository) } } diff --git a/feature-wallet-connect-impl/src/main/java/io/novafoundation/nova/feature_wallet_connect_impl/domain/model/WalletConnectSessionAccount.kt b/feature-wallet-connect-impl/src/main/java/io/novafoundation/nova/feature_wallet_connect_impl/domain/model/WalletConnectPairingAccount.kt similarity index 60% rename from feature-wallet-connect-impl/src/main/java/io/novafoundation/nova/feature_wallet_connect_impl/domain/model/WalletConnectSessionAccount.kt rename to feature-wallet-connect-impl/src/main/java/io/novafoundation/nova/feature_wallet_connect_impl/domain/model/WalletConnectPairingAccount.kt index ec9ebe4518..cc2d933a05 100644 --- a/feature-wallet-connect-impl/src/main/java/io/novafoundation/nova/feature_wallet_connect_impl/domain/model/WalletConnectSessionAccount.kt +++ b/feature-wallet-connect-impl/src/main/java/io/novafoundation/nova/feature_wallet_connect_impl/domain/model/WalletConnectPairingAccount.kt @@ -1,6 +1,6 @@ package io.novafoundation.nova.feature_wallet_connect_impl.domain.model -class WalletConnectSessionAccount( +class WalletConnectPairingAccount( val metaId: Long, - val sessionTopic: String + val pairingTopic: String ) diff --git a/feature-wallet-connect-impl/src/main/java/io/novafoundation/nova/feature_wallet_connect_impl/domain/session/RealWalletConnectSessionInteractor.kt b/feature-wallet-connect-impl/src/main/java/io/novafoundation/nova/feature_wallet_connect_impl/domain/session/RealWalletConnectSessionInteractor.kt index d4bfd4ad49..05433fe91d 100644 --- a/feature-wallet-connect-impl/src/main/java/io/novafoundation/nova/feature_wallet_connect_impl/domain/session/RealWalletConnectSessionInteractor.kt +++ b/feature-wallet-connect-impl/src/main/java/io/novafoundation/nova/feature_wallet_connect_impl/domain/session/RealWalletConnectSessionInteractor.kt @@ -14,11 +14,12 @@ import io.novafoundation.nova.common.utils.toImmutable import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.addressIn +import io.novafoundation.nova.feature_wallet_connect_impl.data.repository.WalletConnectPairingRepository import io.novafoundation.nova.feature_wallet_connect_impl.data.repository.WalletConnectSessionRepository import io.novafoundation.nova.feature_wallet_connect_impl.domain.model.SessionChains import io.novafoundation.nova.feature_wallet_connect_impl.domain.model.SessionDappMetadata +import io.novafoundation.nova.feature_wallet_connect_impl.domain.model.WalletConnectPairingAccount import io.novafoundation.nova.feature_wallet_connect_impl.domain.model.WalletConnectSession -import io.novafoundation.nova.feature_wallet_connect_impl.domain.model.WalletConnectSessionAccount import io.novafoundation.nova.feature_wallet_connect_impl.domain.model.WalletConnectSessionDetails import io.novafoundation.nova.feature_wallet_connect_impl.domain.model.WalletConnectSessionDetails.SessionStatus import io.novafoundation.nova.feature_wallet_connect_impl.domain.model.WalletConnectSessionProposal @@ -33,7 +34,6 @@ import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.map import kotlinx.coroutines.withContext import java.util.concurrent.ConcurrentHashMap @@ -45,6 +45,7 @@ class RealWalletConnectSessionInteractor( private val caip2Resolver: Caip2Resolver, private val caip2Parser: Caip2Parser, private val walletConnectRequestFactory: WalletConnectRequest.Factory, + private val walletConnectPairingRepository: WalletConnectPairingRepository, private val walletConnectSessionRepository: WalletConnectSessionRepository, private val accountRepository: AccountRepository, ) : WalletConnectSessionInteractor { @@ -121,52 +122,58 @@ class RealWalletConnectSessionInteractor( val pairingTopic = settledSessionResponse.session.pairingTopic val pendingSessionSettlement = pendingSessionSettlementsByPairingTopic[pairingTopic] ?: return - val sessionTopic = settledSessionResponse.session.topic - val walletConnectSessionAccount = WalletConnectSessionAccount(pendingSessionSettlement.metaId, sessionTopic) - walletConnectSessionRepository.addSessionAccount(walletConnectSessionAccount) + val walletConnectPairingAccount = WalletConnectPairingAccount(metaId = pendingSessionSettlement.metaId, pairingTopic = pairingTopic) + walletConnectPairingRepository.addPairingAccount(walletConnectPairingAccount) + walletConnectSessionRepository.addSession(settledSessionResponse.session) } override suspend fun onSessionDelete(sessionDelete: Wallet.Model.SessionDelete) { if (sessionDelete !is Wallet.Model.SessionDelete.Success) return - walletConnectSessionRepository.deleteSessionAccount(sessionDelete.topic) + walletConnectSessionRepository.removeSession(sessionDelete.topic) } - override suspend fun getSessionAccount(sessionTopic: String): WalletConnectSessionAccount? { - return walletConnectSessionRepository.getSessionAccount(sessionTopic) + override suspend fun getSession(sessionTopic: String): Wallet.Model.Session? { + return walletConnectSessionRepository.getSession(sessionTopic) + } + + override suspend fun getPairingAccount(pairingTopic: String): WalletConnectPairingAccount? { + return walletConnectPairingRepository.getPairingAccount(pairingTopic) } override fun activeSessionsFlow(metaId: Long?): Flow> { - return walletConnectSessionRepository.allSessionAccountsFlow().map { sessionAccounts -> - val activeSessions = Web3Wallet.getListOfActiveSessions() + return combine( + walletConnectSessionRepository.allSessionsFlow(), + walletConnectPairingRepository.allPairingAccountsFlow() + ) { activeSessions, pairingAccounts -> val metaAccounts = if (metaId == null) { accountRepository.allMetaAccounts() } else { listOf(accountRepository.getMetaAccount(metaId)) } - createWalletSessions(activeSessions, metaAccounts, sessionAccounts) + createWalletSessions(activeSessions, metaAccounts, pairingAccounts) } } override fun activeSessionFlow(sessionTopic: String): Flow { - val sessionAccountFlow = walletConnectSessionRepository.sessionAccountFlow(sessionTopic) + val sessionFlow = walletConnectSessionRepository.sessionFlow(sessionTopic) val chainsWrappedFlow = flowOf { caip2Resolver.chainsByCaip2() } - return combine(sessionAccountFlow, chainsWrappedFlow) { sessionAccount, chainsByCaip2 -> - if (sessionAccount == null) return@combine null + return combine(sessionFlow, chainsWrappedFlow) { session, chainsByCaip2 -> + if (session == null) return@combine null - val activeSession = Web3Wallet.getActiveSessionByTopic(sessionTopic) ?: return@combine null - val metaAccount = accountRepository.getMetaAccount(sessionAccount.metaId) + val pairingAccount = walletConnectPairingRepository.getPairingAccount(session.pairingTopic) ?: return@combine null + val metaAccount = accountRepository.getMetaAccount(pairingAccount.metaId) - createWalletSessionDetails(activeSession, metaAccount, chainsByCaip2) + createWalletSessionDetails(session, metaAccount, chainsByCaip2) } } override suspend fun disconnect(sessionTopic: String): Result<*> { return withContext(Dispatchers.Default) { Web3Wallet.disconnectSession(sessionTopic).onSuccess { - walletConnectSessionRepository.deleteSessionAccount(sessionTopic) + walletConnectSessionRepository.removeSession(sessionTopic) } } } @@ -214,14 +221,14 @@ class RealWalletConnectSessionInteractor( private fun createWalletSessions( sessions: List, metaAccounts: List, - sessionAccounts: List + pairingAccounts: List ): List { val metaAccountsById = metaAccounts.associateBy(MetaAccount::id) - val sessionAccountsByTopic = sessionAccounts.associateBy(WalletConnectSessionAccount::sessionTopic) + val pairingAccountByPairingTopic = pairingAccounts.associateBy(WalletConnectPairingAccount::pairingTopic) return sessions.mapNotNull { session -> - val sessionAccount = sessionAccountsByTopic[session.topic] ?: return@mapNotNull null - val metaAccount = metaAccountsById[sessionAccount.metaId] ?: return@mapNotNull null + val pairingAccount = pairingAccountByPairingTopic[session.pairingTopic] ?: return@mapNotNull null + val metaAccount = metaAccountsById[pairingAccount.metaId] ?: return@mapNotNull null WalletConnectSession( connectedMetaAccount = metaAccount, diff --git a/feature-wallet-connect-impl/src/main/java/io/novafoundation/nova/feature_wallet_connect_impl/domain/session/RealWalletConnectSessionsUseCase.kt b/feature-wallet-connect-impl/src/main/java/io/novafoundation/nova/feature_wallet_connect_impl/domain/session/RealWalletConnectSessionsUseCase.kt index 41e135f3ec..86690ec385 100644 --- a/feature-wallet-connect-impl/src/main/java/io/novafoundation/nova/feature_wallet_connect_impl/domain/session/RealWalletConnectSessionsUseCase.kt +++ b/feature-wallet-connect-impl/src/main/java/io/novafoundation/nova/feature_wallet_connect_impl/domain/session/RealWalletConnectSessionsUseCase.kt @@ -1,23 +1,31 @@ package io.novafoundation.nova.feature_wallet_connect_impl.domain.session +import com.walletconnect.android.CoreClient import com.walletconnect.web3.wallet.client.Web3Wallet +import io.novafoundation.nova.common.utils.mapToSet import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_wallet_connect_api.domain.sessions.WalletConnectSessionsUseCase +import io.novafoundation.nova.feature_wallet_connect_impl.data.repository.WalletConnectPairingRepository import io.novafoundation.nova.feature_wallet_connect_impl.data.repository.WalletConnectSessionRepository import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.withContext internal class RealWalletConnectSessionsUseCase( + private val pairingRepository: WalletConnectPairingRepository, private val sessionRepository: WalletConnectSessionRepository, ) : WalletConnectSessionsUseCase { override fun activeSessionsNumberFlow(): Flow { - return sessionRepository.numberOfSessionAccountsFlow() + return sessionRepository.numberOfSessionsFlow() } override fun activeSessionsNumberFlow(metaAccount: MetaAccount): Flow { - return sessionRepository.numberOfSessionAccountsFlow(metaAccount) + return pairingRepository.pairingAccountsByMetaIdFlow(metaAccount.id).flatMapLatest { pairings -> + val pairingTopics = pairings.mapToSet { it.pairingTopic } + sessionRepository.numberOfSessionsFlow(pairingTopics) + } } override suspend fun activeSessionsNumber(): Int { @@ -25,8 +33,13 @@ internal class RealWalletConnectSessionsUseCase( } override suspend fun syncActiveSessions() = withContext(Dispatchers.Default) { - val activeSessionTopics = Web3Wallet.getListOfActiveSessions().map { it.topic } + val activePairingTopics = CoreClient.Pairing.getPairings() + .filter { it.isActive } + .map { it.topic } - sessionRepository.removeAllSessionsOtherThan(activeSessionTopics) + pairingRepository.removeAllPairingsOtherThan(activePairingTopics) + + val activeSessionTopics = Web3Wallet.getListOfActiveSessions() + sessionRepository.setSessions(activeSessionTopics) } } diff --git a/feature-wallet-connect-impl/src/main/java/io/novafoundation/nova/feature_wallet_connect_impl/domain/session/WalletConnectSessionInteractor.kt b/feature-wallet-connect-impl/src/main/java/io/novafoundation/nova/feature_wallet_connect_impl/domain/session/WalletConnectSessionInteractor.kt index 4f356c0218..7c3aab628b 100644 --- a/feature-wallet-connect-impl/src/main/java/io/novafoundation/nova/feature_wallet_connect_impl/domain/session/WalletConnectSessionInteractor.kt +++ b/feature-wallet-connect-impl/src/main/java/io/novafoundation/nova/feature_wallet_connect_impl/domain/session/WalletConnectSessionInteractor.kt @@ -3,8 +3,8 @@ package io.novafoundation.nova.feature_wallet_connect_impl.domain.session import com.walletconnect.web3.wallet.client.Wallet import com.walletconnect.web3.wallet.client.Wallet.Model.SessionProposal import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount +import io.novafoundation.nova.feature_wallet_connect_impl.domain.model.WalletConnectPairingAccount import io.novafoundation.nova.feature_wallet_connect_impl.domain.model.WalletConnectSession -import io.novafoundation.nova.feature_wallet_connect_impl.domain.model.WalletConnectSessionAccount import io.novafoundation.nova.feature_wallet_connect_impl.domain.model.WalletConnectSessionDetails import io.novafoundation.nova.feature_wallet_connect_impl.domain.model.WalletConnectSessionProposal import io.novafoundation.nova.feature_wallet_connect_impl.domain.session.requests.WalletConnectRequest @@ -27,7 +27,9 @@ interface WalletConnectSessionInteractor { suspend fun onSessionDelete(sessionDelete: Wallet.Model.SessionDelete) - suspend fun getSessionAccount(sessionTopic: String): WalletConnectSessionAccount? + suspend fun getSession(sessionTopic: String): Wallet.Model.Session? + + suspend fun getPairingAccount(pairingTopic: String): WalletConnectPairingAccount? fun activeSessionsFlow(metaId: Long?): Flow> diff --git a/feature-wallet-connect-impl/src/main/java/io/novafoundation/nova/feature_wallet_connect_impl/presentation/service/RealWalletConnectService.kt b/feature-wallet-connect-impl/src/main/java/io/novafoundation/nova/feature_wallet_connect_impl/presentation/service/RealWalletConnectService.kt index d43314fad6..bccf4aa3e8 100644 --- a/feature-wallet-connect-impl/src/main/java/io/novafoundation/nova/feature_wallet_connect_impl/presentation/service/RealWalletConnectService.kt +++ b/feature-wallet-connect-impl/src/main/java/io/novafoundation/nova/feature_wallet_connect_impl/presentation/service/RealWalletConnectService.kt @@ -77,8 +77,8 @@ internal class RealWalletConnectService( } private suspend fun handleSessionRequest(sessionRequest: Wallet.Model.SessionRequest) { - val sdkSession = Web3Wallet.getActiveSessionByTopic(sessionRequest.topic) ?: run { respondNoSession(sessionRequest); return } - val appSession = interactor.getSessionAccount(sessionRequest.topic) ?: run { respondNoSession(sessionRequest); return } + val sdkSession = interactor.getSession(sessionRequest.topic) ?: run { respondNoSession(sessionRequest); return } + val appPairing = interactor.getPairingAccount(sdkSession.pairingTopic) ?: run { respondNoSession(sessionRequest); return } val walletConnectRequest = interactor.parseSessionRequest(sessionRequest) .onFailure { error -> @@ -94,7 +94,7 @@ internal class RealWalletConnectService( ExternalSignPayload( signRequest = walletConnectRequest.toExternalSignRequest(), dappMetadata = mapWalletConnectSessionToSignDAppMetadata(sdkSession), - wallet = ExternalSignWallet.WithId(appSession.metaId) + wallet = ExternalSignWallet.WithId(appPairing.metaId) ) ) } diff --git a/feature-wallet-connect-impl/src/main/java/io/novafoundation/nova/feature_wallet_connect_impl/presentation/sessions/approve/WalletConnectApproveSessionViewModel.kt b/feature-wallet-connect-impl/src/main/java/io/novafoundation/nova/feature_wallet_connect_impl/presentation/sessions/approve/WalletConnectApproveSessionViewModel.kt index 7cd284276e..20a86760e9 100644 --- a/feature-wallet-connect-impl/src/main/java/io/novafoundation/nova/feature_wallet_connect_impl/presentation/sessions/approve/WalletConnectApproveSessionViewModel.kt +++ b/feature-wallet-connect-impl/src/main/java/io/novafoundation/nova/feature_wallet_connect_impl/presentation/sessions/approve/WalletConnectApproveSessionViewModel.kt @@ -50,12 +50,17 @@ class WalletConnectApproveSessionViewModel( private val selectWalletMixinFactory: SelectWalletMixin.Factory ) : BaseViewModel() { - val selectWalletMixin = selectWalletMixinFactory.create(this) + private val proposal = responder.requireLastInput() + + val selectWalletMixin = selectWalletMixinFactory.create( + coroutineScope = this, + selectionParams = ::walletSelectionParams + ) private val processState = MutableStateFlow(ProgressState.IDLE) private val sessionProposalFlow = flowOf { - interactor.resolveSessionProposal(responder.requireLastInput()) + interactor.resolveSessionProposal(proposal) }.shareInBackground() val sessionMetadata = sessionProposalFlow.map { it.dappMetadata } @@ -118,6 +123,22 @@ class WalletConnectApproveSessionViewModel( _showNetworksBottomSheet.value = networksListFlow.first().event() } + private suspend fun walletSelectionParams(): SelectWalletMixin.SelectionParams { + val pairingAccount = interactor.getPairingAccount(proposal.pairingTopic) + + return if (pairingAccount != null) { + SelectWalletMixin.SelectionParams( + selectionAllowed = false, + initialSelection = SelectWalletMixin.InitialSelection.SpecificWallet(pairingAccount.metaId) + ) + } else { + SelectWalletMixin.SelectionParams( + selectionAllowed = true, + initialSelection = SelectWalletMixin.InitialSelection.ActiveWallet + ) + } + } + private fun constructSessionAlerts(metaAccount: MetaAccount, sessionProposal: WalletConnectSessionProposal): SessionAlerts { val chains = sessionProposal.resolvedChains diff --git a/feature-wallet-connect-impl/src/main/res/layout/fragment_wc_session_approve.xml b/feature-wallet-connect-impl/src/main/res/layout/fragment_wc_session_approve.xml index e542cf4779..dc117e8cc8 100644 --- a/feature-wallet-connect-impl/src/main/res/layout/fragment_wc_session_approve.xml +++ b/feature-wallet-connect-impl/src/main/res/layout/fragment_wc_session_approve.xml @@ -87,7 +87,7 @@ android:layout_marginEnd="16dp" android:gravity="center_horizontal" android:text="@string/dapp_confirm_authorize_subtitle" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/wcApproveSessionTitle" /> diff --git a/feature-wallet-connect-impl/src/main/res/layout/item_bottom_sheet_wc_networks_label.xml b/feature-wallet-connect-impl/src/main/res/layout/item_bottom_sheet_wc_networks_label.xml index 24e5ca7e19..eef1ed6159 100644 --- a/feature-wallet-connect-impl/src/main/res/layout/item_bottom_sheet_wc_networks_label.xml +++ b/feature-wallet-connect-impl/src/main/res/layout/item_bottom_sheet_wc_networks_label.xml @@ -3,7 +3,7 @@ android:layout_width="match_parent" xmlns:tools="http://schemas.android.com/tools" tools:background="@color/secondary_screen_background" - android:textColor="@color/text_tertiary" + android:textColor="@color/text_secondary" tools:text="@string/common_required" android:layout_marginStart="16dp" style="@style/TextAppearance.NovaFoundation.Regular.Footnote" diff --git a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/mappers/AssetMappers.kt b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/mappers/AssetMappers.kt index cffbbc9b1c..d6c904091a 100644 --- a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/mappers/AssetMappers.kt +++ b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/mappers/AssetMappers.kt @@ -1,12 +1,17 @@ package io.novafoundation.nova.feature_wallet_impl.data.mappers import io.novafoundation.nova.common.utils.orZero +import io.novafoundation.nova.core_db.model.AssetLocal +import io.novafoundation.nova.core_db.model.AssetLocal.EDCountingModeLocal +import io.novafoundation.nova.core_db.model.AssetLocal.TransferableModeLocal import io.novafoundation.nova.core_db.model.AssetWithToken import io.novafoundation.nova.core_db.model.CurrencyLocal import io.novafoundation.nova.core_db.model.TokenLocal import io.novafoundation.nova.core_db.model.TokenWithCurrency import io.novafoundation.nova.feature_currency_api.presentation.mapper.mapCurrencyFromLocal import io.novafoundation.nova.feature_wallet_api.domain.model.Asset +import io.novafoundation.nova.feature_wallet_api.domain.model.Asset.EDCountingMode +import io.novafoundation.nova.feature_wallet_api.domain.model.Asset.TransferableMode import io.novafoundation.nova.feature_wallet_api.domain.model.CoinRateChange import io.novafoundation.nova.feature_wallet_api.domain.model.Token import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain @@ -46,7 +51,23 @@ fun mapAssetLocalToAsset( reservedInPlanks = asset?.reservedInPlanks.orZero(), bondedInPlanks = asset?.bondedInPlanks.orZero(), unbondingInPlanks = asset?.unbondingInPlanks.orZero(), - redeemableInPlanks = asset?.redeemableInPlanks.orZero() + redeemableInPlanks = asset?.redeemableInPlanks.orZero(), + transferableMode = mapTransferableModeFromLocal(asset?.transferableMode), + edCountingMode = mapEdCountingModeFromLocal(asset?.edCountingMode) ) } } + +private fun mapTransferableModeFromLocal(modeLocal: TransferableModeLocal?): TransferableMode { + return when (modeLocal ?: AssetLocal.defaultTransferableMode()) { + TransferableModeLocal.REGULAR -> TransferableMode.REGULAR + TransferableModeLocal.HOLDS_AND_FREEZES -> TransferableMode.HOLDS_AND_FREEZES + } +} + +private fun mapEdCountingModeFromLocal(modeLocal: EDCountingModeLocal?): EDCountingMode { + return when (modeLocal ?: AssetLocal.defaultEdCountingMode()) { + EDCountingModeLocal.TOTAL -> EDCountingMode.TOTAL + EDCountingModeLocal.FREE -> EDCountingMode.FREE + } +} diff --git a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/balances/equilibrium/EquilibriumAssetBalance.kt b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/balances/equilibrium/EquilibriumAssetBalance.kt index ed3cacfe27..4c3601be46 100644 --- a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/balances/equilibrium/EquilibriumAssetBalance.kt +++ b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/balances/equilibrium/EquilibriumAssetBalance.kt @@ -24,6 +24,8 @@ import io.novafoundation.nova.core.updater.SharedRequestsBuilder import io.novafoundation.nova.core_db.dao.AssetDao import io.novafoundation.nova.core_db.dao.LockDao import io.novafoundation.nova.core_db.model.AssetLocal +import io.novafoundation.nova.core_db.model.AssetLocal.EDCountingModeLocal +import io.novafoundation.nova.core_db.model.AssetLocal.TransferableModeLocal import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_wallet_api.data.cache.AssetCache import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.balances.AssetBalance @@ -140,16 +142,18 @@ class EquilibriumAssetBalance( val reservedByAssetId = reservedBalancesWithBlocks.associateBy { it.assetId } val diff = assetCache.updateAssetsByChain(metaAccount, chain) { asset: Chain.Asset -> - val free = freeByAssetId[asset.id]?.balance - val reserved = reservedByAssetId[asset.id]?.reservedBalance - val locks = if (asset.isUtilityAsset) assetBalances.lock else BigInteger.ZERO + val free = freeByAssetId[asset.id]?.balance.orZero() + val reserved = reservedByAssetId[asset.id]?.reservedBalance.orZero() + val locks = if (asset.isUtilityAsset) assetBalances.lock.orZero() else BigInteger.ZERO AssetLocal( assetId = asset.id, chainId = asset.chainId, metaId = metaAccount.id, - freeInPlanks = free.orZero(), - reservedInPlanks = reserved.orZero(), - frozenInPlanks = locks.orZero(), + freeInPlanks = free, + reservedInPlanks = reserved, + frozenInPlanks = locks, + transferableMode = TransferableModeLocal.REGULAR, + edCountingMode = EDCountingModeLocal.TOTAL, redeemableInPlanks = BigInteger.ZERO, bondedInPlanks = BigInteger.ZERO, unbondingInPlanks = BigInteger.ZERO diff --git a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/balances/evnNative/EvmNativeAssetBalance.kt b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/balances/evmNative/EvmNativeAssetBalance.kt similarity index 99% rename from feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/balances/evnNative/EvmNativeAssetBalance.kt rename to feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/balances/evmNative/EvmNativeAssetBalance.kt index 92fe919a4b..42d1ea7702 100644 --- a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/balances/evnNative/EvmNativeAssetBalance.kt +++ b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/balances/evmNative/EvmNativeAssetBalance.kt @@ -1,4 +1,4 @@ -package io.novafoundation.nova.feature_wallet_impl.data.network.blockchain.assets.balances.evnNative +package io.novafoundation.nova.feature_wallet_impl.data.network.blockchain.assets.balances.evmNative import io.novafoundation.nova.core.ethereum.Web3Api import io.novafoundation.nova.core.updater.SharedRequestsBuilder diff --git a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/balances/orml/OrmlAssetBalance.kt b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/balances/orml/OrmlAssetBalance.kt index edf66c756f..86dc8c1aa9 100644 --- a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/balances/orml/OrmlAssetBalance.kt +++ b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/balances/orml/OrmlAssetBalance.kt @@ -4,6 +4,8 @@ import io.novafoundation.nova.common.utils.decodeValue import io.novafoundation.nova.common.utils.tokens import io.novafoundation.nova.core.updater.SharedRequestsBuilder import io.novafoundation.nova.core_db.dao.LockDao +import io.novafoundation.nova.core_db.model.AssetLocal.EDCountingModeLocal +import io.novafoundation.nova.core_db.model.AssetLocal.TransferableModeLocal import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_wallet_api.data.cache.AssetCache import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.balances.AssetBalance @@ -96,12 +98,16 @@ class OrmlAssetBalance( metaId: Long, chainAsset: Chain.Asset, ormlAccountData: OrmlAccountData - ) = assetCache.updateAsset(metaId, chainAsset) { - it.copy( - frozenInPlanks = ormlAccountData.frozen, - freeInPlanks = ormlAccountData.free, - reservedInPlanks = ormlAccountData.reserved - ) + ) = assetCache.updateAsset(metaId, chainAsset) { local -> + with(ormlAccountData) { + local.copy( + frozenInPlanks = frozen, + freeInPlanks = free, + reservedInPlanks = reserved, + transferableMode = TransferableModeLocal.REGULAR, + edCountingMode = EDCountingModeLocal.TOTAL, + ) + } } private fun RuntimeSnapshot.ormlBalanceKey(accountId: AccountId, chainAsset: Chain.Asset): String { diff --git a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/balances/statemine/StatemineAssetBalance.kt b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/balances/statemine/StatemineAssetBalance.kt index 3ae2bc1229..0f8382c3e7 100644 --- a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/balances/statemine/StatemineAssetBalance.kt +++ b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/balances/statemine/StatemineAssetBalance.kt @@ -3,6 +3,8 @@ package io.novafoundation.nova.feature_wallet_impl.data.network.blockchain.asset import io.novafoundation.nova.common.utils.decodeValue import io.novafoundation.nova.core.storage.StorageCache import io.novafoundation.nova.core.updater.SharedRequestsBuilder +import io.novafoundation.nova.core_db.model.AssetLocal.EDCountingModeLocal +import io.novafoundation.nova.core_db.model.AssetLocal.TransferableModeLocal import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_wallet_api.data.cache.AssetCache import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.balances.AssetBalance @@ -136,10 +138,13 @@ class StatemineAssetBalance( } else { BigInteger.ZERO } + val freeBalance = assetAccount.balance it.copy( frozenInPlanks = frozenBalance, - freeInPlanks = assetAccount.balance + freeInPlanks = freeBalance, + transferableMode = TransferableModeLocal.REGULAR, + edCountingMode = EDCountingModeLocal.TOTAL, ) } } diff --git a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/transfers/validations/Common.kt b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/transfers/validations/Common.kt index 922adf097e..cc7ae9474b 100644 --- a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/transfers/validations/Common.kt +++ b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/transfers/validations/Common.kt @@ -10,6 +10,7 @@ import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.t import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.tranfers.AssetTransfersValidationSystemBuilder import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.tranfers.recipientOrNull import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.tranfers.sendingAmountInCommissionAsset +import io.novafoundation.nova.feature_wallet_api.domain.model.balanceCountedTowardsED import io.novafoundation.nova.feature_wallet_api.domain.validation.AmountProducer import io.novafoundation.nova.feature_wallet_api.domain.validation.EnoughTotalToStayAboveEDValidationFactory import io.novafoundation.nova.feature_wallet_api.domain.validation.PhishingValidationFactory @@ -51,7 +52,7 @@ fun AssetTransfersValidationSystemBuilder.sufficientCommissionBalanceToStayAbove ) { enoughTotalToStayAboveEDValidationFactory.validate( fee = { it.originFee }, - balance = { it.originCommissionAsset.total }, + balance = { it.originCommissionAsset.balanceCountedTowardsED() }, chainWithAsset = { ChainWithAsset(it.transfer.originChain, it.transfer.originChain.commissionAsset) }, error = { payload, _ -> AssetTransferValidationFailure.NotEnoughFunds.ToStayAboveED(payload.transfer.originChain.commissionAsset) } ) @@ -74,7 +75,7 @@ fun AssetTransfersValidationSystemBuilder.doNotCrossExistentialDeposit( fee: AmountProducer, extraAmount: AmountProducer, ) = doNotCrossExistentialDeposit( - totalBalance = { it.originUsedAsset.total }, + countableTowardsEdBalance = { it.originUsedAsset.balanceCountedTowardsED() }, fee = fee, extraAmount = extraAmount, existentialDeposit = { assetSourceRegistry.existentialDepositForUsedAsset(it.transfer) }, diff --git a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/crosschain/PalletXcmRepository.kt b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/crosschain/PalletXcmRepository.kt index 99dd6bac0e..0ba03a6748 100644 --- a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/crosschain/PalletXcmRepository.kt +++ b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/crosschain/PalletXcmRepository.kt @@ -1,10 +1,15 @@ package io.novafoundation.nova.feature_wallet_impl.data.network.crosschain import io.novafoundation.nova.common.utils.enumValueOfOrNull +import io.novafoundation.nova.common.utils.xcmPalletName import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId import io.novafoundation.nova.runtime.storage.source.StorageDataSource import jp.co.soramitsu.fearless_utils.runtime.definitions.types.composite.DictEnum import jp.co.soramitsu.fearless_utils.runtime.definitions.types.skipAliases +import jp.co.soramitsu.fearless_utils.runtime.metadata.RuntimeMetadata +import jp.co.soramitsu.fearless_utils.runtime.metadata.callOrNull +import jp.co.soramitsu.fearless_utils.runtime.metadata.module.MetadataFunction +import jp.co.soramitsu.fearless_utils.runtime.metadata.moduleOrNull enum class XcmVersion { V0, V1, V2, V3; @@ -24,20 +29,34 @@ class RealPalletXcmRepository( ) : PalletXcmRepository { override suspend fun lowestPresentMultiLocationVersion(chainId: ChainId): XcmVersion? { - return lowestPresentXcmTypeVersion(chainId, "xcm.VersionedMultiLocation") + return lowestPresentXcmTypeVersionFromCallArgument( + chainId = chainId, + getCall = { it.moduleOrNull(it.xcmPalletName())?.callOrNull("reserve_transfer_assets") }, + argumentName = "dest" + ) } override suspend fun lowestPresentMultiAssetsVersion(chainId: ChainId): XcmVersion? { - return lowestPresentXcmTypeVersion(chainId, "xcm.VersionedMultiAssets") + return lowestPresentXcmTypeVersionFromCallArgument( + chainId = chainId, + getCall = { it.moduleOrNull(it.xcmPalletName())?.callOrNull("reserve_transfer_assets") }, + argumentName = "assets" + ) } override suspend fun lowestPresentMultiAssetVersion(chainId: ChainId): XcmVersion? { - return lowestPresentXcmTypeVersion(chainId, "xcm.VersionedMultiAsset") + return lowestPresentMultiAssetsVersion(chainId) } - private suspend fun lowestPresentXcmTypeVersion(chainId: ChainId, typeName: String): XcmVersion? { + private suspend fun lowestPresentXcmTypeVersionFromCallArgument( + chainId: ChainId, + getCall: (RuntimeMetadata) -> MetadataFunction?, + argumentName: String, + ): XcmVersion? { return remoteStorageDataSource.query(chainId) { - val type = runtime.typeRegistry[typeName]?.skipAliases() as? DictEnum ?: return@query null + val call = getCall(runtime.metadata) ?: return@query null + val argument = call.arguments.find { it.name == argumentName } ?: return@query null + val type = argument.type?.skipAliases() as? DictEnum ?: return@query null val allSupportedVersions = type.elements.values.map { it.name } val leastSupportedVersion = allSupportedVersions.min() diff --git a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/di/modules/EvmNativeAssetsModule.kt b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/di/modules/EvmNativeAssetsModule.kt index 350836e181..2aeaa8c339 100644 --- a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/di/modules/EvmNativeAssetsModule.kt +++ b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/di/modules/EvmNativeAssetsModule.kt @@ -9,7 +9,7 @@ import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.A import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.AssetSourceRegistry import io.novafoundation.nova.feature_wallet_api.domain.interfaces.CoinPriceRepository import io.novafoundation.nova.feature_wallet_impl.data.network.blockchain.assets.StaticAssetSource -import io.novafoundation.nova.feature_wallet_impl.data.network.blockchain.assets.balances.evnNative.EvmNativeAssetBalance +import io.novafoundation.nova.feature_wallet_impl.data.network.blockchain.assets.balances.evmNative.EvmNativeAssetBalance import io.novafoundation.nova.feature_wallet_impl.data.network.blockchain.assets.history.evmNative.EvmNativeAssetHistory import io.novafoundation.nova.feature_wallet_impl.data.network.blockchain.assets.transfers.evmNative.EvmNativeAssetTransfers import io.novafoundation.nova.feature_wallet_impl.data.network.etherscan.EtherscanTransactionsApi diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/ExtrinsicValidityUseCase.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/ExtrinsicValidityUseCase.kt index 5f5588f21b..a60fbc89c6 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/ExtrinsicValidityUseCase.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/ExtrinsicValidityUseCase.kt @@ -56,7 +56,7 @@ fun LifecycleOwner.startExtrinsicValidityTimer( customMessageFormat = timerFormat, lifecycle = lifecycle, onTick = { view, _ -> - val textColorRes = if (validityPeriod.closeToExpire()) R.color.text_negative else R.color.text_tertiary + val textColorRes = if (validityPeriod.closeToExpire()) R.color.text_negative else R.color.text_secondary view.setTextColorRes(textColorRes) }, 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 7de147edbf..8d50bf7078 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 @@ -1,6 +1,7 @@ package io.novafoundation.nova.runtime.multiNetwork.chain.mappers import com.google.gson.Gson +import io.novafoundation.nova.common.utils.asGsonParsedIntOrNull import io.novafoundation.nova.common.utils.asGsonParsedLongOrNull import io.novafoundation.nova.core_db.model.chain.AssetSourceLocal import io.novafoundation.nova.core_db.model.chain.ChainAssetLocal @@ -26,6 +27,7 @@ private const val CHAIN_THEME_COLOR = "themeColor" private const val CHAIN_STAKING_WIKI = "stakingWiki" private const val DEFAULT_BLOCK_TIME = "defaultBlockTime" private const val RELAYCHAIN_AS_NATIVE = "relaychainAsNative" +private const val MAX_ELECTING_VOTES = "stakingMaxElectingVoters" fun mapRemoteChainToLocal( chainRemote: ChainRemote, @@ -44,7 +46,8 @@ fun mapRemoteChainToLocal( themeColor = (it[CHAIN_THEME_COLOR] as? String), stakingWiki = (it[CHAIN_STAKING_WIKI] as? String), defaultBlockTimeMillis = it[DEFAULT_BLOCK_TIME].asGsonParsedLongOrNull(), - relaychainAsNative = it[RELAYCHAIN_AS_NATIVE] as? Boolean + relaychainAsNative = it[RELAYCHAIN_AS_NATIVE] as? Boolean, + stakingMaxElectingVoters = it[MAX_ELECTING_VOTES].asGsonParsedIntOrNull(), ) } 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 0ff873f478..7a13baa6eb 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 @@ -43,7 +43,8 @@ data class Chain( val themeColor: String?, val stakingWiki: String?, val defaultBlockTimeMillis: Long?, - val relaychainAsNative: Boolean? + val relaychainAsNative: Boolean?, + val stakingMaxElectingVoters: Int?, ) data class Types( diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/runtime/RuntimeFactory.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/runtime/RuntimeFactory.kt index 476598a418..9089be66fa 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/runtime/RuntimeFactory.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/runtime/RuntimeFactory.kt @@ -150,5 +150,5 @@ class RuntimeFactory( private fun fromJson(types: String): TypeDefinitionsTree = gson.fromJson(types, TypeDefinitionsTree::class.java) - private fun allSiTypeMappings() = SiTypeMapping.default() + SiVoteTypeMapping + private fun allSiTypeMappings() = SiTypeMapping.default() + SiVoteTypeMapping() } diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/runtime/types/custom/vote/SiVoteTypeMapping.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/runtime/types/custom/vote/SiVoteTypeMapping.kt index f2544ad9c3..310fefd381 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/runtime/types/custom/vote/SiVoteTypeMapping.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/runtime/types/custom/vote/SiVoteTypeMapping.kt @@ -1,26 +1,12 @@ package io.novafoundation.nova.runtime.multiNetwork.runtime.types.custom.vote -import jp.co.soramitsu.fearless_utils.runtime.definitions.registry.TypePresetBuilder -import jp.co.soramitsu.fearless_utils.runtime.definitions.types.Type -import jp.co.soramitsu.fearless_utils.runtime.definitions.v14.typeMapping.SiTypeMapping -import jp.co.soramitsu.fearless_utils.runtime.metadata.v14.PortableType -import jp.co.soramitsu.fearless_utils.runtime.metadata.v14.path -import jp.co.soramitsu.fearless_utils.runtime.metadata.v14.type -import jp.co.soramitsu.fearless_utils.scale.EncodableStruct +import jp.co.soramitsu.fearless_utils.runtime.definitions.v14.typeMapping.ReplaceTypesSiTypeMapping -object SiVoteTypeMapping : SiTypeMapping { +fun SiVoteTypeMapping(): ReplaceTypesSiTypeMapping { + val voteType = VoteType("NovaWallet.ConvictionVote") - override fun map( - originalDefinition: EncodableStruct, - suggestedTypeName: String, - typesBuilder: TypePresetBuilder - ): Type<*>? { - val path = originalDefinition.type.path - - return if (path.isNotEmpty() && path.last() == "Vote") { - VoteType(suggestedTypeName) - } else { - null - } - } + return ReplaceTypesSiTypeMapping( + "pallet_democracy.vote.Vote" to voteType, + "pallet_conviction_voting.vote.Vote" to voteType + ) } diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/network/updaters/SingleChainUpdateSystem.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/network/updaters/SingleChainUpdateSystem.kt index 7bc454118f..883565bceb 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/network/updaters/SingleChainUpdateSystem.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/network/updaters/SingleChainUpdateSystem.kt @@ -5,10 +5,12 @@ import io.novafoundation.nova.runtime.ethereum.StorageSharedRequestsBuilderFacto import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.state.SelectedAssetOptionSharedState import io.novafoundation.nova.runtime.state.SelectedAssetOptionSharedState.SupportedAssetOption +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.shareIn abstract class SingleChainUpdateSystem( chainRegistry: ChainRegistry, @@ -18,13 +20,15 @@ abstract class SingleChainUpdateSystem( abstract fun getUpdaters(selectedAssetOption: SupportedAssetOption): Collection> - override fun start(): Flow = singleAssetSharedState.selectedOption.flatMapLatest { selectedOption -> + private val updateFlow = singleAssetSharedState.selectedOption.flatMapLatest { selectedOption -> val chain = selectedOption.assetWithChain.chain val updaters = getUpdaters(selectedOption) runUpdaters(chain, updaters) - }.flowOn(Dispatchers.Default) + }.shareIn(CoroutineScope(Dispatchers.Default), replay = 1, started = SharingStarted.WhileSubscribed()) + + override fun start(): Flow = updateFlow } class ConstantSingleChainUpdateSystem(