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 f8d76b8131..0997f05574 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 @@ -2,16 +2,16 @@ package io.novafoundation.nova.app.di.app.navigation 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.PolkadotVaultVariantSignCommunicatorImpl -import io.novafoundation.nova.app.root.navigation.account.SelectAddressCommunicatorImpl -import io.novafoundation.nova.app.root.navigation.account.SelectMultipleWalletsCommunicatorImpl -import io.novafoundation.nova.app.root.navigation.account.SelectWalletCommunicatorImpl -import io.novafoundation.nova.app.root.navigation.cloudBackup.ChangeBackupPasswordCommunicatorImpl -import io.novafoundation.nova.app.root.navigation.cloudBackup.RestoreBackupPasswordCommunicatorImpl -import io.novafoundation.nova.app.root.navigation.cloudBackup.SyncWalletsBackupPasswordCommunicatorImpl -import io.novafoundation.nova.app.root.navigation.pincode.PinCodeTwoFactorVerificationCommunicatorImpl +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry +import io.novafoundation.nova.app.root.navigation.navigators.Navigator +import io.novafoundation.nova.app.root.navigation.navigators.account.PolkadotVaultVariantSignCommunicatorImpl +import io.novafoundation.nova.app.root.navigation.navigators.account.SelectAddressCommunicatorImpl +import io.novafoundation.nova.app.root.navigation.navigators.account.SelectMultipleWalletsCommunicatorImpl +import io.novafoundation.nova.app.root.navigation.navigators.account.SelectWalletCommunicatorImpl +import io.novafoundation.nova.app.root.navigation.navigators.cloudBackup.ChangeBackupPasswordCommunicatorImpl +import io.novafoundation.nova.app.root.navigation.navigators.cloudBackup.RestoreBackupPasswordCommunicatorImpl +import io.novafoundation.nova.app.root.navigation.navigators.cloudBackup.SyncWalletsBackupPasswordCommunicatorImpl +import io.novafoundation.nova.app.root.navigation.navigators.pincode.PinCodeTwoFactorVerificationCommunicatorImpl import io.novafoundation.nova.common.di.scope.ApplicationScope import io.novafoundation.nova.common.sequrity.verification.PinCodeTwoFactorVerificationCommunicator import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectAddress.SelectAddressCommunicator @@ -30,34 +30,34 @@ class AccountNavigationModule { @Provides @ApplicationScope fun providePinCodeTwoFactorVerificationCommunicator( - navigationHolder: NavigationHolder - ): PinCodeTwoFactorVerificationCommunicator = PinCodeTwoFactorVerificationCommunicatorImpl(navigationHolder) + navigationHoldersRegistry: NavigationHoldersRegistry + ): PinCodeTwoFactorVerificationCommunicator = PinCodeTwoFactorVerificationCommunicatorImpl(navigationHoldersRegistry) @Provides @ApplicationScope fun provideSelectWalletCommunicator( - navigationHolder: NavigationHolder - ): SelectWalletCommunicator = SelectWalletCommunicatorImpl(navigationHolder) + navigationHoldersRegistry: NavigationHoldersRegistry + ): SelectWalletCommunicator = SelectWalletCommunicatorImpl(navigationHoldersRegistry) @Provides @ApplicationScope fun provideParitySignerCommunicator( - navigationHolder: NavigationHolder - ): PolkadotVaultVariantSignCommunicator = PolkadotVaultVariantSignCommunicatorImpl(navigationHolder) + navigationHoldersRegistry: NavigationHoldersRegistry + ): PolkadotVaultVariantSignCommunicator = PolkadotVaultVariantSignCommunicatorImpl(navigationHoldersRegistry) @Provides @ApplicationScope fun provideSelectAddressCommunicator( router: AssetsRouter, - navigationHolder: NavigationHolder - ): SelectAddressCommunicator = SelectAddressCommunicatorImpl(router, navigationHolder) + navigationHoldersRegistry: NavigationHoldersRegistry + ): SelectAddressCommunicator = SelectAddressCommunicatorImpl(router, navigationHoldersRegistry) @Provides @ApplicationScope fun provideSelectMultipleWalletsCommunicator( router: AssetsRouter, - navigationHolder: NavigationHolder - ): SelectMultipleWalletsCommunicator = SelectMultipleWalletsCommunicatorImpl(router, navigationHolder) + navigationHoldersRegistry: NavigationHoldersRegistry + ): SelectMultipleWalletsCommunicator = SelectMultipleWalletsCommunicatorImpl(router, navigationHoldersRegistry) @ApplicationScope @Provides @@ -67,20 +67,20 @@ class AccountNavigationModule { @ApplicationScope fun providePushGovernanceSettingsCommunicator( router: AccountRouter, - navigationHolder: NavigationHolder - ): SyncWalletsBackupPasswordCommunicator = SyncWalletsBackupPasswordCommunicatorImpl(router, navigationHolder) + navigationHoldersRegistry: NavigationHoldersRegistry + ): SyncWalletsBackupPasswordCommunicator = SyncWalletsBackupPasswordCommunicatorImpl(router, navigationHoldersRegistry) @Provides @ApplicationScope fun provideChangeBackupPasswordCommunicator( router: AccountRouter, - navigationHolder: NavigationHolder - ): ChangeBackupPasswordCommunicator = ChangeBackupPasswordCommunicatorImpl(router, navigationHolder) + navigationHoldersRegistry: NavigationHoldersRegistry + ): ChangeBackupPasswordCommunicator = ChangeBackupPasswordCommunicatorImpl(router, navigationHoldersRegistry) @Provides @ApplicationScope fun provideRestoreBackupPasswordCommunicator( router: AccountRouter, - navigationHolder: NavigationHolder - ): RestoreBackupPasswordCommunicator = RestoreBackupPasswordCommunicatorImpl(router, navigationHolder) + navigationHoldersRegistry: NavigationHoldersRegistry + ): RestoreBackupPasswordCommunicator = RestoreBackupPasswordCommunicatorImpl(router, navigationHoldersRegistry) } diff --git a/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/BuyNavigationModule.kt b/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/BuyNavigationModule.kt index c6895a9540..dfdd0d3ef1 100644 --- a/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/BuyNavigationModule.kt +++ b/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/BuyNavigationModule.kt @@ -2,8 +2,8 @@ package io.novafoundation.nova.app.di.app.navigation import dagger.Module import dagger.Provides -import io.novafoundation.nova.app.root.navigation.NavigationHolder -import io.novafoundation.nova.app.root.navigation.buy.BuyNavigator +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry +import io.novafoundation.nova.app.root.navigation.navigators.buy.BuyNavigator import io.novafoundation.nova.common.di.scope.ApplicationScope import io.novafoundation.nova.feature_buy_impl.presentation.BuyRouter @@ -12,5 +12,6 @@ class BuyNavigationModule { @ApplicationScope @Provides - fun provideRouter(navigationHolder: NavigationHolder): BuyRouter = BuyNavigator(navigationHolder) + fun provideRouter(navigationHoldersRegistry: NavigationHoldersRegistry): BuyRouter = + BuyNavigator(navigationHoldersRegistry) } diff --git a/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/CloudBackupNavigationModule.kt b/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/CloudBackupNavigationModule.kt index c7209237c4..1653b39119 100644 --- a/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/CloudBackupNavigationModule.kt +++ b/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/CloudBackupNavigationModule.kt @@ -2,8 +2,8 @@ package io.novafoundation.nova.app.di.app.navigation import dagger.Module import dagger.Provides -import io.novafoundation.nova.app.root.navigation.NavigationHolder -import io.novafoundation.nova.app.root.navigation.cloudBackup.CloudBackupNavigator +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry +import io.novafoundation.nova.app.root.navigation.navigators.cloudBackup.CloudBackupNavigator import io.novafoundation.nova.common.di.scope.ApplicationScope import io.novafoundation.nova.feature_cloud_backup_impl.presentation.CloudBackupRouter @@ -12,5 +12,6 @@ class CloudBackupNavigationModule { @ApplicationScope @Provides - fun provideRouter(navigationHolder: NavigationHolder): CloudBackupRouter = CloudBackupNavigator(navigationHolder) + fun provideRouter(navigationHoldersRegistry: NavigationHoldersRegistry): CloudBackupRouter = + CloudBackupNavigator(navigationHoldersRegistry) } diff --git a/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/CurrencyNavigationModule.kt b/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/CurrencyNavigationModule.kt index 10ad03bbc0..d4d60442dd 100644 --- a/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/CurrencyNavigationModule.kt +++ b/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/CurrencyNavigationModule.kt @@ -2,8 +2,8 @@ package io.novafoundation.nova.app.di.app.navigation import dagger.Module import dagger.Provides -import io.novafoundation.nova.app.root.navigation.NavigationHolder -import io.novafoundation.nova.app.root.navigation.wallet.CurrencyNavigator +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry +import io.novafoundation.nova.app.root.navigation.navigators.wallet.CurrencyNavigator import io.novafoundation.nova.app.root.presentation.RootRouter import io.novafoundation.nova.common.di.scope.ApplicationScope import io.novafoundation.nova.feature_currency_api.presentation.CurrencyRouter @@ -15,6 +15,6 @@ class CurrencyNavigationModule { @Provides fun provideRouter( rootRouter: RootRouter, - navigationHolder: NavigationHolder - ): CurrencyRouter = CurrencyNavigator(rootRouter, navigationHolder) + navigationHoldersRegistry: NavigationHoldersRegistry, + ): CurrencyRouter = CurrencyNavigator(rootRouter, navigationHoldersRegistry) } diff --git a/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/DAppNavigationModule.kt b/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/DAppNavigationModule.kt index a517e27bed..f446d89316 100644 --- a/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/DAppNavigationModule.kt +++ b/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/DAppNavigationModule.kt @@ -2,11 +2,11 @@ package io.novafoundation.nova.app.di.app.navigation import dagger.Module import dagger.Provides -import io.novafoundation.nova.app.root.navigation.NavigationHolder -import io.novafoundation.nova.app.root.navigation.dApp.DAppNavigator -import io.novafoundation.nova.app.root.navigation.dApp.DAppSearchCommunicatorImpl +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry +import io.novafoundation.nova.app.root.navigation.navigators.dApp.DAppNavigator +import io.novafoundation.nova.app.root.navigation.navigators.dApp.DAppSearchCommunicatorImpl import io.novafoundation.nova.common.di.scope.ApplicationScope -import io.novafoundation.nova.feature_dapp_impl.DAppRouter +import io.novafoundation.nova.feature_dapp_impl.presentation.DAppRouter import io.novafoundation.nova.feature_dapp_impl.presentation.search.DAppSearchCommunicator @Module @@ -14,11 +14,13 @@ class DAppNavigationModule { @ApplicationScope @Provides - fun provideRouter(navigationHolder: NavigationHolder): DAppRouter = DAppNavigator(navigationHolder) + fun provideRouter( + navigationHoldersRegistry: NavigationHoldersRegistry + ): DAppRouter = DAppNavigator(navigationHoldersRegistry) @ApplicationScope @Provides - fun provideSearchDappCommunicator(navigationHolder: NavigationHolder): DAppSearchCommunicator { - return DAppSearchCommunicatorImpl(navigationHolder) + fun provideSearchDappCommunicator(navigationHoldersRegistry: NavigationHoldersRegistry): DAppSearchCommunicator { + return DAppSearchCommunicatorImpl(navigationHoldersRegistry) } } diff --git a/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/DeepLinkingNavigationModule.kt b/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/DeepLinkingNavigationModule.kt index 1894563283..140a12726d 100644 --- a/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/DeepLinkingNavigationModule.kt +++ b/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/DeepLinkingNavigationModule.kt @@ -2,13 +2,13 @@ package io.novafoundation.nova.app.di.app.navigation import dagger.Module import dagger.Provides -import io.novafoundation.nova.app.root.navigation.deepLinking.DeepLinkingNavigator +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry +import io.novafoundation.nova.app.root.navigation.navigators.deepLinking.DeepLinkingNavigator import io.novafoundation.nova.common.di.scope.ApplicationScope import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter import io.novafoundation.nova.feature_assets.presentation.AssetsRouter -import io.novafoundation.nova.feature_dapp_impl.DAppRouter +import io.novafoundation.nova.feature_dapp_impl.presentation.DAppRouter import io.novafoundation.nova.feature_deep_linking.presentation.handling.DeepLinkingRouter -import io.novafoundation.nova.feature_governance_impl.presentation.GovernanceRouter @Module class DeepLinkingNavigationModule { @@ -16,14 +16,14 @@ class DeepLinkingNavigationModule { @ApplicationScope @Provides fun provideRouter( + navigationHoldersRegistry: NavigationHoldersRegistry, accountRouter: AccountRouter, assetsRouter: AssetsRouter, - dAppRouter: DAppRouter, - governanceRouter: GovernanceRouter + dAppRouter: DAppRouter ): DeepLinkingRouter = DeepLinkingNavigator( + navigationHoldersRegistry = navigationHoldersRegistry, accountRouter = accountRouter, assetsRouter = assetsRouter, - dAppRouter = dAppRouter, - governanceRouter = governanceRouter + dAppRouter = dAppRouter ) } diff --git a/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/ExternalSignNavigationModule.kt b/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/ExternalSignNavigationModule.kt index 96d8cb8f40..4d3848e36e 100644 --- a/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/ExternalSignNavigationModule.kt +++ b/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/ExternalSignNavigationModule.kt @@ -2,9 +2,9 @@ package io.novafoundation.nova.app.di.app.navigation import dagger.Module import dagger.Provides -import io.novafoundation.nova.app.root.navigation.NavigationHolder -import io.novafoundation.nova.app.root.navigation.externalSign.ExternalSignCommunicatorImpl -import io.novafoundation.nova.app.root.navigation.externalSign.ExternalSignNavigator +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry +import io.novafoundation.nova.app.root.navigation.navigators.externalSign.ExternalSignCommunicatorImpl +import io.novafoundation.nova.app.root.navigation.navigators.externalSign.ExternalSignNavigator import io.novafoundation.nova.common.di.scope.ApplicationScope import io.novafoundation.nova.common.utils.sequrity.AutomaticInteractionGate import io.novafoundation.nova.feature_external_sign_api.model.ExternalSignCommunicator @@ -15,14 +15,15 @@ class ExternalSignNavigationModule { @ApplicationScope @Provides - fun provideRouter(navigationHolder: NavigationHolder): ExternalSignRouter = ExternalSignNavigator(navigationHolder) + fun provideRouter(navigationHoldersRegistry: NavigationHoldersRegistry): ExternalSignRouter = + ExternalSignNavigator(navigationHoldersRegistry) @ApplicationScope @Provides fun provideSignExtrinsicCommunicator( - navigationHolder: NavigationHolder, + navigationHoldersRegistry: NavigationHoldersRegistry, automaticInteractionGate: AutomaticInteractionGate, ): ExternalSignCommunicator { - return ExternalSignCommunicatorImpl(navigationHolder, automaticInteractionGate) + return ExternalSignCommunicatorImpl(navigationHoldersRegistry, automaticInteractionGate) } } diff --git a/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/GovernanceNavigationModule.kt b/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/GovernanceNavigationModule.kt index 08fb3d5143..7334ef1e45 100644 --- a/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/GovernanceNavigationModule.kt +++ b/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/GovernanceNavigationModule.kt @@ -2,12 +2,13 @@ package io.novafoundation.nova.app.di.app.navigation 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.governance.GovernanceNavigator -import io.novafoundation.nova.app.root.navigation.governance.SelectTracksCommunicatorImpl -import io.novafoundation.nova.app.root.navigation.governance.TinderGovVoteCommunicatorImpl +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry +import io.novafoundation.nova.app.root.navigation.navigators.Navigator +import io.novafoundation.nova.app.root.navigation.navigators.governance.GovernanceNavigator +import io.novafoundation.nova.app.root.navigation.navigators.governance.SelectTracksCommunicatorImpl +import io.novafoundation.nova.app.root.navigation.navigators.governance.TinderGovVoteCommunicatorImpl import io.novafoundation.nova.common.di.scope.ApplicationScope +import io.novafoundation.nova.common.resources.ContextManager import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.list.SelectTracksCommunicator import io.novafoundation.nova.feature_governance_impl.presentation.GovernanceRouter import io.novafoundation.nova.feature_governance_impl.presentation.referenda.vote.setup.tindergov.TinderGovVoteCommunicator @@ -18,21 +19,22 @@ class GovernanceNavigationModule { @ApplicationScope @Provides fun provideRouter( - navigationHolder: NavigationHolder, + navigationHoldersRegistry: NavigationHoldersRegistry, commonNavigator: Navigator, - ): GovernanceRouter = GovernanceNavigator(navigationHolder, commonNavigator) + contextManager: ContextManager + ): GovernanceRouter = GovernanceNavigator(navigationHoldersRegistry, commonNavigator, contextManager) @Provides @ApplicationScope fun provideSelectTracksCommunicator( router: GovernanceRouter, - navigationHolder: NavigationHolder - ): SelectTracksCommunicator = SelectTracksCommunicatorImpl(router, navigationHolder) + navigationHoldersRegistry: NavigationHoldersRegistry + ): SelectTracksCommunicator = SelectTracksCommunicatorImpl(router, navigationHoldersRegistry) @Provides @ApplicationScope fun provideTinderGovVoteCommunicator( router: GovernanceRouter, - navigationHolder: NavigationHolder - ): TinderGovVoteCommunicator = TinderGovVoteCommunicatorImpl(router, navigationHolder) + navigationHoldersRegistry: NavigationHoldersRegistry + ): TinderGovVoteCommunicator = TinderGovVoteCommunicatorImpl(router, navigationHoldersRegistry) } diff --git a/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/LedgerNavigationModule.kt b/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/LedgerNavigationModule.kt index eb1e849883..9879c7ba8b 100644 --- a/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/LedgerNavigationModule.kt +++ b/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/LedgerNavigationModule.kt @@ -2,10 +2,10 @@ package io.novafoundation.nova.app.di.app.navigation import dagger.Module import dagger.Provides -import io.novafoundation.nova.app.root.navigation.NavigationHolder -import io.novafoundation.nova.app.root.navigation.ledger.LedgerNavigator -import io.novafoundation.nova.app.root.navigation.ledger.LedgerSignCommunicatorImpl -import io.novafoundation.nova.app.root.navigation.ledger.SelectLedgerAddressCommunicatorImpl +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry +import io.novafoundation.nova.app.root.navigation.navigators.ledger.LedgerNavigator +import io.novafoundation.nova.app.root.navigation.navigators.ledger.LedgerSignCommunicatorImpl +import io.novafoundation.nova.app.root.navigation.navigators.ledger.SelectLedgerAddressCommunicatorImpl import io.novafoundation.nova.common.di.scope.ApplicationScope import io.novafoundation.nova.feature_account_api.presenatation.sign.LedgerSignCommunicator import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter @@ -17,17 +17,18 @@ class LedgerNavigationModule { @ApplicationScope @Provides - fun provideSelectLedgerAddressCommunicator(navigationHolder: NavigationHolder): SelectLedgerAddressInterScreenCommunicator { - return SelectLedgerAddressCommunicatorImpl(navigationHolder) + fun provideSelectLedgerAddressCommunicator(navigationHoldersRegistry: NavigationHoldersRegistry): SelectLedgerAddressInterScreenCommunicator { + return SelectLedgerAddressCommunicatorImpl(navigationHoldersRegistry) } @Provides @ApplicationScope fun provideLedgerSignerCommunicator( - navigationHolder: NavigationHolder - ): LedgerSignCommunicator = LedgerSignCommunicatorImpl(navigationHolder) + navigationHoldersRegistry: NavigationHoldersRegistry + ): LedgerSignCommunicator = LedgerSignCommunicatorImpl(navigationHoldersRegistry) @ApplicationScope @Provides - fun provideRouter(router: AccountRouter, navigationHolder: NavigationHolder): LedgerRouter = LedgerNavigator(router, navigationHolder) + fun provideRouter(router: AccountRouter, navigationHoldersRegistry: NavigationHoldersRegistry): LedgerRouter = + LedgerNavigator(router, navigationHoldersRegistry) } diff --git a/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/NavigationModule.kt b/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/NavigationModule.kt index 969ae4ea19..f9bf967984 100644 --- a/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/NavigationModule.kt +++ b/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/NavigationModule.kt @@ -3,10 +3,13 @@ package io.novafoundation.nova.app.di.app.navigation import dagger.Module import dagger.Provides import io.novafoundation.nova.app.di.app.navigation.staking.StakingNavigationModule -import io.novafoundation.nova.app.root.navigation.NavigationHolder -import io.novafoundation.nova.app.root.navigation.Navigator +import io.novafoundation.nova.app.root.navigation.holders.RootNavigationHolder +import io.novafoundation.nova.app.root.navigation.holders.SplitScreenNavigationHolder +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry +import io.novafoundation.nova.app.root.navigation.navigators.Navigator import io.novafoundation.nova.app.root.presentation.RootRouter import io.novafoundation.nova.common.di.scope.ApplicationScope +import io.novafoundation.nova.common.navigation.DelayedNavigationRouter import io.novafoundation.nova.common.resources.ContextManager import io.novafoundation.nova.feature_assets.presentation.AssetsRouter import io.novafoundation.nova.feature_crowdloan_impl.presentation.CrowdloanRouter @@ -40,17 +43,32 @@ class NavigationModule { @ApplicationScope @Provides - fun provideNavigatorHolder( + fun provideMainNavigatorHolder( contextManager: ContextManager - ): NavigationHolder = NavigationHolder(contextManager) + ): SplitScreenNavigationHolder = SplitScreenNavigationHolder(contextManager) + + @ApplicationScope + @Provides + fun provideDappNavigatorHolder( + contextManager: ContextManager + ): RootNavigationHolder = RootNavigationHolder(contextManager) + + @ApplicationScope + @Provides + fun provideNavigationHoldersRegistry( + rootNavigatorHolder: RootNavigationHolder, + splitScreenNavigationHolder: SplitScreenNavigationHolder, + ): NavigationHoldersRegistry { + return NavigationHoldersRegistry(splitScreenNavigationHolder, rootNavigatorHolder) + } @ApplicationScope @Provides fun provideNavigator( - navigatorHolder: NavigationHolder, + navigationHoldersRegistry: NavigationHoldersRegistry, walletConnectRouter: WalletConnectRouter, stakingDashboardRouter: StakingDashboardRouter, - ): Navigator = Navigator(navigatorHolder, walletConnectRouter, stakingDashboardRouter) + ): Navigator = Navigator(navigationHoldersRegistry, walletConnectRouter, stakingDashboardRouter) @Provides @ApplicationScope @@ -71,4 +89,8 @@ class NavigationModule { @ApplicationScope @Provides fun provideCrowdloanRouter(navigator: Navigator): CrowdloanRouter = navigator + + @ApplicationScope + @Provides + fun provideDelayedNavigationRouter(navigator: Navigator): DelayedNavigationRouter = navigator } diff --git a/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/NftNavigationModule.kt b/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/NftNavigationModule.kt index cd06c06b5a..1cadfbcfe0 100644 --- a/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/NftNavigationModule.kt +++ b/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/NftNavigationModule.kt @@ -2,8 +2,8 @@ package io.novafoundation.nova.app.di.app.navigation import dagger.Module import dagger.Provides -import io.novafoundation.nova.app.root.navigation.NavigationHolder -import io.novafoundation.nova.app.root.navigation.nft.NftNavigator +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry +import io.novafoundation.nova.app.root.navigation.navigators.nft.NftNavigator import io.novafoundation.nova.common.di.scope.ApplicationScope import io.novafoundation.nova.feature_nft_impl.NftRouter @@ -12,5 +12,6 @@ class NftNavigationModule { @ApplicationScope @Provides - fun provideRouter(navigationHolder: NavigationHolder): NftRouter = NftNavigator(navigationHolder) + fun provideRouter(navigationHoldersRegistry: NavigationHoldersRegistry): NftRouter = + NftNavigator(navigationHoldersRegistry) } diff --git a/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/PushNotificationsNavigationModule.kt b/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/PushNotificationsNavigationModule.kt index bbdfc050da..669e835018 100644 --- a/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/PushNotificationsNavigationModule.kt +++ b/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/PushNotificationsNavigationModule.kt @@ -2,10 +2,10 @@ package io.novafoundation.nova.app.di.app.navigation import dagger.Module import dagger.Provides -import io.novafoundation.nova.app.root.navigation.NavigationHolder -import io.novafoundation.nova.app.root.navigation.push.PushGovernanceSettingsCommunicatorImpl -import io.novafoundation.nova.app.root.navigation.push.PushNotificationsNavigator -import io.novafoundation.nova.app.root.navigation.push.PushStakingSettingsCommunicatorImpl +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry +import io.novafoundation.nova.app.root.navigation.navigators.push.PushGovernanceSettingsCommunicatorImpl +import io.novafoundation.nova.app.root.navigation.navigators.push.PushNotificationsNavigator +import io.novafoundation.nova.app.root.navigation.navigators.push.PushStakingSettingsCommunicatorImpl import io.novafoundation.nova.common.di.scope.ApplicationScope import io.novafoundation.nova.feature_push_notifications.PushNotificationsRouter import io.novafoundation.nova.feature_push_notifications.presentation.governance.PushGovernanceSettingsCommunicator @@ -16,19 +16,20 @@ class PushNotificationsNavigationModule { @ApplicationScope @Provides - fun provideRouter(navigationHolder: NavigationHolder): PushNotificationsRouter = PushNotificationsNavigator(navigationHolder) + fun provideRouter(navigationHoldersRegistry: NavigationHoldersRegistry): PushNotificationsRouter = + PushNotificationsNavigator(navigationHoldersRegistry) @Provides @ApplicationScope fun providePushGovernanceSettingsCommunicator( router: PushNotificationsRouter, - navigationHolder: NavigationHolder - ): PushGovernanceSettingsCommunicator = PushGovernanceSettingsCommunicatorImpl(router, navigationHolder) + navigationHoldersRegistry: NavigationHoldersRegistry + ): PushGovernanceSettingsCommunicator = PushGovernanceSettingsCommunicatorImpl(router, navigationHoldersRegistry) @Provides @ApplicationScope fun providePushStakingSettingsCommunicator( router: PushNotificationsRouter, - navigationHolder: NavigationHolder - ): PushStakingSettingsCommunicator = PushStakingSettingsCommunicatorImpl(router, navigationHolder) + navigationHoldersRegistry: NavigationHoldersRegistry + ): PushStakingSettingsCommunicator = PushStakingSettingsCommunicatorImpl(router, navigationHoldersRegistry) } diff --git a/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/SettingsNavigationModule.kt b/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/SettingsNavigationModule.kt index 6abdb481f5..676ccb9d60 100644 --- a/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/SettingsNavigationModule.kt +++ b/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/SettingsNavigationModule.kt @@ -2,9 +2,9 @@ package io.novafoundation.nova.app.di.app.navigation 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.settings.SettingsNavigator +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry +import io.novafoundation.nova.app.root.navigation.navigators.Navigator +import io.novafoundation.nova.app.root.navigation.navigators.settings.SettingsNavigator import io.novafoundation.nova.app.root.presentation.RootRouter import io.novafoundation.nova.common.di.scope.ApplicationScope import io.novafoundation.nova.feature_settings_impl.SettingsRouter @@ -17,8 +17,8 @@ class SettingsNavigationModule { @Provides fun provideRouter( rootRouter: RootRouter, - navigationHolder: NavigationHolder, + navigationHoldersRegistry: NavigationHoldersRegistry, walletConnectRouter: WalletConnectRouter, navigator: Navigator, - ): SettingsRouter = SettingsNavigator(navigationHolder, rootRouter, walletConnectRouter, navigator) + ): SettingsRouter = SettingsNavigator(navigationHoldersRegistry, rootRouter, walletConnectRouter, navigator) } diff --git a/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/SwapNavigationModule.kt b/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/SwapNavigationModule.kt index ff0c5e6488..813ff4d165 100644 --- a/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/SwapNavigationModule.kt +++ b/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/SwapNavigationModule.kt @@ -2,9 +2,9 @@ package io.novafoundation.nova.app.di.app.navigation 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.swap.SwapNavigator +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry +import io.novafoundation.nova.app.root.navigation.navigators.Navigator +import io.novafoundation.nova.app.root.navigation.navigators.swap.SwapNavigator import io.novafoundation.nova.common.di.scope.ApplicationScope import io.novafoundation.nova.feature_swap_impl.presentation.SwapRouter @@ -14,7 +14,7 @@ class SwapNavigationModule { @ApplicationScope @Provides fun provideRouter( - navigationHolder: NavigationHolder, + navigationHoldersRegistry: NavigationHoldersRegistry, commonDelegate: Navigator - ): SwapRouter = SwapNavigator(navigationHolder, commonDelegate) + ): SwapRouter = SwapNavigator(navigationHoldersRegistry, commonDelegate) } diff --git a/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/VersionsNavigationModule.kt b/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/VersionsNavigationModule.kt index bf09faaadc..fd4135c0dd 100644 --- a/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/VersionsNavigationModule.kt +++ b/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/VersionsNavigationModule.kt @@ -2,10 +2,11 @@ package io.novafoundation.nova.app.di.app.navigation import dagger.Module import dagger.Provides -import io.novafoundation.nova.app.root.navigation.NavigationHolder -import io.novafoundation.nova.app.root.navigation.versions.VersionsNavigator +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry +import io.novafoundation.nova.app.root.navigation.navigators.versions.VersionsNavigator import io.novafoundation.nova.common.data.network.AppLinksProvider import io.novafoundation.nova.common.di.scope.ApplicationScope +import io.novafoundation.nova.common.resources.ContextManager import io.novafoundation.nova.feature_versions_api.presentation.VersionsRouter @Module @@ -14,7 +15,8 @@ class VersionsNavigationModule { @Provides @ApplicationScope fun provideRouter( - navigationHolder: NavigationHolder, + navigationHoldersRegistry: NavigationHoldersRegistry, + contextManager: ContextManager, appLinksProvider: AppLinksProvider - ): VersionsRouter = VersionsNavigator(navigationHolder, appLinksProvider.storeUrl) + ): VersionsRouter = VersionsNavigator(navigationHoldersRegistry, contextManager, appLinksProvider.storeUrl) } diff --git a/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/VoteNavigationModule.kt b/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/VoteNavigationModule.kt index d1fc2490e1..dd2248e523 100644 --- a/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/VoteNavigationModule.kt +++ b/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/VoteNavigationModule.kt @@ -2,8 +2,8 @@ package io.novafoundation.nova.app.di.app.navigation import dagger.Module import dagger.Provides -import io.novafoundation.nova.app.root.navigation.Navigator -import io.novafoundation.nova.app.root.navigation.vote.VoteNavigator +import io.novafoundation.nova.app.root.navigation.navigators.Navigator +import io.novafoundation.nova.app.root.navigation.navigators.vote.VoteNavigator import io.novafoundation.nova.common.di.scope.ApplicationScope import io.novafoundation.nova.feature_vote.presentation.VoteRouter diff --git a/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/WalletConnectNavigationModule.kt b/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/WalletConnectNavigationModule.kt index fe0c027479..7c3b7fed77 100644 --- a/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/WalletConnectNavigationModule.kt +++ b/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/WalletConnectNavigationModule.kt @@ -2,9 +2,9 @@ package io.novafoundation.nova.app.di.app.navigation import dagger.Module import dagger.Provides -import io.novafoundation.nova.app.root.navigation.NavigationHolder -import io.novafoundation.nova.app.root.navigation.walletConnect.ApproveSessionCommunicatorImpl -import io.novafoundation.nova.app.root.navigation.walletConnect.WalletConnectNavigator +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry +import io.novafoundation.nova.app.root.navigation.navigators.walletConnect.ApproveSessionCommunicatorImpl +import io.novafoundation.nova.app.root.navigation.navigators.walletConnect.WalletConnectNavigator import io.novafoundation.nova.common.di.scope.ApplicationScope import io.novafoundation.nova.common.utils.sequrity.AutomaticInteractionGate import io.novafoundation.nova.feature_wallet_connect_impl.WalletConnectRouter @@ -16,11 +16,12 @@ class WalletConnectNavigationModule { @Provides @ApplicationScope fun provideApproveSessionCommunicator( - navigationHolder: NavigationHolder, + navigationHoldersRegistry: NavigationHoldersRegistry, automaticInteractionGate: AutomaticInteractionGate, - ): ApproveSessionCommunicator = ApproveSessionCommunicatorImpl(navigationHolder, automaticInteractionGate) + ): ApproveSessionCommunicator = ApproveSessionCommunicatorImpl(navigationHoldersRegistry, automaticInteractionGate) @ApplicationScope @Provides - fun provideRouter(navigationHolder: NavigationHolder): WalletConnectRouter = WalletConnectNavigator(navigationHolder) + fun provideRouter(navigationHoldersRegistry: NavigationHoldersRegistry): WalletConnectRouter = + WalletConnectNavigator(navigationHoldersRegistry) } diff --git a/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/staking/NominationPoolsStakingNavigationModule.kt b/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/staking/NominationPoolsStakingNavigationModule.kt index af3cd895d1..d9a8173d43 100644 --- a/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/staking/NominationPoolsStakingNavigationModule.kt +++ b/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/staking/NominationPoolsStakingNavigationModule.kt @@ -2,9 +2,9 @@ package io.novafoundation.nova.app.di.app.navigation.staking 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.staking.nominationPools.NominationPoolsStakingNavigator +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry +import io.novafoundation.nova.app.root.navigation.navigators.Navigator +import io.novafoundation.nova.app.root.navigation.navigators.staking.nominationPools.NominationPoolsStakingNavigator import io.novafoundation.nova.common.di.scope.ApplicationScope import io.novafoundation.nova.feature_staking_impl.presentation.NominationPoolsRouter @@ -13,7 +13,7 @@ class NominationPoolsStakingNavigationModule { @Provides @ApplicationScope - fun provideRouter(navigationHolder: NavigationHolder, navigator: Navigator): NominationPoolsRouter { - return NominationPoolsStakingNavigator(navigationHolder, navigator) + fun provideRouter(navigationHoldersRegistry: NavigationHoldersRegistry, navigator: Navigator): NominationPoolsRouter { + return NominationPoolsStakingNavigator(navigationHoldersRegistry, navigator) } } diff --git a/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/staking/ParachainStakingNavigationModule.kt b/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/staking/ParachainStakingNavigationModule.kt index ed83dfc671..bb7bb6314d 100644 --- a/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/staking/ParachainStakingNavigationModule.kt +++ b/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/staking/ParachainStakingNavigationModule.kt @@ -2,11 +2,11 @@ package io.novafoundation.nova.app.di.app.navigation.staking 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.staking.parachain.ParachainStakingNavigator -import io.novafoundation.nova.app.root.navigation.staking.parachain.SelectCollatorInterScreenCommunicatorImpl -import io.novafoundation.nova.app.root.navigation.staking.parachain.SelectCollatorSettingsInterScreenCommunicatorImpl +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry +import io.novafoundation.nova.app.root.navigation.navigators.Navigator +import io.novafoundation.nova.app.root.navigation.navigators.staking.parachain.ParachainStakingNavigator +import io.novafoundation.nova.app.root.navigation.navigators.staking.parachain.SelectCollatorInterScreenCommunicatorImpl +import io.novafoundation.nova.app.root.navigation.navigators.staking.parachain.SelectCollatorSettingsInterScreenCommunicatorImpl import io.novafoundation.nova.common.di.scope.ApplicationScope import io.novafoundation.nova.feature_staking_impl.presentation.ParachainStakingRouter import io.novafoundation.nova.feature_staking_impl.presentation.parachainStaking.collator.common.SelectCollatorInterScreenCommunicator @@ -17,19 +17,22 @@ class ParachainStakingNavigationModule { @Provides @ApplicationScope - fun provideParachainStakingRouter(navigationHolder: NavigationHolder, navigator: Navigator): ParachainStakingRouter { - return ParachainStakingNavigator(navigationHolder, navigator) + fun provideParachainStakingRouter( + navigationHoldersRegistry: NavigationHoldersRegistry, + navigator: Navigator + ): ParachainStakingRouter { + return ParachainStakingNavigator(navigationHoldersRegistry, navigator) } @Provides @ApplicationScope - fun provideSelectCollatorCommunicator(navigationHolder: NavigationHolder): SelectCollatorInterScreenCommunicator { - return SelectCollatorInterScreenCommunicatorImpl(navigationHolder) + fun provideSelectCollatorCommunicator(navigationHoldersRegistry: NavigationHoldersRegistry): SelectCollatorInterScreenCommunicator { + return SelectCollatorInterScreenCommunicatorImpl(navigationHoldersRegistry) } @Provides @ApplicationScope - fun provideSelectCollatorSettingsCommunicator(navigationHolder: NavigationHolder): SelectCollatorSettingsInterScreenCommunicator { - return SelectCollatorSettingsInterScreenCommunicatorImpl(navigationHolder) + fun provideSelectCollatorSettingsCommunicator(navigationHoldersRegistry: NavigationHoldersRegistry): SelectCollatorSettingsInterScreenCommunicator { + return SelectCollatorSettingsInterScreenCommunicatorImpl(navigationHoldersRegistry) } } diff --git a/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/staking/RelayStakingNavigationModule.kt b/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/staking/RelayStakingNavigationModule.kt index 7575743fcf..c0fc20e345 100644 --- a/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/staking/RelayStakingNavigationModule.kt +++ b/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/staking/RelayStakingNavigationModule.kt @@ -2,9 +2,9 @@ package io.novafoundation.nova.app.di.app.navigation.staking 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.staking.relaychain.RelayStakingNavigator +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry +import io.novafoundation.nova.app.root.navigation.navigators.Navigator +import io.novafoundation.nova.app.root.navigation.navigators.staking.relaychain.RelayStakingNavigator import io.novafoundation.nova.common.di.scope.ApplicationScope import io.novafoundation.nova.feature_staking_impl.presentation.StakingDashboardRouter import io.novafoundation.nova.feature_staking_impl.presentation.StakingRouter @@ -15,10 +15,10 @@ class RelayStakingNavigationModule { @Provides @ApplicationScope fun provideRelayStakingRouter( - navigationHolder: NavigationHolder, + navigationHoldersRegistry: NavigationHoldersRegistry, navigator: Navigator, dashboardRouter: StakingDashboardRouter ): StakingRouter { - return RelayStakingNavigator(navigationHolder, navigator, dashboardRouter) + return RelayStakingNavigator(navigationHoldersRegistry, navigator, dashboardRouter) } } diff --git a/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/staking/StakingNavigationModule.kt b/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/staking/StakingNavigationModule.kt index d830cdacd1..e92110bcdf 100644 --- a/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/staking/StakingNavigationModule.kt +++ b/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/staking/StakingNavigationModule.kt @@ -2,10 +2,10 @@ package io.novafoundation.nova.app.di.app.navigation.staking 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.staking.StakingDashboardNavigator -import io.novafoundation.nova.app.root.navigation.staking.StartMultiStakingNavigator +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry +import io.novafoundation.nova.app.root.navigation.navigators.Navigator +import io.novafoundation.nova.app.root.navigation.navigators.staking.StakingDashboardNavigator +import io.novafoundation.nova.app.root.navigation.navigators.staking.StartMultiStakingNavigator import io.novafoundation.nova.common.di.scope.ApplicationScope import io.novafoundation.nova.feature_staking_impl.presentation.StakingDashboardRouter import io.novafoundation.nova.feature_staking_impl.presentation.StartMultiStakingRouter @@ -21,8 +21,8 @@ class StakingNavigationModule { @Provides @ApplicationScope - fun provideStakingDashboardNavigator(navigationHolder: NavigationHolder): StakingDashboardNavigator { - return StakingDashboardNavigator(navigationHolder) + fun provideStakingDashboardNavigator(): StakingDashboardNavigator { + return StakingDashboardNavigator() } @Provides @@ -32,10 +32,10 @@ class StakingNavigationModule { @Provides @ApplicationScope fun provideStartMultiStakingRouter( - navigationHolder: NavigationHolder, + navigationHoldersRegistry: NavigationHoldersRegistry, dashboardRouter: StakingDashboardRouter, commonNavigator: Navigator ): StartMultiStakingRouter { - return StartMultiStakingNavigator(navigationHolder, dashboardRouter, commonNavigator) + return StartMultiStakingNavigator(navigationHoldersRegistry, dashboardRouter, commonNavigator) } } 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 431598b2bf..cb40e9e42b 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 @@ -2,13 +2,16 @@ package io.novafoundation.nova.app.root.di import dagger.BindsInstance import dagger.Component -import io.novafoundation.nova.app.root.navigation.NavigationHolder -import io.novafoundation.nova.app.root.navigation.staking.StakingDashboardNavigator +import io.novafoundation.nova.app.root.navigation.holders.RootNavigationHolder +import io.novafoundation.nova.app.root.navigation.holders.SplitScreenNavigationHolder +import io.novafoundation.nova.app.root.navigation.navigators.staking.StakingDashboardNavigator import io.novafoundation.nova.app.root.presentation.RootRouter import io.novafoundation.nova.app.root.presentation.di.RootActivityComponent import io.novafoundation.nova.app.root.presentation.main.di.MainFragmentComponent +import io.novafoundation.nova.app.root.presentation.splitScreen.di.SplitScreenFragmentComponent import io.novafoundation.nova.common.di.CommonApi import io.novafoundation.nova.common.di.scope.FeatureScope +import io.novafoundation.nova.common.navigation.DelayedNavigationRouter 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 @@ -17,7 +20,7 @@ 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_dapp_impl.presentation.DAppRouter import io.novafoundation.nova.feature_deep_linking.di.DeepLinkingFeatureApi import io.novafoundation.nova.feature_governance_api.di.GovernanceFeatureApi import io.novafoundation.nova.feature_governance_impl.presentation.GovernanceRouter @@ -42,19 +45,23 @@ interface RootComponent { fun mainActivityComponentFactory(): RootActivityComponent.Factory + fun splitScreenFragmentComponentFactory(): SplitScreenFragmentComponent.Factory + fun mainFragmentComponentFactory(): MainFragmentComponent.Factory @Component.Factory interface Factory { fun create( - @BindsInstance navigationHolder: NavigationHolder, + @BindsInstance splitScreenNavigationHolder: SplitScreenNavigationHolder, + @BindsInstance rootNavigationHolder: RootNavigationHolder, @BindsInstance rootRouter: RootRouter, @BindsInstance governanceRouter: GovernanceRouter, @BindsInstance dAppRouter: DAppRouter, @BindsInstance assetsRouter: AssetsRouter, @BindsInstance accountRouter: AccountRouter, @BindsInstance stakingDashboardNavigator: StakingDashboardNavigator, + @BindsInstance delayedNavigationRouter: DelayedNavigationRouter, deps: RootDependencies ): RootComponent } 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 a70d6508e9..75c169768c 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 @@ -3,6 +3,7 @@ package io.novafoundation.nova.app.root.di import android.content.Context import coil.ImageLoader import io.novafoundation.nova.common.data.network.AppLinksProvider +import io.novafoundation.nova.common.mixin.actionAwaitable.ActionAwaitableMixin import io.novafoundation.nova.common.mixin.api.NetworkStateMixin import io.novafoundation.nova.common.resources.ContextManager import io.novafoundation.nova.common.resources.ResourceManager @@ -12,6 +13,7 @@ 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.common.view.bottomSheet.action.ActionBottomSheetLauncherFactory +import io.novafoundation.nova.core_db.dao.BrowserTabsDao import io.novafoundation.nova.feature_account_api.data.events.MetaAccountChangesEventBus import io.novafoundation.nova.feature_account_api.data.proxy.ProxySyncService import io.novafoundation.nova.feature_account_api.data.proxy.validation.ProxyExtrinsicValidationRequestBus @@ -22,6 +24,7 @@ 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.BrowserTabExternalRepository import io.novafoundation.nova.feature_dapp_api.data.repository.DAppMetadataRepository import io.novafoundation.nova.feature_deep_linking.presentation.handling.RootDeepLinkHandler import io.novafoundation.nova.feature_governance_api.data.MutableGovernanceState @@ -39,6 +42,36 @@ import kotlinx.coroutines.flow.MutableStateFlow interface RootDependencies { + val systemCallExecutor: SystemCallExecutor + + val contextManager: ContextManager + + val walletConnectService: WalletConnectService + + val imageLoader: ImageLoader + + val automaticInteractionGate: AutomaticInteractionGate + + val walletConnectSessionsUseCase: WalletConnectSessionsUseCase + + val pushNotificationsInteractor: PushNotificationsInteractor + + val rootDeepLinkHandler: RootDeepLinkHandler + + val welcomePushNotificationsInteractor: WelcomePushNotificationsInteractor + + val applyLocalSnapshotToCloudBackupUseCase: ApplyLocalSnapshotToCloudBackupUseCase + + val actionBottomSheetLauncherFactory: ActionBottomSheetLauncherFactory + + val tabsDao: BrowserTabsDao + + val balancesUpdateSystem: BalancesUpdateSystem + + val actionAwaitableMixinFactory: ActionAwaitableMixin.Factory + + val browserTabExternalRepository: BrowserTabExternalRepository + fun updateNotificationsInteractor(): UpdateNotificationsInteractor fun contributionsInteractor(): ContributionsInteractor @@ -59,8 +92,6 @@ interface RootDependencies { fun currencyInteractor(): CurrencyInteractor - val balancesUpdateSystem: BalancesUpdateSystem - fun stakingRepository(): StakingRepository fun chainRegistry(): ChainRegistry @@ -86,26 +117,4 @@ interface RootDependencies { fun proxyHaveEnoughFeeValidationFactory(): ProxyHaveEnoughFeeValidationFactory fun context(): Context - - val systemCallExecutor: SystemCallExecutor - - val contextManager: ContextManager - - val walletConnectService: WalletConnectService - - val imageLoader: ImageLoader - - val automaticInteractionGate: AutomaticInteractionGate - - val walletConnectSessionsUseCase: WalletConnectSessionsUseCase - - val pushNotificationsInteractor: PushNotificationsInteractor - - val rootDeepLinkHandler: RootDeepLinkHandler - - val welcomePushNotificationsInteractor: WelcomePushNotificationsInteractor - - val applyLocalSnapshotToCloudBackupUseCase: ApplyLocalSnapshotToCloudBackupUseCase - - val actionBottomSheetLauncherFactory: ActionBottomSheetLauncherFactory } 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 406eef32ff..8d3c2c81d2 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 @@ -1,11 +1,13 @@ package io.novafoundation.nova.app.root.di -import io.novafoundation.nova.app.root.navigation.NavigationHolder -import io.novafoundation.nova.app.root.navigation.Navigator -import io.novafoundation.nova.app.root.navigation.staking.StakingDashboardNavigator +import io.novafoundation.nova.app.root.navigation.holders.RootNavigationHolder +import io.novafoundation.nova.app.root.navigation.holders.SplitScreenNavigationHolder +import io.novafoundation.nova.app.root.navigation.navigators.Navigator +import io.novafoundation.nova.app.root.navigation.navigators.staking.StakingDashboardNavigator import io.novafoundation.nova.common.di.FeatureApiHolder import io.novafoundation.nova.common.di.FeatureContainer import io.novafoundation.nova.common.di.scope.ApplicationScope +import io.novafoundation.nova.common.navigation.DelayedNavigationRouter 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 @@ -14,7 +16,7 @@ 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_dapp_impl.presentation.DAppRouter import io.novafoundation.nova.feature_deep_linking.di.DeepLinkingFeatureApi import io.novafoundation.nova.feature_governance_api.di.GovernanceFeatureApi import io.novafoundation.nova.feature_governance_impl.presentation.GovernanceRouter @@ -29,13 +31,15 @@ import javax.inject.Inject @ApplicationScope class RootFeatureHolder @Inject constructor( - private val navigationHolder: NavigationHolder, + private val splitScreenNavigationHolder: SplitScreenNavigationHolder, + private val rootNavigationHolder: RootNavigationHolder, private val navigator: Navigator, private val governanceRouter: GovernanceRouter, private val dAppRouter: DAppRouter, private val accountRouter: AccountRouter, private val assetsRouter: AssetsRouter, private val stakingDashboardNavigator: StakingDashboardNavigator, + private val delayedNavRouter: DelayedNavigationRouter, featureContainer: FeatureContainer ) : FeatureApiHolder(featureContainer) { @@ -60,6 +64,17 @@ class RootFeatureHolder @Inject constructor( .build() return DaggerRootComponent.factory() - .create(navigationHolder, navigator, governanceRouter, dAppRouter, assetsRouter, accountRouter, stakingDashboardNavigator, rootFeatureDependencies) + .create( + splitScreenNavigationHolder, + rootNavigationHolder, + navigator, + governanceRouter, + dAppRouter, + assetsRouter, + accountRouter, + stakingDashboardNavigator, + delayedNavRouter, + rootFeatureDependencies + ) } } diff --git a/app/src/main/java/io/novafoundation/nova/app/root/domain/SplitScreenInteractor.kt b/app/src/main/java/io/novafoundation/nova/app/root/domain/SplitScreenInteractor.kt new file mode 100644 index 0000000000..ebd1c849ac --- /dev/null +++ b/app/src/main/java/io/novafoundation/nova/app/root/domain/SplitScreenInteractor.kt @@ -0,0 +1,23 @@ +package io.novafoundation.nova.app.root.domain + +import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository +import io.novafoundation.nova.feature_dapp_api.data.model.SimpleTabModel +import io.novafoundation.nova.feature_dapp_api.data.repository.BrowserTabExternalRepository +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flatMapLatest + +class SplitScreenInteractor( + private val repository: BrowserTabExternalRepository, + private val accountRepository: AccountRepository +) { + + fun observeTabNamesById(): Flow> { + return accountRepository.selectedMetaAccountFlow() + .flatMapLatest { repository.observeTabsWithNames(it.id) } + } + + suspend fun removeAllTabs() { + val metaAccount = accountRepository.getSelectedMetaAccount() + repository.removeTabsForMetaAccount(metaAccount.id) + } +} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/BaseNavigator.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/BaseNavigator.kt deleted file mode 100644 index 66ce87bdbe..0000000000 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/BaseNavigator.kt +++ /dev/null @@ -1,47 +0,0 @@ -package io.novafoundation.nova.app.root.navigation - -import android.os.Bundle -import androidx.annotation.IdRes -import androidx.navigation.NavController -import io.novafoundation.nova.common.navigation.ReturnableRouter - -abstract class BaseNavigator( - private val navigationHolder: NavigationHolder -) : ReturnableRouter { - - override fun back() { - navigationHolder.executeBack() - } - - /** - * Performs conditional navigation based on current destination - * @param cases - array of pairs (currentDestination, navigationAction) - */ - fun performNavigation( - cases: Array>, - args: Bundle? = null - ) { - val navController = navigationHolder.navController - - navController?.currentDestination?.let { currentDestination -> - val (_, case) = cases.find { (startDestination, _) -> startDestination == currentDestination.id } - ?: throw IllegalArgumentException("Unknown case for ${currentDestination.label}") - - currentDestination.getAction(case)?.let { - navController.navigate(case, args) - } - } - } - - protected fun performNavigation(@IdRes actionId: Int, args: Bundle? = null) { - val navController = navigationHolder.navController - - navController?.performNavigation(actionId, args) - } - - protected fun NavController.performNavigation(@IdRes actionId: Int, args: Bundle? = null) { - currentDestination?.getAction(actionId)?.let { - navigate(actionId, args) - } - } -} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/Ext.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/Ext.kt index b0384d2f05..a6d4806fa9 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/Ext.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/Ext.kt @@ -1,10 +1,16 @@ package io.novafoundation.nova.app.root.navigation import android.annotation.SuppressLint +import android.os.Bundle import androidx.annotation.IdRes import androidx.navigation.NavBackStackEntry import androidx.navigation.NavController import androidx.navigation.NavGraph +import io.novafoundation.nova.app.R +import io.novafoundation.nova.app.root.navigation.delayedNavigation.NavComponentDelayedNavigation +import io.novafoundation.nova.app.root.navigation.navigators.BaseNavigator +import io.novafoundation.nova.app.root.presentation.splitScreen.SplitScreenFragment +import io.novafoundation.nova.app.root.presentation.splitScreen.SplitScreenPayload @SuppressLint("RestrictedApi") fun NavController.getBackStackEntryBefore(@IdRes id: Int): NavBackStackEntry { @@ -22,3 +28,12 @@ fun NavController.getBackStackEntryBefore(@IdRes id: Int): NavBackStackEntry { return backStack[previousIndex] } + +fun BaseNavigator.openSplitScreenWithInstantAction(actionId: Int, nestedActionExtras: Bundle? = null) { + val delayedNavigation = NavComponentDelayedNavigation(actionId, nestedActionExtras) + + val splitScreenPayload = SplitScreenPayload.InstantNavigationOnAttach(delayedNavigation) + navigationBuilder().action(R.id.action_open_split_screen) + .setArgs(SplitScreenFragment.createPayload(splitScreenPayload)) + .navigateInRoot() +} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/NavStackInterScreenCommunicator.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/NavStackInterScreenCommunicator.kt index 8ada92c3f4..b32b782ede 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/NavStackInterScreenCommunicator.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/NavStackInterScreenCommunicator.kt @@ -5,19 +5,22 @@ import androidx.annotation.CallSuper import androidx.lifecycle.asFlow import androidx.navigation.NavBackStackEntry import androidx.navigation.NavController +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry +import io.novafoundation.nova.app.root.navigation.navigators.builder.NavigationBuilderRegistry +import io.novafoundation.nova.app.root.navigation.navigators.navigationBuilder import io.novafoundation.nova.common.navigation.InterScreenCommunicator import kotlinx.coroutines.flow.Flow import java.util.UUID abstract class NavStackInterScreenCommunicator( - private val navigationHolder: NavigationHolder, + private val navigationHoldersRegistry: NavigationHoldersRegistry ) : InterScreenCommunicator { private val responseKey = UUID.randomUUID().toString() private val requestKey = UUID.randomUUID().toString() protected val navController: NavController - get() = navigationHolder.navController!! + get() = navigationHoldersRegistry.firstAttachedNavController!! // from requester - retrieve from current entry override val latestResponse: O? @@ -68,4 +71,8 @@ abstract class NavStackInterScreenCommunicator( .getLiveData(responseKey) .asFlow() } + + protected fun navigationBuilder(): NavigationBuilderRegistry { + return navigationHoldersRegistry.navigationBuilder() + } } diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/NavigationHolder.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/NavigationHolder.kt deleted file mode 100644 index e87b441677..0000000000 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/NavigationHolder.kt +++ /dev/null @@ -1,30 +0,0 @@ -package io.novafoundation.nova.app.root.navigation - -import androidx.navigation.NavController -import io.novafoundation.nova.common.resources.ContextManager - -class NavigationHolder(val contextManager: ContextManager) { - - var navController: NavController? = null - private set - - fun attach(navController: NavController) { - this.navController = navController - } - - fun detach() { - navController = null - } - - fun finishApp() { - contextManager.getActivity()?.finish() - } -} - -fun NavigationHolder.executeBack() { - val popped = navController!!.popBackStack() - - if (!popped) { - contextManager.getActivity()!!.finish() - } -} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/buy/BuyNavigator.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/buy/BuyNavigator.kt deleted file mode 100644 index 84103b8122..0000000000 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/buy/BuyNavigator.kt +++ /dev/null @@ -1,7 +0,0 @@ -package io.novafoundation.nova.app.root.navigation.buy - -import io.novafoundation.nova.app.root.navigation.BaseNavigator -import io.novafoundation.nova.app.root.navigation.NavigationHolder -import io.novafoundation.nova.feature_buy_impl.presentation.BuyRouter - -class BuyNavigator(navigationHolder: NavigationHolder) : BuyRouter, BaseNavigator(navigationHolder) diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/cloudBackup/CloudBackupNavigator.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/cloudBackup/CloudBackupNavigator.kt deleted file mode 100644 index 1094220025..0000000000 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/cloudBackup/CloudBackupNavigator.kt +++ /dev/null @@ -1,7 +0,0 @@ -package io.novafoundation.nova.app.root.navigation.cloudBackup - -import io.novafoundation.nova.app.root.navigation.BaseNavigator -import io.novafoundation.nova.app.root.navigation.NavigationHolder -import io.novafoundation.nova.feature_cloud_backup_impl.presentation.CloudBackupRouter - -class CloudBackupNavigator(navigationHolder: NavigationHolder) : CloudBackupRouter, BaseNavigator(navigationHolder) 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 deleted file mode 100644 index f39f944c4f..0000000000 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/dApp/DAppNavigator.kt +++ /dev/null @@ -1,45 +0,0 @@ -package io.novafoundation.nova.app.root.navigation.dApp - -import io.novafoundation.nova.app.R -import io.novafoundation.nova.app.root.navigation.BaseNavigator -import io.novafoundation.nova.app.root.navigation.NavigationHolder -import io.novafoundation.nova.feature_dapp_impl.DAppRouter -import io.novafoundation.nova.feature_dapp_impl.presentation.addToFavourites.AddToFavouritesFragment -import io.novafoundation.nova.feature_dapp_impl.presentation.addToFavourites.AddToFavouritesPayload -import io.novafoundation.nova.feature_dapp_impl.presentation.browser.main.DAppBrowserFragment -import io.novafoundation.nova.feature_dapp_impl.presentation.search.DappSearchFragment -import io.novafoundation.nova.feature_dapp_impl.presentation.search.SearchPayload - -class DAppNavigator( - private val navigationHolder: NavigationHolder, -) : BaseNavigator(navigationHolder), DAppRouter { - - override fun openChangeAccount() = performNavigation(R.id.action_open_switch_wallet) - - 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, - args = DappSearchFragment.getBundle(SearchPayload(initialUrl = null)) - ) - - override fun openAddToFavourites(payload: AddToFavouritesPayload) = performNavigation( - actionId = R.id.action_DAppBrowserFragment_to_addToFavouritesFragment, - args = AddToFavouritesFragment.getBundle(payload) - ) - - override fun openAuthorizedDApps() = performNavigation( - actionId = R.id.action_mainFragment_to_authorizedDAppsFragment - ) -} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/deepLinking/DeepLinkingNavigator.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/deepLinking/DeepLinkingNavigator.kt deleted file mode 100644 index d8a0a664f4..0000000000 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/deepLinking/DeepLinkingNavigator.kt +++ /dev/null @@ -1,38 +0,0 @@ -package io.novafoundation.nova.app.root.navigation.deepLinking - -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_assets.presentation.AssetsRouter -import io.novafoundation.nova.feature_dapp_impl.DAppRouter -import io.novafoundation.nova.feature_deep_linking.presentation.handling.DeepLinkingRouter -import io.novafoundation.nova.feature_governance_api.presentation.referenda.details.ReferendumDetailsPayload -import io.novafoundation.nova.feature_governance_impl.presentation.GovernanceRouter -import io.novafoundation.nova.feature_wallet_api.presentation.model.AssetPayload - -class DeepLinkingNavigator( - private val accountRouter: AccountRouter, - private val assetsRouter: AssetsRouter, - private val dAppRouter: DAppRouter, - private val governanceRouter: GovernanceRouter -) : DeepLinkingRouter { - - override fun openAssetDetails(payload: AssetPayload) { - assetsRouter.openAssetDetails(payload) - } - - override fun openDAppBrowser(url: String) { - dAppRouter.openDAppBrowser(url) - } - - override fun openImportAccountScreen(importAccountPayload: ImportAccountPayload) { - accountRouter.openImportAccountScreen(importAccountPayload) - } - - override fun openReferendum(payload: ReferendumDetailsPayload) { - governanceRouter.openReferendum(payload) - } - - override fun openStakingDashboard() { - assetsRouter.openStaking() - } -} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/externalSign/ExternalSignNavigator.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/externalSign/ExternalSignNavigator.kt deleted file mode 100644 index 8d94537066..0000000000 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/externalSign/ExternalSignNavigator.kt +++ /dev/null @@ -1,17 +0,0 @@ -package io.novafoundation.nova.app.root.navigation.externalSign - -import io.novafoundation.nova.app.R -import io.novafoundation.nova.app.root.navigation.BaseNavigator -import io.novafoundation.nova.app.root.navigation.NavigationHolder -import io.novafoundation.nova.feature_external_sign_impl.ExternalSignRouter -import io.novafoundation.nova.feature_external_sign_impl.presentation.extrinsicDetails.ExternalExtrinsicDetailsFragment - -class ExternalSignNavigator( - navigationHolder: NavigationHolder -) : BaseNavigator(navigationHolder), ExternalSignRouter { - - override fun openExtrinsicDetails(extrinsicContent: String) = performNavigation( - actionId = R.id.action_ConfirmSignExtrinsicFragment_to_extrinsicDetailsFragment, - args = ExternalExtrinsicDetailsFragment.getBundle(extrinsicContent) - ) -} 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 deleted file mode 100644 index 57e7d29530..0000000000 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/governance/GovernanceNavigator.kt +++ /dev/null @@ -1,232 +0,0 @@ -package io.novafoundation.nova.app.root.navigation.governance - -import android.os.Bundle -import io.novafoundation.nova.app.R -import io.novafoundation.nova.app.root.navigation.BaseNavigator -import io.novafoundation.nova.app.root.navigation.NavigationHolder -import io.novafoundation.nova.app.root.navigation.Navigator -import io.novafoundation.nova.common.utils.showBrowser -import io.novafoundation.nova.feature_dapp_impl.presentation.browser.main.DAppBrowserFragment -import io.novafoundation.nova.feature_governance_impl.BuildConfig -import io.novafoundation.nova.feature_governance_impl.presentation.GovernanceRouter -import io.novafoundation.nova.feature_governance_impl.presentation.common.description.DescriptionFragment -import io.novafoundation.nova.feature_governance_impl.presentation.common.description.DescriptionPayload -import io.novafoundation.nova.feature_governance_impl.presentation.delegation.delegate.delegators.DelegateDelegatorsFragment -import io.novafoundation.nova.feature_governance_impl.presentation.delegation.delegate.delegators.DelegateDelegatorsPayload -import io.novafoundation.nova.feature_governance_impl.presentation.delegation.delegate.detail.main.DelegateDetailsFragment -import io.novafoundation.nova.feature_governance_impl.presentation.delegation.delegate.detail.main.DelegateDetailsPayload -import io.novafoundation.nova.feature_governance_impl.presentation.delegation.delegate.detail.votedReferenda.VotedReferendaFragment -import io.novafoundation.nova.feature_governance_impl.presentation.delegation.delegate.detail.votedReferenda.VotedReferendaPayload -import io.novafoundation.nova.feature_governance_impl.presentation.delegation.delegation.create.chooseAmount.NewDelegationChooseAmountFragment -import io.novafoundation.nova.feature_governance_impl.presentation.delegation.delegation.create.chooseAmount.NewDelegationChooseAmountPayload -import io.novafoundation.nova.feature_governance_impl.presentation.delegation.delegation.create.chooseTrack.NewDelegationChooseTracksFragment -import io.novafoundation.nova.feature_governance_impl.presentation.delegation.delegation.create.chooseTrack.NewDelegationChooseTracksPayload -import io.novafoundation.nova.feature_governance_impl.presentation.delegation.delegation.create.confirm.NewDelegationConfirmFragment -import io.novafoundation.nova.feature_governance_impl.presentation.delegation.delegation.create.confirm.NewDelegationConfirmPayload -import io.novafoundation.nova.feature_governance_impl.presentation.delegation.delegation.removeVotes.RemoveVotesFragment -import io.novafoundation.nova.feature_governance_impl.presentation.delegation.delegation.removeVotes.RemoveVotesPayload -import io.novafoundation.nova.feature_governance_impl.presentation.delegation.delegation.revoke.chooseTracks.RevokeDelegationChooseTracksFragment -import io.novafoundation.nova.feature_governance_impl.presentation.delegation.delegation.revoke.chooseTracks.RevokeDelegationChooseTracksPayload -import io.novafoundation.nova.feature_governance_impl.presentation.delegation.delegation.revoke.confirm.RevokeDelegationConfirmFragment -import io.novafoundation.nova.feature_governance_impl.presentation.delegation.delegation.revoke.confirm.RevokeDelegationConfirmPayload -import io.novafoundation.nova.feature_governance_impl.presentation.referenda.details.ReferendumDetailsFragment -import io.novafoundation.nova.feature_governance_api.presentation.referenda.details.ReferendumDetailsPayload -import io.novafoundation.nova.feature_governance_impl.presentation.common.info.ReferendumInfoFragment -import io.novafoundation.nova.feature_governance_impl.presentation.common.info.ReferendumInfoPayload -import io.novafoundation.nova.feature_governance_impl.presentation.referenda.full.ReferendumFullDetailsFragment -import io.novafoundation.nova.feature_governance_impl.presentation.referenda.full.ReferendumFullDetailsPayload -import io.novafoundation.nova.feature_governance_impl.presentation.referenda.vote.confirm.ConfirmReferendumVoteFragment -import io.novafoundation.nova.feature_governance_impl.presentation.referenda.vote.confirm.ConfirmVoteReferendumPayload -import io.novafoundation.nova.feature_governance_impl.presentation.referenda.vote.setup.common.SetupVoteFragment -import io.novafoundation.nova.feature_governance_impl.presentation.referenda.vote.setup.common.SetupVotePayload -import io.novafoundation.nova.feature_governance_impl.presentation.referenda.voters.ReferendumVotersFragment -import io.novafoundation.nova.feature_governance_impl.presentation.referenda.voters.ReferendumVotersPayload - -class GovernanceNavigator( - private val navigationHolder: NavigationHolder, - private val commonNavigator: Navigator, -) : BaseNavigator(navigationHolder), GovernanceRouter { - - override fun openReferendum(payload: ReferendumDetailsPayload) { - 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( - actionId = R.id.action_referendumDetailsFragment_to_referendumFullDetailsFragment, - args = ReferendumFullDetailsFragment.getBundle(payload) - ) - - override fun openReferendumVoters(payload: ReferendumVotersPayload) = performNavigation( - actionId = R.id.action_referendumDetailsFragment_to_referendumVotersFragment, - args = ReferendumVotersFragment.getBundle(payload) - ) - - override fun openSetupReferendumVote(payload: SetupVotePayload) = performNavigation( - actionId = R.id.action_referendumDetailsFragment_to_setupVoteReferendumFragment, - args = SetupVoteFragment.getBundle(payload) - ) - - override fun openSetupTinderGovVote(payload: SetupVotePayload) = performNavigation( - actionId = R.id.action_tinderGovCards_to_setupTinderGovVoteFragment, - args = SetupVoteFragment.getBundle(payload) - ) - - override fun backToReferendumDetails() = performNavigation(R.id.action_confirmReferendumVote_to_referendumDetailsFragment) - - override fun finishUnlockFlow(shouldCloseLocksScreen: Boolean) { - if (shouldCloseLocksScreen) { - performNavigation(R.id.action_confirmReferendumVote_to_mainFragment) - } else { - back() - } - } - - override fun openWalletDetails(id: Long) { - commonNavigator.openWalletDetails(id) - } - - override fun openAddDelegation() { - performNavigation( - cases = arrayOf( - R.id.mainFragment to R.id.action_mainFragment_to_delegation, - R.id.yourDelegationsFragment to R.id.action_yourDelegations_to_delegationList, - ) - ) - } - - override fun openYourDelegations() { - performNavigation(R.id.action_mainFragment_to_your_delegation) - } - - override fun openBecomingDelegateTutorial() { - navigationHolder.contextManager.getActivity() - ?.showBrowser(BuildConfig.DELEGATION_TUTORIAL_URL) - } - - override fun backToYourDelegations() = performNavigation(R.id.action_back_to_your_delegations) - - override fun openRevokeDelegationChooseTracks(payload: RevokeDelegationChooseTracksPayload) = performNavigation( - actionId = R.id.action_delegateDetailsFragment_to_revokeDelegationChooseTracksFragment, - args = RevokeDelegationChooseTracksFragment.getBundle(payload) - ) - - override fun openRevokeDelegationsConfirm(payload: RevokeDelegationConfirmPayload) = performNavigation( - actionId = R.id.action_revokeDelegationChooseTracksFragment_to_revokeDelegationConfirmFragment, - args = RevokeDelegationConfirmFragment.getBundle(payload) - ) - - override fun openDelegateSearch() { - performNavigation(R.id.action_delegateListFragment_to_delegateSearchFragment) - } - - override fun openSelectGovernanceTracks(bundle: Bundle) { - performNavigation(R.id.action_open_select_governance_tracks, args = bundle) - } - - override fun openTinderGovCards() { - performNavigation(R.id.action_openTinderGovCards) - } - - override fun openTinderGovBasket() { - performNavigation(R.id.action_tinderGovCards_to_tinderGovBasket) - } - - override fun openConfirmTinderGovVote() { - performNavigation(R.id.action_setupTinderGovBasket_to_confirmTinderGovVote) - } - - override fun backToTinderGovCards() = performNavigation( - actionId = R.id.action_confirmTinderGovVote_to_tinderGovCards - ) - - override fun openReferendumInfo(payload: ReferendumInfoPayload) = performNavigation( - cases = arrayOf( - R.id.tinderGovCards to R.id.action_tinderGovCards_to_referendumInfo, - R.id.setupTinderGovBasketFragment to R.id.action_setupTinderGovBasket_to_referendumInfo - ), - args = ReferendumInfoFragment.getBundle(payload) - ) - - override fun openReferendaSearch() { - performNavigation(R.id.action_open_referenda_search) - } - - override fun openReferendaFilters() { - performNavigation(R.id.action_open_referenda_filters) - } - - override fun openRemoveVotes(payload: RemoveVotesPayload) = performNavigation( - actionId = R.id.action_open_remove_votes, - args = RemoveVotesFragment.getBundle(payload) - ) - - override fun openDelegateDelegators(payload: DelegateDelegatorsPayload) { - val bundle = DelegateDelegatorsFragment.getBundle(payload) - return performNavigation(R.id.action_delegateDetailsFragment_to_delegateDelegatorsFragment, args = bundle) - } - - override fun openDelegateDetails(payload: DelegateDetailsPayload) { - performNavigation( - cases = arrayOf( - R.id.delegateListFragment to R.id.action_delegateListFragment_to_delegateDetailsFragment, - R.id.yourDelegationsFragment to R.id.action_yourDelegations_to_delegationDetails, - R.id.delegateSearchFragment to R.id.action_delegateSearchFragment_to_delegateDetailsFragment, - ), - args = DelegateDetailsFragment.getBundle(payload) - ) - } - - override fun openNewDelegationChooseTracks(payload: NewDelegationChooseTracksPayload) = performNavigation( - actionId = R.id.action_delegateDetailsFragment_to_selectDelegationTracks, - args = NewDelegationChooseTracksFragment.getBundle(payload) - ) - - override fun openNewDelegationChooseAmount(payload: NewDelegationChooseAmountPayload) = performNavigation( - actionId = R.id.action_selectDelegationTracks_to_newDelegationChooseAmountFragment, - args = NewDelegationChooseAmountFragment.getBundle(payload) - ) - - override fun openNewDelegationConfirm(payload: NewDelegationConfirmPayload) = performNavigation( - actionId = R.id.action_newDelegationChooseAmountFragment_to_newDelegationConfirmFragment, - args = NewDelegationConfirmFragment.getBundle(payload) - ) - - override fun openVotedReferenda(payload: VotedReferendaPayload) = performNavigation( - actionId = R.id.action_delegateDetailsFragment_to_votedReferendaFragment, - args = VotedReferendaFragment.getBundle(payload) - ) - - override fun openDelegateFullDescription(payload: DescriptionPayload) = performNavigation( - actionId = R.id.action_delegateDetailsFragment_to_delegateFullDescription, - args = DescriptionFragment.getBundle(payload) - ) - - override fun openDAppBrowser(initialUrl: String) = performNavigation( - actionId = R.id.action_referendumDetailsFragment_to_DAppBrowserGraph, - args = DAppBrowserFragment.getBundle(initialUrl) - ) - - override fun openReferendumDescription(payload: DescriptionPayload) = performNavigation( - actionId = R.id.action_referendumDetailsFragment_to_referendumDescription, - args = DescriptionFragment.getBundle(payload) - ) - - override fun openConfirmVoteReferendum(payload: ConfirmVoteReferendumPayload) = performNavigation( - actionId = R.id.action_setupVoteReferendumFragment_to_confirmReferendumVote, - args = ConfirmReferendumVoteFragment.getBundle(payload) - ) - - override fun openGovernanceLocksOverview() = performNavigation( - actionId = R.id.action_mainFragment_to_governanceLocksOverview - ) - - override fun openConfirmGovernanceUnlock() = performNavigation( - actionId = R.id.action_governanceLocksOverview_to_confirmGovernanceUnlock - ) -} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/holders/NavigationHolder.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/holders/NavigationHolder.kt new file mode 100644 index 0000000000..83598d34e0 --- /dev/null +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/holders/NavigationHolder.kt @@ -0,0 +1,46 @@ +package io.novafoundation.nova.app.root.navigation.holders + +import androidx.navigation.NavController +import io.novafoundation.nova.common.resources.ContextManager + +abstract class NavigationHolder(val contextManager: ContextManager) { + + var navController: NavController? = null + private set + + fun isControllerAttached(): Boolean { + return navController != null + } + + fun attach(navController: NavController) { + this.navController = navController + } + + /** + * Detaches the current navController only if it matches the one provided. + * This check ensures that if a new screen with a navController is attached, + * it doesn't lose its navController when the previous screen calls detach. + * By verifying equality, we prevent unintended detachment. + */ + fun detachNavController(navController: NavController) { + if (this.navController == navController) { + this.navController = null + } + } + + fun detach() { + navController = null + } + + fun finishApp() { + contextManager.getActivity()?.finish() + } + + fun executeBack() { + val popped = navController!!.popBackStack() + + if (!popped) { + contextManager.getActivity()!!.finish() + } + } +} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/holders/RootNavigationHolder.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/holders/RootNavigationHolder.kt new file mode 100644 index 0000000000..b2d61304d3 --- /dev/null +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/holders/RootNavigationHolder.kt @@ -0,0 +1,5 @@ +package io.novafoundation.nova.app.root.navigation.holders + +import io.novafoundation.nova.common.resources.ContextManager + +class RootNavigationHolder(contextManager: ContextManager) : NavigationHolder(contextManager) diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/holders/SplitScreenNavigationHolder.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/holders/SplitScreenNavigationHolder.kt new file mode 100644 index 0000000000..cd8c568868 --- /dev/null +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/holders/SplitScreenNavigationHolder.kt @@ -0,0 +1,5 @@ +package io.novafoundation.nova.app.root.navigation.holders + +import io.novafoundation.nova.common.resources.ContextManager + +class SplitScreenNavigationHolder(contextManager: ContextManager) : NavigationHolder(contextManager) diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/ledger/LedgerNavigator.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/ledger/LedgerNavigator.kt deleted file mode 100644 index 14acfb5a6c..0000000000 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/ledger/LedgerNavigator.kt +++ /dev/null @@ -1,70 +0,0 @@ -package io.novafoundation.nova.app.root.navigation.ledger - -import io.novafoundation.nova.app.R -import io.novafoundation.nova.app.root.navigation.BaseNavigator -import io.novafoundation.nova.app.root.navigation.NavigationHolder -import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter -import io.novafoundation.nova.feature_ledger_impl.presentation.LedgerRouter -import io.novafoundation.nova.feature_ledger_impl.presentation.account.addChain.selectAddress.AddLedgerChainAccountSelectAddressFragment -import io.novafoundation.nova.feature_ledger_impl.presentation.account.addChain.selectAddress.AddLedgerChainAccountSelectAddressPayload -import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectAddress.SelectAddressLedgerFragment -import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectAddress.SelectLedgerAddressPayload -import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.finish.FinishImportGenericLedgerFragment -import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.finish.FinishImportGenericLedgerPayload -import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.preview.PreviewImportGenericLedgerFragment -import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.preview.PreviewImportGenericLedgerPayload -import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.legacy.finish.FinishImportLedgerFragment -import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.legacy.finish.FinishImportLedgerPayload - -class LedgerNavigator( - private val accountRouter: AccountRouter, - navigationHolder: NavigationHolder -) : BaseNavigator(navigationHolder), LedgerRouter { - - override fun openImportFillWallet() = performNavigation(R.id.action_startImportLedgerFragment_to_fillWalletImportLedgerFragment) - - override fun returnToImportFillWallet() = performNavigation(R.id.action_selectAddressImportLedgerFragment_to_fillWalletImportLedgerFragment) - - override fun openSelectImportAddress(payload: SelectLedgerAddressPayload) = performNavigation( - actionId = R.id.action_selectLedgerImportFragment_to_selectAddressImportLedgerFragment, - args = SelectAddressLedgerFragment.getBundle(payload) - ) - - override fun openCreatePincode() { - accountRouter.openCreatePincode() - } - - override fun openMain() { - accountRouter.openMain() - } - - override fun openFinishImportLedger(payload: FinishImportLedgerPayload) = performNavigation( - actionId = R.id.action_fillWalletImportLedgerFragment_to_finishImportLedgerFragment, - args = FinishImportLedgerFragment.getBundle(payload) - ) - - override fun finishSignFlow() { - back() - } - - override fun openAddChainAccountSelectAddress(payload: AddLedgerChainAccountSelectAddressPayload) = performNavigation( - actionId = R.id.action_addChainAccountSelectLedgerFragment_to_addChainAccountSelectAddressLedgerFragment, - args = AddLedgerChainAccountSelectAddressFragment.getBundle(payload) - ) - - override fun openSelectLedgerGeneric() = performNavigation(R.id.action_startImportGenericLedgerFragment_to_selectLedgerGenericImportFragment) - - override fun openSelectAddressGenericLedger(payload: SelectLedgerAddressPayload) = performNavigation( - actionId = R.id.action_selectLedgerGenericImportFragment_to_selectAddressImportGenericLedgerFragment, - args = SelectAddressLedgerFragment.getBundle(payload) - ) - override fun openPreviewLedgerAccountsGeneric(payload: PreviewImportGenericLedgerPayload) = performNavigation( - actionId = R.id.action_selectAddressImportGenericLedgerFragment_to_previewImportGenericLedgerFragment, - args = PreviewImportGenericLedgerFragment.getBundle(payload) - ) - - override fun openFinishImportLedgerGeneric(payload: FinishImportGenericLedgerPayload) = performNavigation( - actionId = R.id.action_previewImportGenericLedgerFragment_to_finishImportGenericLedgerFragment, - args = FinishImportGenericLedgerFragment.getBundle(payload) - ) -} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigationFragment/MainNavHostFragment.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigationFragment/MainNavHostFragment.kt new file mode 100644 index 0000000000..fa65b140fb --- /dev/null +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigationFragment/MainNavHostFragment.kt @@ -0,0 +1,8 @@ +package io.novafoundation.nova.app.root.navigation.navigationFragment + +import io.novafoundation.nova.app.R + +class MainNavHostFragment : NovaNavHostFragment() { + + override val containerId: Int = R.id.mainNavHost +} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/NovaNavHostFragment.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigationFragment/NovaNavHostFragment.kt similarity index 76% rename from app/src/main/java/io/novafoundation/nova/app/root/navigation/NovaNavHostFragment.kt rename to app/src/main/java/io/novafoundation/nova/app/root/navigation/navigationFragment/NovaNavHostFragment.kt index 8eab521632..8712bce2e4 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/NovaNavHostFragment.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigationFragment/NovaNavHostFragment.kt @@ -1,19 +1,19 @@ -package io.novafoundation.nova.app.root.navigation +package io.novafoundation.nova.app.root.navigation.navigationFragment import android.annotation.SuppressLint import androidx.navigation.NavController import androidx.navigation.fragment.DialogFragmentNavigator import androidx.navigation.fragment.NavHostFragment -import io.novafoundation.nova.app.R import io.novafoundation.nova.app.root.navigation.navigators.AddFragmentNavigator -class NovaNavHostFragment : NavHostFragment() { +abstract class NovaNavHostFragment : NavHostFragment() { + + abstract val containerId: Int @SuppressLint("MissingSuperCall") override fun onCreateNavController(navController: NavController) { navController.navigatorProvider.addNavigator(DialogFragmentNavigator(requireContext(), childFragmentManager)) - - val addFragmentNavigator = AddFragmentNavigator(requireContext(), childFragmentManager, R.id.navHost) + val addFragmentNavigator = AddFragmentNavigator(requireContext(), childFragmentManager, containerId) navController.navigatorProvider.addNavigator(addFragmentNavigator) } diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigationFragment/RootNavHostFragment.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigationFragment/RootNavHostFragment.kt new file mode 100644 index 0000000000..de609c5e99 --- /dev/null +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigationFragment/RootNavHostFragment.kt @@ -0,0 +1,8 @@ +package io.novafoundation.nova.app.root.navigation.navigationFragment + +import io.novafoundation.nova.app.R + +class RootNavHostFragment : NovaNavHostFragment() { + + override val containerId: Int = R.id.rootNavHost +} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/BaseNavigator.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/BaseNavigator.kt new file mode 100644 index 0000000000..068b57b510 --- /dev/null +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/BaseNavigator.kt @@ -0,0 +1,33 @@ +package io.novafoundation.nova.app.root.navigation.navigators + +import io.novafoundation.nova.app.root.navigation.navigators.builder.NavigationBuilderRegistry +import io.novafoundation.nova.common.navigation.ReturnableRouter + +abstract class BaseNavigator( + private val navigationHoldersRegistry: NavigationHoldersRegistry +) : ReturnableRouter { + + val currentBackStackEntry + get() = navigationHoldersRegistry.firstAttachedNavController + ?.currentBackStackEntry + + val previousBackStackEntry + get() = navigationHoldersRegistry.firstAttachedNavController + ?.previousBackStackEntry + + val currentDestination + get() = navigationHoldersRegistry.firstAttachedNavController + ?.currentDestination + + override fun back() { + navigationHoldersRegistry.firstAttachedHolder.executeBack() + } + + fun finishApp() { + navigationHoldersRegistry.firstAttachedHolder.finishApp() + } + + fun navigationBuilder(): NavigationBuilderRegistry { + return navigationHoldersRegistry.navigationBuilder() + } +} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/NavigationHoldersRegistry.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/NavigationHoldersRegistry.kt new file mode 100644 index 0000000000..9abf305311 --- /dev/null +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/NavigationHoldersRegistry.kt @@ -0,0 +1,25 @@ +package io.novafoundation.nova.app.root.navigation.navigators + +import androidx.navigation.NavController +import io.novafoundation.nova.app.root.navigation.holders.NavigationHolder +import io.novafoundation.nova.app.root.navigation.holders.RootNavigationHolder +import io.novafoundation.nova.app.root.navigation.holders.SplitScreenNavigationHolder +import io.novafoundation.nova.app.root.navigation.navigators.builder.NavigationBuilderRegistry + +class NavigationHoldersRegistry( + val splitScreenNavigationHolder: SplitScreenNavigationHolder, + val rootNavigationHolder: RootNavigationHolder +) { + + private val holders = listOf(splitScreenNavigationHolder, rootNavigationHolder) + + val firstAttachedHolder: NavigationHolder + get() = holders.first { it.isControllerAttached() } + + val firstAttachedNavController: NavController? + get() = firstAttachedHolder.navController +} + +fun NavigationHoldersRegistry.navigationBuilder(): NavigationBuilderRegistry { + return NavigationBuilderRegistry(this) +} 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/navigators/Navigator.kt similarity index 54% rename from app/src/main/java/io/novafoundation/nova/app/root/navigation/Navigator.kt rename to app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/Navigator.kt index 035bca311e..20f8f4e294 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/navigators/Navigator.kt @@ -1,14 +1,14 @@ -package io.novafoundation.nova.app.root.navigation +package io.novafoundation.nova.app.root.navigation.navigators import android.os.Bundle import androidx.lifecycle.asFlow -import androidx.navigation.NavController import androidx.navigation.NavOptions import io.novafoundation.nova.app.R import io.novafoundation.nova.app.root.navigation.delayedNavigation.BackDelayedNavigation import io.novafoundation.nova.app.root.navigation.delayedNavigation.NavComponentDelayedNavigation import io.novafoundation.nova.app.root.presentation.RootRouter import io.novafoundation.nova.common.navigation.DelayedNavigation +import io.novafoundation.nova.common.navigation.DelayedNavigationRouter import io.novafoundation.nova.common.utils.getParcelableCompat import io.novafoundation.nova.common.utils.postToUiThread import io.novafoundation.nova.feature_account_api.domain.model.PolkadotVaultVariant @@ -97,42 +97,43 @@ import io.novafoundation.nova.splash.SplashRouter import kotlinx.coroutines.flow.Flow class Navigator( - private val navigationHolder: NavigationHolder, + navigationHoldersRegistry: NavigationHoldersRegistry, private val walletConnectDelegate: WalletConnectRouter, private val stakingDashboardDelegate: StakingDashboardRouter -) : BaseNavigator(navigationHolder), +) : BaseNavigator(navigationHoldersRegistry), SplashRouter, OnboardingRouter, AccountRouter, AssetsRouter, RootRouter, - CrowdloanRouter { - - private val navController: NavController? - get() = navigationHolder.navController + CrowdloanRouter, + DelayedNavigationRouter { override fun openWelcomeScreen() { - when (navController?.currentDestination?.id) { - R.id.accountsFragment -> navController?.navigate(R.id.action_walletManagment_to_welcome, WelcomeFragment.bundle(false)) - R.id.splashFragment -> navController?.navigate(R.id.action_splash_to_onboarding, WelcomeFragment.bundle(false)) - } + navigationBuilder().cases() + .addCase(R.id.accountsFragment, R.id.action_walletManagment_to_welcome) + .addCase(R.id.splashFragment, R.id.action_splash_to_onboarding) + .setArgs(WelcomeFragment.bundle(false)) + .navigateInFirstAttachedContext() } override fun openInitialCheckPincode() { - val action = PinCodeAction.Check(NavComponentDelayedNavigation(R.id.action_open_main), ToolbarConfiguration()) - val bundle = PincodeFragment.getPinCodeBundle(action) - navController?.navigate(R.id.action_splash_to_pin, bundle) + val action = PinCodeAction.Check(NavComponentDelayedNavigation(R.id.action_open_split_screen), ToolbarConfiguration()) + + navigationBuilder().action(R.id.action_splash_to_pin) + .setArgs(PincodeFragment.getPinCodeBundle(action)) + .navigateInRoot() } override fun openCreateFirstWallet() { - navController?.navigate( - R.id.action_welcomeFragment_to_startCreateWallet, - StartCreateWalletFragment.bundle(StartCreateWalletPayload(FlowType.FIRST_WALLET)) - ) + navigationBuilder().action(R.id.action_welcomeFragment_to_startCreateWallet) + .setArgs(StartCreateWalletFragment.bundle(StartCreateWalletPayload(FlowType.FIRST_WALLET))) + .navigateInFirstAttachedContext() } override fun openMain() { - navController?.navigate(R.id.action_open_main) + navigationBuilder().action(R.id.action_open_split_screen) + .navigateInRoot() } override fun openAfterPinCode(delayedNavigation: DelayedNavigation) { @@ -146,227 +147,269 @@ class Navigator( .setPopExitAnim(R.anim.fragment_close_exit) .build() - navController?.navigate(delayedNavigation.globalActionId, delayedNavigation.extras, navOptions) + navigationBuilder().action(delayedNavigation.globalActionId) + .setArgs(delayedNavigation.extras) + .setNavOptions(navOptions) + .navigateInFirstAttachedContext() } - is BackDelayedNavigation -> { - navController?.popBackStack() - } + is BackDelayedNavigation -> back() } } override fun openCreatePincode() { - val bundle = buildCreatePinBundle() - - when (navController?.currentDestination?.id) { - R.id.splashFragment -> navController?.navigate(R.id.action_splash_to_pin, bundle) - R.id.importAccountFragment -> navController?.navigate(R.id.action_importAccountFragment_to_pincodeFragment, bundle) - R.id.confirmMnemonicFragment -> navController?.navigate(R.id.action_confirmMnemonicFragment_to_pincodeFragment, bundle) - R.id.createWatchWalletFragment -> navController?.navigate(R.id.action_watchWalletFragment_to_pincodeFragment, bundle) - R.id.finishImportParitySignerFragment -> navController?.navigate(R.id.action_finishImportParitySignerFragment_to_pincodeFragment, bundle) - R.id.finishImportLedgerFragment -> navController?.navigate(R.id.action_finishImportLedgerFragment_to_pincodeFragment, bundle) - R.id.createCloudBackupPasswordFragment -> navController?.navigate(R.id.action_createCloudBackupPasswordFragment_to_pincodeFragment, bundle) - R.id.restoreCloudBackupFragment -> navController?.navigate(R.id.action_restoreCloudBackupFragment_to_pincodeFragment, bundle) - R.id.finishImportGenericLedgerFragment -> navController?.navigate(R.id.action_finishImportGenericLedgerFragment_to_pincodeFragment, bundle) - } + val args = buildCreatePinBundle() + + navigationBuilder().cases() + .addCase(R.id.splashFragment, R.id.action_splash_to_pin) + .addCase(R.id.importAccountFragment, R.id.action_importAccountFragment_to_pincodeFragment) + .addCase(R.id.confirmMnemonicFragment, R.id.action_confirmMnemonicFragment_to_pincodeFragment) + .addCase(R.id.createWatchWalletFragment, R.id.action_watchWalletFragment_to_pincodeFragment) + .addCase(R.id.finishImportParitySignerFragment, R.id.action_finishImportParitySignerFragment_to_pincodeFragment) + .addCase(R.id.finishImportLedgerFragment, R.id.action_finishImportLedgerFragment_to_pincodeFragment) + .addCase(R.id.createCloudBackupPasswordFragment, R.id.action_createCloudBackupPasswordFragment_to_pincodeFragment) + .addCase(R.id.restoreCloudBackupFragment, R.id.action_restoreCloudBackupFragment_to_pincodeFragment) + .addCase(R.id.finishImportGenericLedgerFragment, R.id.action_finishImportGenericLedgerFragment_to_pincodeFragment) + .setArgs(args) + .navigateInRoot() } override fun openAdvancedSettings(payload: AdvancedEncryptionModePayload) { - navController?.navigate(R.id.action_open_advancedEncryptionFragment, AdvancedEncryptionFragment.getBundle(payload)) + navigationBuilder().action(R.id.action_open_advancedEncryptionFragment) + .setArgs(AdvancedEncryptionFragment.getBundle(payload)) + .navigateInFirstAttachedContext() } override fun openConfirmMnemonicOnCreate(confirmMnemonicPayload: ConfirmMnemonicPayload) { - val bundle = ConfirmMnemonicFragment.getBundle(confirmMnemonicPayload) - - navController?.navigate( - R.id.action_backupMnemonicFragment_to_confirmMnemonicFragment, - bundle - ) + navigationBuilder().action(R.id.action_backupMnemonicFragment_to_confirmMnemonicFragment) + .setArgs(ConfirmMnemonicFragment.getBundle(confirmMnemonicPayload)) + .navigateInFirstAttachedContext() } override fun openImportAccountScreen(payload: ImportAccountPayload) { - val currentDestination = navController?.currentDestination ?: return - val actionId = when (currentDestination.id) { - // Wee need the splash 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(actionId, ImportAccountFragment.getBundle(payload)) + navigationBuilder().cases() + .addCase(R.id.splashFragment, R.id.action_splashFragment_to_import_nav_graph) + .setFallbackCase(R.id.action_import_nav_graph) + .setArgs(ImportAccountFragment.getBundle(payload)) + .navigateInFirstAttachedContext() } override fun openMnemonicScreen(accountName: String?, addAccountPayload: AddAccountPayload) { - val destination = when (val currentDestinationId = navController?.currentDestination?.id) { - R.id.welcomeFragment -> R.id.action_welcomeFragment_to_mnemonic_nav_graph - R.id.startCreateWalletFragment -> R.id.action_startCreateWalletFragment_to_mnemonic_nav_graph - R.id.walletDetailsFragment -> R.id.action_accountDetailsFragment_to_mnemonic_nav_graph - else -> throw IllegalArgumentException("Unknown current destination to open mnemonic screen: $currentDestinationId") - } - val payload = BackupMnemonicPayload.Create(accountName, addAccountPayload) - navController?.navigate(destination, BackupMnemonicFragment.getBundle(payload)) + + navigationBuilder().cases() + .addCase(R.id.welcomeFragment, R.id.action_welcomeFragment_to_mnemonic_nav_graph) + .addCase(R.id.startCreateWalletFragment, R.id.action_startCreateWalletFragment_to_mnemonic_nav_graph) + .addCase(R.id.walletDetailsFragment, R.id.action_accountDetailsFragment_to_mnemonic_nav_graph) + .setArgs(BackupMnemonicFragment.getBundle(payload)) + .navigateInFirstAttachedContext() } override fun openContribute(payload: ContributePayload) { val bundle = CrowdloanContributeFragment.getBundle(payload) - when (navController?.currentDestination?.id) { - R.id.mainFragment -> navController?.navigate(R.id.action_mainFragment_to_crowdloanContributeFragment, bundle) - R.id.moonbeamCrowdloanTermsFragment -> navController?.navigate(R.id.action_moonbeamCrowdloanTermsFragment_to_crowdloanContributeFragment, bundle) - } + navigationBuilder().cases() + .addCase(R.id.mainFragment, R.id.action_mainFragment_to_crowdloanContributeFragment) + .addCase(R.id.moonbeamCrowdloanTermsFragment, R.id.action_moonbeamCrowdloanTermsFragment_to_crowdloanContributeFragment) + .setArgs(bundle) + .navigateInFirstAttachedContext() } + @Deprecated("TODO: Use communicator api instead") override val customBonusFlow: Flow - get() = navController!!.currentBackStackEntry!!.savedStateHandle + get() = currentBackStackEntry!!.savedStateHandle .getLiveData(CrowdloanContributeFragment.KEY_BONUS_LIVE_DATA) .asFlow() + @Deprecated("TODO: Use communicator api instead") override val latestCustomBonus: BonusPayload? - get() = navController!!.currentBackStackEntry!!.savedStateHandle + get() = currentBackStackEntry!!.savedStateHandle .get(CrowdloanContributeFragment.KEY_BONUS_LIVE_DATA) override fun openCustomContribute(payload: CustomContributePayload) { - navController?.navigate(R.id.action_crowdloanContributeFragment_to_customContributeFragment, CustomContributeFragment.getBundle(payload)) + navigationBuilder().action(R.id.action_crowdloanContributeFragment_to_customContributeFragment) + .setArgs(CustomContributeFragment.getBundle(payload)) + .navigateInFirstAttachedContext() } + @Deprecated("TODO: Use communicator api instead") override fun setCustomBonus(payload: BonusPayload) { - navController!!.previousBackStackEntry!!.savedStateHandle.set(CrowdloanContributeFragment.KEY_BONUS_LIVE_DATA, payload) + previousBackStackEntry!!.savedStateHandle.set(CrowdloanContributeFragment.KEY_BONUS_LIVE_DATA, payload) } override fun openConfirmContribute(payload: ConfirmContributePayload) { - navController?.navigate(R.id.action_crowdloanContributeFragment_to_confirmContributeFragment, ConfirmContributeFragment.getBundle(payload)) - } - - override fun back() { - navigationHolder.executeBack() + navigationBuilder().action(R.id.action_crowdloanContributeFragment_to_confirmContributeFragment) + .setArgs(ConfirmContributeFragment.getBundle(payload)) + .navigateInFirstAttachedContext() } override fun returnToMain() { - navController?.navigate(R.id.back_to_main) + navigationBuilder().action(R.id.back_to_main) + .navigateInFirstAttachedContext() } override fun openMoonbeamFlow(payload: ContributePayload) { - navController?.navigate(R.id.action_mainFragment_to_moonbeamCrowdloanTermsFragment, MoonbeamCrowdloanTermsFragment.getBundle(payload)) + navigationBuilder().action(R.id.action_mainFragment_to_moonbeamCrowdloanTermsFragment) + .setArgs(MoonbeamCrowdloanTermsFragment.getBundle(payload)) + .navigateInFirstAttachedContext() } override fun openAddAccount(payload: AddAccountPayload) { - navController?.navigate(R.id.action_open_onboarding, WelcomeFragment.bundle(payload)) + navigationBuilder().action(R.id.action_open_onboarding) + .setArgs(WelcomeFragment.bundle(payload)) + .navigateInFirstAttachedContext() } - override fun openFilter(payload: TransactionHistoryFilterPayload) = performNavigation( - actionId = R.id.action_mainFragment_to_filterFragment, - args = TransactionHistoryFilterFragment.getBundle(payload) - ) + override fun openFilter(payload: TransactionHistoryFilterPayload) { + navigationBuilder().action(R.id.action_mainFragment_to_filterFragment) + .setArgs(TransactionHistoryFilterFragment.getBundle(payload)) + .navigateInFirstAttachedContext() + } override fun openSend(payload: SendPayload, initialRecipientAddress: String?) { val extras = SelectSendFragment.getBundle(payload, initialRecipientAddress) - navController?.navigate(R.id.action_open_send, extras) + navigationBuilder().action(R.id.action_open_send) + .setArgs(extras) + .navigateInFirstAttachedContext() } override fun openConfirmTransfer(transferDraft: TransferDraft) { val bundle = ConfirmSendFragment.getBundle(transferDraft) - navController?.navigate(R.id.action_chooseAmountFragment_to_confirmTransferFragment, bundle) + navigationBuilder().action(R.id.action_chooseAmountFragment_to_confirmTransferFragment) + .setArgs(bundle) + .navigateInFirstAttachedContext() } override fun openTransferDetail(transaction: OperationParcelizeModel.Transfer) { val bundle = TransferDetailFragment.getBundle(transaction) - navController?.navigate(R.id.open_transfer_detail, bundle) + navigationBuilder().action(R.id.open_transfer_detail) + .setArgs(bundle) + .navigateInFirstAttachedContext() } override fun openRewardDetail(reward: OperationParcelizeModel.Reward) { val bundle = RewardDetailFragment.getBundle(reward) - navController?.navigate(R.id.open_reward_detail, bundle) + navigationBuilder().action(R.id.open_reward_detail) + .setArgs(bundle) + .navigateInFirstAttachedContext() } override fun openPoolRewardDetail(reward: OperationParcelizeModel.PoolReward) { val bundle = PoolRewardDetailFragment.getBundle(reward) - navController?.navigate(R.id.open_pool_reward_detail, bundle) + navigationBuilder().action(R.id.open_pool_reward_detail) + .setArgs(bundle) + .navigateInFirstAttachedContext() } override fun openSwapDetail(swap: OperationParcelizeModel.Swap) { val bundle = SwapDetailFragment.getBundle(swap) - navController?.navigate(R.id.open_swap_detail, bundle) + navigationBuilder().action(R.id.open_swap_detail) + .setArgs(bundle) + .navigateInFirstAttachedContext() } override fun openExtrinsicDetail(extrinsic: OperationParcelizeModel.Extrinsic) { - val bundle = ExtrinsicDetailFragment.getBundle(extrinsic) - - navController?.navigate(R.id.open_extrinsic_detail, bundle) + navigationBuilder().action(R.id.open_extrinsic_detail) + .setArgs(ExtrinsicDetailFragment.getBundle(extrinsic)) + .navigateInFirstAttachedContext() } override fun openWallets() { - navController?.navigate(R.id.action_open_accounts) + navigationBuilder().action(R.id.action_open_accounts) + .navigateInFirstAttachedContext() } override fun openSwitchWallet() { - navController?.navigate(R.id.action_open_switch_wallet) + navigationBuilder().action(R.id.action_open_switch_wallet) + .navigateInFirstAttachedContext() } override fun openDelegatedAccountsUpdates() { - navController?.navigate(R.id.action_switchWalletFragment_to_delegatedAccountUpdates) + navigationBuilder().action(R.id.action_switchWalletFragment_to_delegatedAccountUpdates) + .navigateInFirstAttachedContext() } override fun openSelectAddress(arguments: Bundle) { - navController?.navigate(R.id.action_open_select_address, arguments) + navigationBuilder().action(R.id.action_open_select_address) + .setArgs(arguments) + .navigateInFirstAttachedContext() } override fun openSelectMultipleWallets(arguments: Bundle) { - navController?.navigate(R.id.action_open_select_multiple_wallets, arguments) + navigationBuilder().action(R.id.action_open_select_multiple_wallets) + .setArgs(arguments) + .navigateInFirstAttachedContext() } override fun openNodes() { - navController?.navigate(R.id.action_mainFragment_to_nodesFragment) + navigationBuilder().action(R.id.action_mainFragment_to_nodesFragment) + .navigateInFirstAttachedContext() } override fun openReceive(assetPayload: AssetPayload) { - navController?.navigate(R.id.action_open_receive, ReceiveFragment.getBundle(assetPayload)) + navigationBuilder().action(R.id.action_open_receive) + .setArgs(ReceiveFragment.getBundle(assetPayload)) + .navigateInFirstAttachedContext() } override fun openAssetSearch() { - navController?.navigate(R.id.action_mainFragment_to_assetSearchFragment) + navigationBuilder().action(R.id.action_mainFragment_to_assetSearchFragment) + .navigateInFirstAttachedContext() } override fun openManageTokens() { - navController?.navigate(R.id.action_mainFragment_to_manageTokensGraph) + navigationBuilder().action(R.id.action_mainFragment_to_manageTokensGraph) + .navigateInFirstAttachedContext() } override fun openManageChainTokens(payload: ManageChainTokensPayload) { val args = ManageChainTokensFragment.getBundle(payload) - navController?.navigate(R.id.action_manageTokensFragment_to_manageChainTokensFragment, args) + navigationBuilder().action(R.id.action_manageTokensFragment_to_manageChainTokensFragment) + .setArgs(args) + .navigateInFirstAttachedContext() } override fun openAddTokenSelectChain() { - navController?.navigate(R.id.action_manageTokensFragment_to_addTokenSelectChainFragment) + navigationBuilder().action(R.id.action_manageTokensFragment_to_addTokenSelectChainFragment) + .navigateInFirstAttachedContext() } override fun openSendFlow() { - navController?.navigate(R.id.action_mainFragment_to_sendFlow) + navigationBuilder().action(R.id.action_mainFragment_to_sendFlow) + .navigateInFirstAttachedContext() } override fun openReceiveFlow() { - navController?.navigate(R.id.action_mainFragment_to_receiveFlow) + navigationBuilder().action(R.id.action_mainFragment_to_receiveFlow) + .navigateInFirstAttachedContext() } override fun openBuyFlow() { - navController?.navigate(R.id.action_mainFragment_to_buyFlow) + navigationBuilder().action(R.id.action_mainFragment_to_buyFlow) + .navigateInFirstAttachedContext() } override fun openBuyFlowFromSendFlow() { - navController?.navigate(R.id.action_sendFlow_to_buyFlow) + navigationBuilder().action(R.id.action_sendFlow_to_buyFlow) + .navigateInFirstAttachedContext() } override fun openAddTokenEnterInfo(payload: AddTokenEnterInfoPayload) { val args = AddTokenEnterInfoFragment.getBundle(payload) - navController?.navigate(R.id.action_addTokenSelectChainFragment_to_addTokenEnterInfoFragment, args) + navigationBuilder().action(R.id.action_addTokenSelectChainFragment_to_addTokenEnterInfoFragment) + .setArgs(args) + .navigateInFirstAttachedContext() } override fun finishAddTokenFlow() { - navController?.navigate(R.id.finish_add_token_flow) + navigationBuilder().action(R.id.finish_add_token_flow) + .navigateInFirstAttachedContext() } override fun openWalletConnectSessions(metaId: Long) { @@ -378,55 +421,73 @@ class Navigator( } override fun openStaking() { - if (navController?.currentDestination?.id != R.id.mainFragment) navController?.navigate(R.id.action_open_main) + if (currentDestination?.id != R.id.mainFragment) { + navigationBuilder().action(R.id.action_open_split_screen) + .navigateInFirstAttachedContext() + } stakingDashboardDelegate.openStakingDashboard() } override fun closeSendFlow() { - navController?.navigate(R.id.action_close_send_flow) + navigationBuilder().action(R.id.action_close_send_flow) + .navigateInFirstAttachedContext() } override fun openSendNetworks(payload: NetworkFlowPayload) { - navController?.navigate(R.id.action_sendFlow_to_sendFlowNetwork, NetworkFlowFragment.createPayload(payload)) + navigationBuilder().action(R.id.action_sendFlow_to_sendFlowNetwork) + .setArgs(NetworkFlowFragment.createPayload(payload)) + .navigateInFirstAttachedContext() } override fun openReceiveNetworks(payload: NetworkFlowPayload) { - navController?.navigate(R.id.action_receiveFlow_to_receiveFlowNetwork, NetworkFlowFragment.createPayload(payload)) + navigationBuilder().action(R.id.action_receiveFlow_to_receiveFlowNetwork) + .setArgs(NetworkFlowFragment.createPayload(payload)) + .navigateInFirstAttachedContext() } override fun openSwapNetworks(payload: NetworkSwapFlowPayload) { - navController?.navigate(R.id.action_selectAssetSwapFlowFragment_to_swapFlowNetworkFragment, NetworkSwapFlowFragment.createPayload(payload)) + navigationBuilder().action(R.id.action_selectAssetSwapFlowFragment_to_swapFlowNetworkFragment) + .setArgs(NetworkSwapFlowFragment.createPayload(payload)) + .navigateInFirstAttachedContext() } - override fun returnToMainSwapScreen() { - navController?.navigate(R.id.action_return_to_swap_settings) + override fun openBuyNetworks(payload: NetworkFlowPayload) { + navigationBuilder().action(R.id.action_buyFlow_to_buyFlowNetwork) + .setArgs(NetworkFlowFragment.createPayload(payload)) + .navigateInFirstAttachedContext() } - override fun openBuyNetworks(payload: NetworkFlowPayload) { - navController?.navigate(R.id.action_buyFlow_to_buyFlowNetwork, NetworkFlowFragment.createPayload(payload)) + override fun returnToMainSwapScreen() { + navigationBuilder().action(R.id.action_return_to_swap_settings) + .navigateInFirstAttachedContext() } override fun openSwapFlow() { val payload = SwapFlowPayload.InitialSelecting - navController?.navigate(R.id.action_mainFragment_to_swapFlow, AssetSwapFlowFragment.getBundle(payload)) + navigationBuilder().action(R.id.action_mainFragment_to_swapFlow) + .setArgs(AssetSwapFlowFragment.getBundle(payload)) + .navigateInFirstAttachedContext() } override fun openSwapSetupAmount(swapSettingsPayload: SwapSettingsPayload) { - navController?.navigate(R.id.action_open_swapSetupAmount, SwapMainSettingsFragment.getBundle(swapSettingsPayload)) + navigationBuilder().action(R.id.action_open_swapSetupAmount) + .setArgs(SwapMainSettingsFragment.getBundle(swapSettingsPayload)) + .navigateInFirstAttachedContext() } override fun finishSelectAndOpenSwapSetupAmount(swapSettingsPayload: SwapSettingsPayload) { - navController?.navigate(R.id.action_finish_and_open_swap_settings, SwapMainSettingsFragment.getBundle(swapSettingsPayload)) + navigationBuilder().action(R.id.action_finish_and_open_swap_settings) + .setArgs(SwapMainSettingsFragment.getBundle(swapSettingsPayload)) + .navigateInFirstAttachedContext() } override fun openNfts() { - navController?.navigate(R.id.action_mainFragment_to_nfts_nav_graph) + navigationBuilder().action(R.id.action_mainFragment_to_nfts_nav_graph) + .navigateInFirstAttachedContext() } override fun nonCancellableVerify() { - val currentDestination = navController?.currentDestination - if (currentDestination?.id == R.id.splashFragment) { return } @@ -435,74 +496,89 @@ class Navigator( val bundle = PincodeFragment.getPinCodeBundle(action) if (currentDestination?.id == R.id.pincodeFragment) { - val currentBackStackEntry = navController!!.currentBackStackEntry val arguments = currentBackStackEntry!!.arguments!!.getParcelableCompat(PincodeFragment.KEY_PINCODE_ACTION) if (arguments is PinCodeAction.Change) { - navController?.navigate(R.id.action_pin_code_access_recovery, bundle) + navigationBuilder().action(R.id.action_pin_code_access_recovery) + .setArgs(bundle) + .navigateInRoot() } } else { - navController?.navigate(R.id.action_pin_code_access_recovery, bundle) + navigationBuilder().action(R.id.action_pin_code_access_recovery) + .setArgs(bundle) + .navigateInRoot() } } override fun openUpdateNotifications() { - navController?.navigate(R.id.action_open_update_notifications) + navigationBuilder().action(R.id.action_open_update_notifications) + .navigateInRoot() } override fun openPushWelcome() { - performNavigation(R.id.action_open_pushNotificationsWelcome) + navigationBuilder().action(R.id.action_open_pushNotificationsWelcome) + .navigateInFirstAttachedContext() } override fun openCloudBackupSettings() { - performNavigation(R.id.action_open_cloudBackupSettings) + navigationBuilder().action(R.id.action_open_cloudBackupSettings) + .navigateInFirstAttachedContext() } override fun returnToWallet() { // to achieve smooth animation postToUiThread { - navController?.navigate(R.id.action_return_to_wallet) + navigationBuilder().action(R.id.action_return_to_wallet) + .navigateInFirstAttachedContext() } } override fun openWalletDetails(metaId: Long) { val extras = WalletDetailsFragment.getBundle(metaId) - - navController?.navigate(R.id.action_open_account_details, extras) + navigationBuilder().action(R.id.action_open_account_details) + .setArgs(extras) + .navigateInFirstAttachedContext() } override fun openNodeDetails(nodeId: Int) { - navController?.navigate(R.id.action_nodesFragment_to_nodeDetailsFragment, NodeDetailsFragment.getBundle(nodeId)) + val extras = NodeDetailsFragment.getBundle(nodeId) + navigationBuilder().action(R.id.action_nodesFragment_to_nodeDetailsFragment) + .setArgs(extras) + .navigateInFirstAttachedContext() } override fun openAssetDetails(assetPayload: AssetPayload) { val bundle = BalanceDetailFragment.getBundle(assetPayload) - val action = when (navController?.currentDestination?.id) { - R.id.mainFragment -> R.id.action_mainFragment_to_balanceDetailFragment - R.id.assetSearchFragment -> R.id.action_assetSearchFragment_to_balanceDetailFragment - R.id.confirmTransferFragment -> R.id.action_confirmTransferFragment_to_balanceDetailFragment - else -> R.id.action_root_to_balanceDetailFragment - } - - navController?.navigate(action, bundle) + navigationBuilder().cases() + .addCase(R.id.mainFragment, R.id.action_mainFragment_to_balanceDetailFragment) + .addCase(R.id.assetSearchFragment, R.id.action_assetSearchFragment_to_balanceDetailFragment) + .addCase(R.id.confirmTransferFragment, R.id.action_confirmTransferFragment_to_balanceDetailFragment) + .setArgs(bundle) + .navigateInFirstAttachedContext() } override fun openAddNode() { - navController?.navigate(R.id.action_nodesFragment_to_addNodeFragment) + navigationBuilder().action(R.id.action_nodesFragment_to_addNodeFragment) + .navigateInFirstAttachedContext() } override fun openChangeWatchAccount(payload: AddAccountPayload.ChainAccount) { val bundle = ChangeWatchAccountFragment.getBundle(payload) - navController?.navigate(R.id.action_accountDetailsFragment_to_changeWatchAccountFragment, bundle) + navigationBuilder().action(R.id.action_accountDetailsFragment_to_changeWatchAccountFragment) + .setArgs(bundle) + .navigateInFirstAttachedContext() } override fun openCreateWallet(payload: StartCreateWalletPayload) { - navController?.navigate(R.id.action_open_create_new_wallet, StartCreateWalletFragment.bundle(payload)) + navigationBuilder().action(R.id.action_open_create_new_wallet) + .setArgs(StartCreateWalletFragment.bundle(payload)) + .navigateInFirstAttachedContext() } override fun openUserContributions() { - navController?.navigate(R.id.action_mainFragment_to_userContributionsFragment) + navigationBuilder().action(R.id.action_mainFragment_to_userContributionsFragment) + .navigateInFirstAttachedContext() } override fun getExportMnemonicDelayedNavigation(exportPayload: ExportPayload.ChainAccount): DelayedNavigation { @@ -527,82 +603,102 @@ class Navigator( override fun exportJsonAction(exportPayload: ExportPayload) { val extras = ExportJsonFragment.getBundle(exportPayload) - navController?.navigate(R.id.action_export_json, extras) + navigationBuilder().action(R.id.action_export_json) + .setArgs(extras) + .navigateInFirstAttachedContext() } override fun finishExportFlow() { - navController?.navigate(R.id.finish_export_flow) + navigationBuilder().action(R.id.finish_export_flow) + .navigateInFirstAttachedContext() } override fun openScanImportParitySigner(payload: ParitySignerStartPayload) { val args = ScanImportParitySignerFragment.getBundle(payload) - navController?.navigate(R.id.action_startImportParitySignerFragment_to_scanImportParitySignerFragment, args) + + navigationBuilder().action(R.id.action_startImportParitySignerFragment_to_scanImportParitySignerFragment) + .setArgs(args) + .navigateInFirstAttachedContext() } override fun openPreviewImportParitySigner(payload: ParitySignerAccountPayload) { val bundle = PreviewImportParitySignerFragment.getBundle(payload) - navController?.navigate(R.id.action_scanImportParitySignerFragment_to_previewImportParitySignerFragment, bundle) + navigationBuilder().action(R.id.action_scanImportParitySignerFragment_to_previewImportParitySignerFragment) + .setArgs(bundle) + .navigateInFirstAttachedContext() } override fun openFinishImportParitySigner(payload: ParitySignerAccountPayload) { val bundle = FinishImportParitySignerFragment.getBundle(payload) - navController?.navigate(R.id.action_previewImportParitySignerFragment_to_finishImportParitySignerFragment, bundle) + navigationBuilder().action(R.id.action_previewImportParitySignerFragment_to_finishImportParitySignerFragment) + .setArgs(bundle) + .navigateInFirstAttachedContext() } override fun openScanParitySignerSignature(payload: ScanSignParitySignerPayload) { val bundle = ScanSignParitySignerFragment.getBundle(payload) - navController?.navigate(R.id.action_showSignParitySignerFragment_to_scanSignParitySignerFragment, bundle) + navigationBuilder().action(R.id.action_showSignParitySignerFragment_to_scanSignParitySignerFragment) + .setArgs(bundle) + .navigateInFirstAttachedContext() } override fun finishParitySignerFlow() { - navController?.navigate(R.id.action_finish_parity_signer_flow) + navigationBuilder().action(R.id.action_finish_parity_signer_flow) + .navigateInFirstAttachedContext() } override fun openAddLedgerChainAccountFlow(payload: AddAccountPayload.ChainAccount) { val bundle = AddChainAccountSelectLedgerFragment.getBundle(payload) - navController?.navigate(R.id.action_accountDetailsFragment_to_addLedgerAccountGraph, bundle) - } - - override fun finishApp() { - navigationHolder.finishApp() + navigationBuilder().action(R.id.action_accountDetailsFragment_to_addLedgerAccountGraph) + .setArgs(bundle) + .navigateInFirstAttachedContext() } override fun openCreateCloudBackupPassword(walletName: String) { val bundle = CreateWalletBackupPasswordFragment.getBundle(CreateBackupPasswordPayload(walletName)) - navController?.navigate(R.id.action_startCreateWalletFragment_to_createCloudBackupPasswordFragment, bundle) + navigationBuilder().action(R.id.action_startCreateWalletFragment_to_createCloudBackupPasswordFragment) + .setArgs(bundle) + .navigateInFirstAttachedContext() } override fun restoreCloudBackup() { - when (navController?.currentDestination?.id) { - R.id.importWalletOptionsFragment -> navController?.navigate(R.id.action_importWalletOptionsFragment_to_restoreCloudBackup) - R.id.startCreateWalletFragment -> navController?.navigate(R.id.action_startCreateWalletFragment_to_resotreCloudBackupFragment) - } + navigationBuilder().cases() + .addCase(R.id.importWalletOptionsFragment, R.id.action_importWalletOptionsFragment_to_restoreCloudBackup) + .addCase(R.id.startCreateWalletFragment, R.id.action_startCreateWalletFragment_to_resotreCloudBackupFragment) + .navigateInFirstAttachedContext() } override fun openSyncWalletsBackupPassword() { - performNavigation(R.id.action_cloudBackupSettings_to_syncWalletsBackupPasswordFragment) + navigationBuilder().action(R.id.action_cloudBackupSettings_to_syncWalletsBackupPasswordFragment) + .navigateInFirstAttachedContext() } override fun openChangeBackupPasswordFlow() { - performNavigation(R.id.action_cloudBackupSettings_to_checkCloudBackupPasswordFragment) + navigationBuilder().action(R.id.action_cloudBackupSettings_to_checkCloudBackupPasswordFragment) + .navigateInFirstAttachedContext() } override fun openRestoreBackupPassword() { - performNavigation(R.id.action_cloudBackupSettings_to_restoreCloudBackupPasswordFragment) + navigationBuilder().action(R.id.action_cloudBackupSettings_to_restoreCloudBackupPasswordFragment) + .navigateInFirstAttachedContext() } override fun openChangeBackupPassword() { - performNavigation(R.id.action_checkCloudBackupPasswordFragment_to_changeBackupPasswordFragment) + navigationBuilder().action(R.id.action_checkCloudBackupPasswordFragment_to_changeBackupPasswordFragment) + .navigateInFirstAttachedContext() } override fun openManualBackupSelectAccount(metaId: Long) { val bundle = ManualBackupSelectAccountFragment.bundle(ManualBackupSelectAccountPayload(metaId)) - performNavigation(R.id.action_manualBackupSelectWalletFragment_to_manualBackupSelectAccountFragment, bundle) + + navigationBuilder().action(R.id.action_manualBackupSelectWalletFragment_to_manualBackupSelectAccountFragment) + .setArgs(bundle) + .navigateInFirstAttachedContext() } override fun openManualBackupConditions(payload: ManualBackupCommonPayload) { @@ -614,27 +710,30 @@ class Navigator( ) val pinCodeBundle = PincodeFragment.getPinCodeBundle(pinCodePayload) - performNavigation( - cases = arrayOf( - R.id.manualBackupSelectWallet to R.id.action_manualBackupSelectWallet_to_pincode_check, - R.id.manualBackupSelectAccount to R.id.action_manualBackupSelectAccount_to_pincode_check - ), - args = pinCodeBundle - ) + navigationBuilder().cases() + .addCase(R.id.manualBackupSelectWallet, R.id.action_manualBackupSelectWallet_to_pincode_check) + .addCase(R.id.manualBackupSelectAccount, R.id.action_manualBackupSelectAccount_to_pincode_check) + .setArgs(pinCodeBundle) + .navigateInFirstAttachedContext() } override fun openManualBackupSecrets(payload: ManualBackupCommonPayload) { val bundle = ManualBackupSecretsFragment.bundle(payload) - performNavigation(R.id.action_manualBackupWarning_to_manualBackupSecrets, bundle) + navigationBuilder().action(R.id.action_manualBackupWarning_to_manualBackupSecrets) + .setArgs(bundle) + .navigateInFirstAttachedContext() } override fun openManualBackupAdvancedSecrets(payload: ManualBackupCommonPayload) { val bundle = ManualBackupAdvancedSecretsFragment.bundle(payload) - performNavigation(R.id.action_manualBackupSecrets_to_manualBackupAdvancedSecrets, bundle) + navigationBuilder().action(R.id.action_manualBackupSecrets_to_manualBackupAdvancedSecrets) + .setArgs(bundle) + .navigateInFirstAttachedContext() } override fun openCreateWatchWallet() { - navController?.navigate(R.id.action_importWalletOptionsFragment_to_createWatchWalletFragment) + navigationBuilder().action(R.id.action_importWalletOptionsFragment_to_createWatchWalletFragment) + .navigateInFirstAttachedContext() } override fun openStartImportParitySigner() { @@ -646,18 +745,20 @@ class Navigator( } override fun openImportOptionsScreen() { - when (navController?.currentDestination?.id) { - R.id.welcomeFragment -> navController?.navigate(R.id.action_welcomeFragment_to_importWalletOptionsFragment) - else -> navController?.navigate(R.id.action_importWalletOptionsFragment) - } + navigationBuilder().cases() + .addCase(R.id.welcomeFragment, R.id.action_welcomeFragment_to_importWalletOptionsFragment) + .setFallbackCase(R.id.action_importWalletOptionsFragment) + .navigateInFirstAttachedContext() } override fun openStartImportLegacyLedger() { - navController?.navigate(R.id.action_importWalletOptionsFragment_to_import_legacy_ledger_graph) + navigationBuilder().action(R.id.action_importWalletOptionsFragment_to_import_legacy_ledger_graph) + .navigateInFirstAttachedContext() } override fun openStartImportGenericLedger() { - navController?.navigate(R.id.action_importWalletOptionsFragment_to_import_generic_ledger_graph) + navigationBuilder().action(R.id.action_importWalletOptionsFragment_to_import_generic_ledger_graph) + .navigateInFirstAttachedContext() } override fun withPinCodeCheckRequired( @@ -671,19 +772,33 @@ class Navigator( PinCodeAction.Check(delayedNavigation, ToolbarConfiguration(pinCodeTitleRes, true)) } - val extras = PincodeFragment.getPinCodeBundle(action) - - navController?.navigate(R.id.open_pincode_check, extras) + navigationBuilder().action(R.id.open_pincode_check) + .setArgs(PincodeFragment.getPinCodeBundle(action)) + .navigateInFirstAttachedContext() } private fun openStartImportPolkadotVault(variant: PolkadotVaultVariant) { val args = StartImportParitySignerFragment.getBundle(ParitySignerStartPayload(variant)) - navController?.navigate(R.id.action_importWalletOptionsFragment_to_import_parity_signer_graph, args) + + navigationBuilder().action(R.id.action_importWalletOptionsFragment_to_import_parity_signer_graph) + .setArgs(args) + .navigateInFirstAttachedContext() } private fun buildCreatePinBundle(): Bundle { - val delayedNavigation = NavComponentDelayedNavigation(R.id.action_open_main) + val delayedNavigation = NavComponentDelayedNavigation(R.id.action_open_split_screen) val action = PinCodeAction.Create(delayedNavigation) return PincodeFragment.getPinCodeBundle(action) } + + override fun runDelayedNavigation(delayedNavigation: DelayedNavigation) { + when (delayedNavigation) { + BackDelayedNavigation -> back() + is NavComponentDelayedNavigation -> { + navigationBuilder().action(delayedNavigation.globalActionId) + .setArgs(delayedNavigation.extras) + .navigateInFirstAttachedContext() + } + } + } } diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/account/PolkadotVaultVariantSignCommunicatorImpl.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/account/PolkadotVaultVariantSignCommunicatorImpl.kt similarity index 85% rename from app/src/main/java/io/novafoundation/nova/app/root/navigation/account/PolkadotVaultVariantSignCommunicatorImpl.kt rename to app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/account/PolkadotVaultVariantSignCommunicatorImpl.kt index f51791b16e..aade76b38b 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/account/PolkadotVaultVariantSignCommunicatorImpl.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/account/PolkadotVaultVariantSignCommunicatorImpl.kt @@ -1,9 +1,9 @@ -package io.novafoundation.nova.app.root.navigation.account +package io.novafoundation.nova.app.root.navigation.navigators.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.app.root.navigation.getBackStackEntryBefore +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry import io.novafoundation.nova.feature_account_api.domain.model.PolkadotVaultVariant import io.novafoundation.nova.feature_account_api.presenatation.sign.SignInterScreenCommunicator.Request import io.novafoundation.nova.feature_account_api.presenatation.sign.SignInterScreenCommunicator.Response @@ -12,8 +12,8 @@ import io.novafoundation.nova.feature_account_impl.presentation.paritySigner.sig import io.novafoundation.nova.feature_account_impl.presentation.paritySigner.sign.show.ShowSignParitySignerPayload class PolkadotVaultVariantSignCommunicatorImpl( - navigationHolder: NavigationHolder, -) : NavStackInterScreenCommunicator(navigationHolder), PolkadotVaultVariantSignCommunicator { + navigationHoldersRegistry: NavigationHoldersRegistry +) : NavStackInterScreenCommunicator(navigationHoldersRegistry), PolkadotVaultVariantSignCommunicator { private var usedPolkadotVaultVariant: PolkadotVaultVariant? = null diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/account/SelectAddressCommunicatorImpl.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/account/SelectAddressCommunicatorImpl.kt similarity index 76% rename from app/src/main/java/io/novafoundation/nova/app/root/navigation/account/SelectAddressCommunicatorImpl.kt rename to app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/account/SelectAddressCommunicatorImpl.kt index c74f095764..e8d259bcd2 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/account/SelectAddressCommunicatorImpl.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/account/SelectAddressCommunicatorImpl.kt @@ -1,15 +1,15 @@ -package io.novafoundation.nova.app.root.navigation.account +package io.novafoundation.nova.app.root.navigation.navigators.account import io.novafoundation.nova.app.root.navigation.NavStackInterScreenCommunicator -import io.novafoundation.nova.app.root.navigation.NavigationHolder +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectAddress.SelectAddressCommunicator import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectAddress.SelectAddressRequester import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectAddress.SelectAddressResponder import io.novafoundation.nova.feature_account_impl.presentation.account.list.selectAddress.SelectAddressFragment import io.novafoundation.nova.feature_assets.presentation.AssetsRouter -class SelectAddressCommunicatorImpl(private val router: AssetsRouter, navigationHolder: NavigationHolder) : - NavStackInterScreenCommunicator(navigationHolder), +class SelectAddressCommunicatorImpl(private val router: AssetsRouter, navigationHoldersRegistry: NavigationHoldersRegistry) : + NavStackInterScreenCommunicator(navigationHoldersRegistry), SelectAddressCommunicator { override fun openRequest(request: SelectAddressRequester.Request) { diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/account/SelectMultipleWalletsCommunicatorImpl.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/account/SelectMultipleWalletsCommunicatorImpl.kt similarity index 81% rename from app/src/main/java/io/novafoundation/nova/app/root/navigation/account/SelectMultipleWalletsCommunicatorImpl.kt rename to app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/account/SelectMultipleWalletsCommunicatorImpl.kt index 93405b4bd2..3a2bb73c15 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/account/SelectMultipleWalletsCommunicatorImpl.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/account/SelectMultipleWalletsCommunicatorImpl.kt @@ -1,15 +1,15 @@ -package io.novafoundation.nova.app.root.navigation.account +package io.novafoundation.nova.app.root.navigation.navigators.account import io.novafoundation.nova.app.root.navigation.NavStackInterScreenCommunicator -import io.novafoundation.nova.app.root.navigation.NavigationHolder +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.list.SelectMultipleWalletsCommunicator import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.list.SelectMultipleWalletsRequester import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.list.SelectMultipleWalletsResponder import io.novafoundation.nova.feature_account_impl.presentation.account.list.multipleSelecting.SelectMultipleWalletsFragment import io.novafoundation.nova.feature_assets.presentation.AssetsRouter -class SelectMultipleWalletsCommunicatorImpl(private val router: AssetsRouter, navigationHolder: NavigationHolder) : - NavStackInterScreenCommunicator(navigationHolder), +class SelectMultipleWalletsCommunicatorImpl(private val router: AssetsRouter, navigationHoldersRegistry: NavigationHoldersRegistry) : + NavStackInterScreenCommunicator(navigationHoldersRegistry), SelectMultipleWalletsCommunicator { override fun openRequest(request: SelectMultipleWalletsRequester.Request) { diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/account/SelectWalletCommunicatorImpl.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/account/SelectWalletCommunicatorImpl.kt similarity index 66% rename from app/src/main/java/io/novafoundation/nova/app/root/navigation/account/SelectWalletCommunicatorImpl.kt rename to app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/account/SelectWalletCommunicatorImpl.kt index a1fe779f27..e53f25bb50 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/account/SelectWalletCommunicatorImpl.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/account/SelectWalletCommunicatorImpl.kt @@ -1,19 +1,19 @@ -package io.novafoundation.nova.app.root.navigation.account +package io.novafoundation.nova.app.root.navigation.navigators.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.app.root.navigation.navigators.NavigationHoldersRegistry import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectWallet.SelectWalletCommunicator import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectWallet.SelectWalletCommunicator.Payload import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectWallet.SelectWalletCommunicator.Response class SelectWalletCommunicatorImpl( - private val navigationHolder: NavigationHolder, -) : NavStackInterScreenCommunicator(navigationHolder), SelectWalletCommunicator { + private val navigationHoldersRegistry: NavigationHoldersRegistry +) : NavStackInterScreenCommunicator(navigationHoldersRegistry), SelectWalletCommunicator { override fun openRequest(request: Payload) { super.openRequest(request) - navigationHolder.navController!!.navigate(R.id.action_open_select_wallet) + navController.navigate(R.id.action_open_select_wallet) } } diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/builder/ActionNavigationBuilder.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/builder/ActionNavigationBuilder.kt new file mode 100644 index 0000000000..59de6ae253 --- /dev/null +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/builder/ActionNavigationBuilder.kt @@ -0,0 +1,14 @@ +package io.novafoundation.nova.app.root.navigation.navigators.builder + +import io.novafoundation.nova.app.root.navigation.holders.NavigationHolder +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry + +class ActionNavigationBuilder( + navigationHoldersRegistry: NavigationHoldersRegistry, + private val actionId: Int +) : NavigationBuilder(navigationHoldersRegistry) { + + override fun performInternal(navigationHolder: NavigationHolder) { + performAction(navigationHolder, actionId) + } +} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/builder/CasesNavigationBuilder.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/builder/CasesNavigationBuilder.kt new file mode 100644 index 0000000000..aa936d5b72 --- /dev/null +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/builder/CasesNavigationBuilder.kt @@ -0,0 +1,42 @@ +package io.novafoundation.nova.app.root.navigation.navigators.builder + +import androidx.navigation.NavDestination +import io.novafoundation.nova.app.root.navigation.holders.NavigationHolder +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry + +class CasesNavigationBuilder( + navigationHoldersRegistry: NavigationHoldersRegistry +) : NavigationBuilder(navigationHoldersRegistry) { + + private class Case(val destination: Int, val actionId: Int) + + private var cases = mutableListOf() + + private var fallbackCaseActionId: Int? = null + + fun addCase(currentDestination: Int, actionId: Int): CasesNavigationBuilder { + cases.add(Case(currentDestination, actionId)) + return this + } + + fun setFallbackCase(actionId: Int): CasesNavigationBuilder { + fallbackCaseActionId = actionId + return this + } + + override fun performInternal(navigationHolder: NavigationHolder) { + val navController = navigationHolder.navController ?: return + val currentDestination = navController.currentDestination ?: return + + val caseActionId = cases.find { case -> case.destination == currentDestination.id } + ?.actionId + ?: fallbackCaseActionId + ?: throw IllegalArgumentException("Unknown case for ${currentDestination.label}") + + performAction(navigationHolder, caseActionId) + } + + private fun NavDestination.hasAction(actionId: Int): Boolean { + return this.getAction(actionId) != null + } +} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/builder/NavigationBuilder.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/builder/NavigationBuilder.kt new file mode 100644 index 0000000000..8b118f36aa --- /dev/null +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/builder/NavigationBuilder.kt @@ -0,0 +1,55 @@ +package io.novafoundation.nova.app.root.navigation.navigators.builder + +import android.os.Bundle +import androidx.navigation.NavDestination +import androidx.navigation.NavOptions +import androidx.navigation.fragment.FragmentNavigator +import io.novafoundation.nova.app.root.navigation.holders.NavigationHolder +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry + +abstract class NavigationBuilder( + private val navigationHoldersRegistry: NavigationHoldersRegistry +) { + + protected var navOptions: NavOptions? = null + protected var args: Bundle? = null + protected var extras: FragmentNavigator.Extras? = null + + fun setArgs(args: Bundle?): NavigationBuilder { + this.args = args + return this + } + + fun setNavOptions(navOptions: NavOptions): NavigationBuilder { + this.navOptions = navOptions + return this + } + + fun setExtras(extras: FragmentNavigator.Extras?): NavigationBuilder { + this.extras = extras + return this + } + + fun navigateInFirstAttachedContext() { + performInternal(navigationHoldersRegistry.firstAttachedHolder) + } + + fun navigateInRoot() { + performInternal(navigationHoldersRegistry.rootNavigationHolder) + } + + protected fun NavigationBuilder.performAction(navigationHolder: NavigationHolder, actionId: Int) { + val navController = navigationHolder.navController ?: return + val currentDestination = navController.currentDestination ?: return + + if (currentDestination.hasAction(actionId)) { + navigationHolder.navController?.navigate(actionId, args, navOptions, extras) + } + } + + protected abstract fun performInternal(navigationHolder: NavigationHolder) +} + +private fun NavDestination.hasAction(actionId: Int): Boolean { + return this.getAction(actionId) != null +} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/builder/NavigationBuilderRegistry.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/builder/NavigationBuilderRegistry.kt new file mode 100644 index 0000000000..0a8e3ecb3d --- /dev/null +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/builder/NavigationBuilderRegistry.kt @@ -0,0 +1,12 @@ +package io.novafoundation.nova.app.root.navigation.navigators.builder + +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry + +class NavigationBuilderRegistry(private val registry: NavigationHoldersRegistry) { + + fun action(actionId: Int) = ActionNavigationBuilder(registry, actionId) + + fun cases() = CasesNavigationBuilder(registry) + + fun graph(graphId: Int) = OpenGraphNavigationBuilder(registry, graphId) +} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/builder/OpenGraphNavigationBuilder.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/builder/OpenGraphNavigationBuilder.kt new file mode 100644 index 0000000000..1f8534f3e0 --- /dev/null +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/builder/OpenGraphNavigationBuilder.kt @@ -0,0 +1,14 @@ +package io.novafoundation.nova.app.root.navigation.navigators.builder + +import io.novafoundation.nova.app.root.navigation.holders.NavigationHolder +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry + +class OpenGraphNavigationBuilder( + navigationHoldersRegistry: NavigationHoldersRegistry, + private val graphId: Int +) : NavigationBuilder(navigationHoldersRegistry) { + + override fun performInternal(navigationHolder: NavigationHolder) { + navigationHolder.navController?.navigate(graphId, args, navOptions, extras) + } +} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/buy/BuyNavigator.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/buy/BuyNavigator.kt new file mode 100644 index 0000000000..9e2a6c9423 --- /dev/null +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/buy/BuyNavigator.kt @@ -0,0 +1,8 @@ +package io.novafoundation.nova.app.root.navigation.navigators.buy + +import io.novafoundation.nova.app.root.navigation.navigators.BaseNavigator +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry +import io.novafoundation.nova.feature_buy_impl.presentation.BuyRouter + +class BuyNavigator(navigationHoldersRegistry: NavigationHoldersRegistry) : BuyRouter, + BaseNavigator(navigationHoldersRegistry) diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/cloudBackup/ChangeBackupPasswordCommunicatorImpl.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/cloudBackup/ChangeBackupPasswordCommunicatorImpl.kt similarity index 78% rename from app/src/main/java/io/novafoundation/nova/app/root/navigation/cloudBackup/ChangeBackupPasswordCommunicatorImpl.kt rename to app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/cloudBackup/ChangeBackupPasswordCommunicatorImpl.kt index 00ec7775ef..0aed81dc89 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/cloudBackup/ChangeBackupPasswordCommunicatorImpl.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/cloudBackup/ChangeBackupPasswordCommunicatorImpl.kt @@ -1,14 +1,14 @@ -package io.novafoundation.nova.app.root.navigation.cloudBackup +package io.novafoundation.nova.app.root.navigation.navigators.cloudBackup import io.novafoundation.nova.app.root.navigation.NavStackInterScreenCommunicator -import io.novafoundation.nova.app.root.navigation.NavigationHolder +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry import io.novafoundation.nova.feature_account_api.presenatation.cloudBackup.changePassword.ChangeBackupPasswordCommunicator import io.novafoundation.nova.feature_account_api.presenatation.cloudBackup.changePassword.ChangeBackupPasswordRequester import io.novafoundation.nova.feature_account_api.presenatation.cloudBackup.changePassword.ChangeBackupPasswordResponder import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter -class ChangeBackupPasswordCommunicatorImpl(private val router: AccountRouter, navigationHolder: NavigationHolder) : - NavStackInterScreenCommunicator(navigationHolder), +class ChangeBackupPasswordCommunicatorImpl(private val router: AccountRouter, navigationHoldersRegistry: NavigationHoldersRegistry) : + NavStackInterScreenCommunicator(navigationHoldersRegistry), ChangeBackupPasswordCommunicator { override fun openRequest(request: ChangeBackupPasswordRequester.EmptyRequest) { diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/cloudBackup/CloudBackupNavigator.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/cloudBackup/CloudBackupNavigator.kt new file mode 100644 index 0000000000..bcf183362b --- /dev/null +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/cloudBackup/CloudBackupNavigator.kt @@ -0,0 +1,8 @@ +package io.novafoundation.nova.app.root.navigation.navigators.cloudBackup + +import io.novafoundation.nova.app.root.navigation.navigators.BaseNavigator +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry +import io.novafoundation.nova.feature_cloud_backup_impl.presentation.CloudBackupRouter + +class CloudBackupNavigator(navigationHoldersRegistry: NavigationHoldersRegistry) : CloudBackupRouter, + BaseNavigator(navigationHoldersRegistry) diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/cloudBackup/RestoreBackupPasswordCommunicatorImpl.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/cloudBackup/RestoreBackupPasswordCommunicatorImpl.kt similarity index 78% rename from app/src/main/java/io/novafoundation/nova/app/root/navigation/cloudBackup/RestoreBackupPasswordCommunicatorImpl.kt rename to app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/cloudBackup/RestoreBackupPasswordCommunicatorImpl.kt index 11f79a2570..3c75b03597 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/cloudBackup/RestoreBackupPasswordCommunicatorImpl.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/cloudBackup/RestoreBackupPasswordCommunicatorImpl.kt @@ -1,14 +1,14 @@ -package io.novafoundation.nova.app.root.navigation.cloudBackup +package io.novafoundation.nova.app.root.navigation.navigators.cloudBackup import io.novafoundation.nova.app.root.navigation.NavStackInterScreenCommunicator -import io.novafoundation.nova.app.root.navigation.NavigationHolder +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry import io.novafoundation.nova.feature_account_api.presenatation.cloudBackup.changePassword.RestoreBackupPasswordCommunicator import io.novafoundation.nova.feature_account_api.presenatation.cloudBackup.changePassword.RestoreBackupPasswordRequester import io.novafoundation.nova.feature_account_api.presenatation.cloudBackup.changePassword.RestoreBackupPasswordResponder import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter -class RestoreBackupPasswordCommunicatorImpl(private val router: AccountRouter, navigationHolder: NavigationHolder) : - NavStackInterScreenCommunicator(navigationHolder), +class RestoreBackupPasswordCommunicatorImpl(private val router: AccountRouter, navigationHoldersRegistry: NavigationHoldersRegistry) : + NavStackInterScreenCommunicator(navigationHoldersRegistry), RestoreBackupPasswordCommunicator { override fun openRequest(request: RestoreBackupPasswordRequester.EmptyRequest) { diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/cloudBackup/SyncWalletsBackupPasswordCommunicatorImpl.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/cloudBackup/SyncWalletsBackupPasswordCommunicatorImpl.kt similarity index 78% rename from app/src/main/java/io/novafoundation/nova/app/root/navigation/cloudBackup/SyncWalletsBackupPasswordCommunicatorImpl.kt rename to app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/cloudBackup/SyncWalletsBackupPasswordCommunicatorImpl.kt index 8f838b4ba8..081fc37c0d 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/cloudBackup/SyncWalletsBackupPasswordCommunicatorImpl.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/cloudBackup/SyncWalletsBackupPasswordCommunicatorImpl.kt @@ -1,14 +1,14 @@ -package io.novafoundation.nova.app.root.navigation.cloudBackup +package io.novafoundation.nova.app.root.navigation.navigators.cloudBackup import io.novafoundation.nova.app.root.navigation.NavStackInterScreenCommunicator -import io.novafoundation.nova.app.root.navigation.NavigationHolder +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter import io.novafoundation.nova.feature_account_api.presenatation.cloudBackup.createPassword.SyncWalletsBackupPasswordCommunicator import io.novafoundation.nova.feature_account_api.presenatation.cloudBackup.createPassword.SyncWalletsBackupPasswordRequester import io.novafoundation.nova.feature_account_api.presenatation.cloudBackup.createPassword.SyncWalletsBackupPasswordResponder -class SyncWalletsBackupPasswordCommunicatorImpl(private val router: AccountRouter, navigationHolder: NavigationHolder) : - NavStackInterScreenCommunicator(navigationHolder), +class SyncWalletsBackupPasswordCommunicatorImpl(private val router: AccountRouter, navigationHoldersRegistry: NavigationHoldersRegistry) : + NavStackInterScreenCommunicator(navigationHoldersRegistry), SyncWalletsBackupPasswordCommunicator { override fun openRequest(request: SyncWalletsBackupPasswordRequester.EmptyRequest) { diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/dApp/DAppNavigator.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/dApp/DAppNavigator.kt new file mode 100644 index 0000000000..5f1b5c9d85 --- /dev/null +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/dApp/DAppNavigator.kt @@ -0,0 +1,109 @@ +package io.novafoundation.nova.app.root.navigation.navigators.dApp + +import androidx.navigation.NavOptions +import androidx.navigation.fragment.FragmentNavigator +import io.novafoundation.nova.app.R +import io.novafoundation.nova.app.root.navigation.navigators.BaseNavigator +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry +import io.novafoundation.nova.app.root.navigation.navigators.builder.NavigationBuilder +import io.novafoundation.nova.feature_dapp_impl.presentation.DAppRouter +import io.novafoundation.nova.feature_dapp_impl.presentation.addToFavourites.AddToFavouritesFragment +import io.novafoundation.nova.feature_dapp_api.presentation.addToFavorites.AddToFavouritesPayload +import io.novafoundation.nova.feature_dapp_api.presentation.browser.main.DAppBrowserPayload +import io.novafoundation.nova.feature_dapp_impl.presentation.browser.main.DAppBrowserFragment +import io.novafoundation.nova.feature_dapp_impl.presentation.search.DappSearchFragment +import io.novafoundation.nova.feature_dapp_impl.presentation.search.SearchPayload + +class DAppNavigator( + navigationHoldersRegistry: NavigationHoldersRegistry, +) : BaseNavigator(navigationHoldersRegistry), DAppRouter { + + override fun openChangeAccount() { + navigationBuilder().action(R.id.action_open_switch_wallet) + .navigateInFirstAttachedContext() + } + + override fun openDAppBrowser(payload: DAppBrowserPayload, extras: FragmentNavigator.Extras?) { + // Close dapp browser if it is already opened + // TODO it's better to provide new url to existing browser + navigationBuilder().graph(R.id.dapp_browser_graph) + .setDappAnimations() + .setExtras(extras) + .setArgs(DAppBrowserFragment.getBundle(payload)) + .navigateInRoot() + } + + override fun openDappSearch() { + openDappSearchWithCategory(categoryId = null) + } + + override fun openDappSearchWithCategory(categoryId: String?) { + navigationBuilder().graph(R.id.dapp_search_graph) + .setDappAnimations() + .setArgs(DappSearchFragment.getBundle(SearchPayload(initialUrl = null, SearchPayload.Request.OPEN_NEW_URL, preselectedCategoryId = categoryId))) + .navigateInRoot() + } + + override fun finishDappSearch() { + navigationBuilder().action(R.id.action_finish_dapp_search) + .navigateInRoot() + } + + override fun openAddToFavourites(payload: AddToFavouritesPayload) { + navigationBuilder().action(R.id.action_DAppBrowserFragment_to_addToFavouritesFragment) + .setArgs(AddToFavouritesFragment.getBundle(payload)) + .navigateInFirstAttachedContext() + } + + override fun openAuthorizedDApps() { + navigationBuilder().action(R.id.action_mainFragment_to_authorizedDAppsFragment) + .navigateInFirstAttachedContext() + } + + override fun openTabs() { + navigationBuilder().graph(R.id.dapp_tabs_graph) + .setDappAnimations() + .navigateInRoot() + } + + override fun closeTabsScreen() { + navigationBuilder().action(R.id.action_finish_tabs_fragment) + .navigateInRoot() + } + + override fun openDAppFavorites() { + navigationBuilder().action(R.id.action_open_dapp_favorites) + .navigateInFirstAttachedContext() + } + + private fun NavigationBuilder.setDappAnimations(): NavigationBuilder { + val currentDestinationId = currentDestination?.id + + // For this currentDestinations we will use default animation. And for other - slide_in, slide_out + val dappDestinations = listOf( + R.id.dappSearchFragment, + R.id.dappBrowserFragment, + R.id.dappTabsFragment + ) + + val navOptionsBuilder = if (currentDestinationId in dappDestinations) { + // Only slide out animation + NavOptions.Builder() + .setEnterAnim(R.anim.fragment_open_enter) + .setExitAnim(R.anim.fragment_open_exit) + .setPopEnterAnim(R.anim.fragment_close_enter) + .setPopExitAnim(R.anim.fragment_slide_out) + .setPopUpTo(R.id.splitScreenFragment, false) + } else { + // Slide in/out animations + NavOptions.Builder() + .setEnterAnim(R.anim.fragment_slide_in) + .setExitAnim(R.anim.fragment_open_exit) + .setPopEnterAnim(R.anim.fragment_close_enter) + .setPopExitAnim(R.anim.fragment_slide_out) + .setPopUpTo(R.id.splitScreenFragment, false) + } + + return setNavOptions(navOptionsBuilder.build()) + } +} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/dApp/DAppSearchCommunicatorImpl.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/dApp/DAppSearchCommunicatorImpl.kt similarity index 60% rename from app/src/main/java/io/novafoundation/nova/app/root/navigation/dApp/DAppSearchCommunicatorImpl.kt rename to app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/dApp/DAppSearchCommunicatorImpl.kt index c07a21f66f..5834e891d1 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/dApp/DAppSearchCommunicatorImpl.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/dApp/DAppSearchCommunicatorImpl.kt @@ -1,18 +1,22 @@ -package io.novafoundation.nova.app.root.navigation.dApp +package io.novafoundation.nova.app.root.navigation.navigators.dApp 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.app.root.navigation.navigators.NavigationHoldersRegistry import io.novafoundation.nova.feature_dapp_impl.presentation.search.DAppSearchCommunicator import io.novafoundation.nova.feature_dapp_impl.presentation.search.DAppSearchCommunicator.Response import io.novafoundation.nova.feature_dapp_impl.presentation.search.DappSearchFragment import io.novafoundation.nova.feature_dapp_impl.presentation.search.SearchPayload -class DAppSearchCommunicatorImpl(navigationHolder: NavigationHolder) : - NavStackInterScreenCommunicator(navigationHolder), +class DAppSearchCommunicatorImpl(navigationHoldersRegistry: NavigationHoldersRegistry) : + NavStackInterScreenCommunicator(navigationHoldersRegistry), DAppSearchCommunicator { + override fun openRequest(request: SearchPayload) { super.openRequest(request) - navController.navigate(R.id.action_DAppBrowserFragment_to_dappSearchFragment, DappSearchFragment.getBundle(request)) + + navigationBuilder().action(R.id.action_open_dappSearch_from_browser) + .setArgs(DappSearchFragment.getBundle(request)) + .navigateInRoot() } } diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/deepLinking/DeepLinkingNavigator.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/deepLinking/DeepLinkingNavigator.kt new file mode 100644 index 0000000000..7ceee63813 --- /dev/null +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/deepLinking/DeepLinkingNavigator.kt @@ -0,0 +1,44 @@ +package io.novafoundation.nova.app.root.navigation.navigators.deepLinking + +import io.novafoundation.nova.app.R +import io.novafoundation.nova.app.root.navigation.navigators.BaseNavigator +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry +import io.novafoundation.nova.app.root.navigation.openSplitScreenWithInstantAction +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_assets.presentation.AssetsRouter +import io.novafoundation.nova.feature_assets.presentation.balance.detail.BalanceDetailFragment +import io.novafoundation.nova.feature_dapp_api.presentation.browser.main.DAppBrowserPayload +import io.novafoundation.nova.feature_dapp_impl.presentation.DAppRouter +import io.novafoundation.nova.feature_deep_linking.presentation.handling.DeepLinkingRouter +import io.novafoundation.nova.feature_governance_api.presentation.referenda.details.ReferendumDetailsPayload +import io.novafoundation.nova.feature_governance_impl.presentation.referenda.details.ReferendumDetailsFragment +import io.novafoundation.nova.feature_wallet_api.presentation.model.AssetPayload + +class DeepLinkingNavigator( + navigationHoldersRegistry: NavigationHoldersRegistry, + private val dAppRouter: DAppRouter, + private val accountRouter: AccountRouter, + private val assetsRouter: AssetsRouter +) : BaseNavigator(navigationHoldersRegistry), DeepLinkingRouter { + + override fun openAssetDetails(payload: AssetPayload) { + openSplitScreenWithInstantAction(R.id.action_mainFragment_to_balanceDetailFragment, BalanceDetailFragment.getBundle(payload)) + } + + override fun openDAppBrowser(url: String) { + dAppRouter.openDAppBrowser(DAppBrowserPayload.Address(url)) + } + + override fun openImportAccountScreen(payload: ImportAccountPayload) { + accountRouter.openImportAccountScreen(payload) + } + + override fun openReferendum(payload: ReferendumDetailsPayload) { + openSplitScreenWithInstantAction(R.id.action_open_referendum_details, ReferendumDetailsFragment.getBundle(payload)) + } + + override fun openStakingDashboard() { + assetsRouter.openStaking() + } +} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/externalSign/ExternalSignCommunicatorImpl.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/externalSign/ExternalSignCommunicatorImpl.kt similarity index 68% rename from app/src/main/java/io/novafoundation/nova/app/root/navigation/externalSign/ExternalSignCommunicatorImpl.kt rename to app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/externalSign/ExternalSignCommunicatorImpl.kt index f4037399c1..c8d97b09e6 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/externalSign/ExternalSignCommunicatorImpl.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/externalSign/ExternalSignCommunicatorImpl.kt @@ -1,8 +1,9 @@ -package io.novafoundation.nova.app.root.navigation.externalSign +package io.novafoundation.nova.app.root.navigation.navigators.externalSign import io.novafoundation.nova.app.R import io.novafoundation.nova.app.root.navigation.FlowInterScreenCommunicator -import io.novafoundation.nova.app.root.navigation.NavigationHolder +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry +import io.novafoundation.nova.app.root.navigation.navigators.navigationBuilder import io.novafoundation.nova.common.utils.sequrity.AutomaticInteractionGate import io.novafoundation.nova.common.utils.sequrity.awaitInteractionAllowed import io.novafoundation.nova.feature_external_sign_api.model.ExternalSignCommunicator @@ -13,7 +14,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch class ExternalSignCommunicatorImpl( - private val navigationHolder: NavigationHolder, + private val navigationHoldersRegistry: NavigationHoldersRegistry, private val automaticInteractionGate: AutomaticInteractionGate, ) : CoroutineScope by CoroutineScope(Dispatchers.Main), FlowInterScreenCommunicator(), @@ -23,7 +24,9 @@ class ExternalSignCommunicatorImpl( launch { automaticInteractionGate.awaitInteractionAllowed() - navigationHolder.navController!!.navigate(R.id.action_open_externalSignGraph, ExternalSignFragment.getBundle(request)) + navigationHoldersRegistry.navigationBuilder().action(R.id.action_open_externalSignGraph) + .setArgs(ExternalSignFragment.getBundle(request)) + .navigateInRoot() } } } diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/externalSign/ExternalSignNavigator.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/externalSign/ExternalSignNavigator.kt new file mode 100644 index 0000000000..8115db59b4 --- /dev/null +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/externalSign/ExternalSignNavigator.kt @@ -0,0 +1,18 @@ +package io.novafoundation.nova.app.root.navigation.navigators.externalSign + +import io.novafoundation.nova.app.R +import io.novafoundation.nova.app.root.navigation.navigators.BaseNavigator +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry +import io.novafoundation.nova.feature_external_sign_impl.ExternalSignRouter +import io.novafoundation.nova.feature_external_sign_impl.presentation.extrinsicDetails.ExternalExtrinsicDetailsFragment + +class ExternalSignNavigator( + navigationHoldersRegistry: NavigationHoldersRegistry +) : BaseNavigator(navigationHoldersRegistry), ExternalSignRouter { + + override fun openExtrinsicDetails(extrinsicContent: String) { + navigationBuilder().action(R.id.action_ConfirmSignExtrinsicFragment_to_extrinsicDetailsFragment) + .setArgs(ExternalExtrinsicDetailsFragment.getBundle(extrinsicContent)) + .navigateInFirstAttachedContext() + } +} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/governance/GovernanceNavigator.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/governance/GovernanceNavigator.kt new file mode 100644 index 0000000000..8147529bf8 --- /dev/null +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/governance/GovernanceNavigator.kt @@ -0,0 +1,264 @@ +package io.novafoundation.nova.app.root.navigation.navigators.governance + +import android.os.Bundle +import io.novafoundation.nova.app.R +import io.novafoundation.nova.app.root.navigation.navigators.BaseNavigator +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry +import io.novafoundation.nova.app.root.navigation.navigators.Navigator +import io.novafoundation.nova.common.resources.ContextManager +import io.novafoundation.nova.common.utils.showBrowser +import io.novafoundation.nova.feature_dapp_api.presentation.browser.main.DAppBrowserPayload +import io.novafoundation.nova.feature_dapp_impl.presentation.browser.main.DAppBrowserFragment +import io.novafoundation.nova.feature_governance_impl.BuildConfig +import io.novafoundation.nova.feature_governance_impl.presentation.GovernanceRouter +import io.novafoundation.nova.feature_governance_impl.presentation.common.description.DescriptionFragment +import io.novafoundation.nova.feature_governance_impl.presentation.common.description.DescriptionPayload +import io.novafoundation.nova.feature_governance_impl.presentation.delegation.delegate.delegators.DelegateDelegatorsFragment +import io.novafoundation.nova.feature_governance_impl.presentation.delegation.delegate.delegators.DelegateDelegatorsPayload +import io.novafoundation.nova.feature_governance_impl.presentation.delegation.delegate.detail.main.DelegateDetailsFragment +import io.novafoundation.nova.feature_governance_impl.presentation.delegation.delegate.detail.main.DelegateDetailsPayload +import io.novafoundation.nova.feature_governance_impl.presentation.delegation.delegate.detail.votedReferenda.VotedReferendaFragment +import io.novafoundation.nova.feature_governance_impl.presentation.delegation.delegate.detail.votedReferenda.VotedReferendaPayload +import io.novafoundation.nova.feature_governance_impl.presentation.delegation.delegation.create.chooseAmount.NewDelegationChooseAmountFragment +import io.novafoundation.nova.feature_governance_impl.presentation.delegation.delegation.create.chooseAmount.NewDelegationChooseAmountPayload +import io.novafoundation.nova.feature_governance_impl.presentation.delegation.delegation.create.chooseTrack.NewDelegationChooseTracksFragment +import io.novafoundation.nova.feature_governance_impl.presentation.delegation.delegation.create.chooseTrack.NewDelegationChooseTracksPayload +import io.novafoundation.nova.feature_governance_impl.presentation.delegation.delegation.create.confirm.NewDelegationConfirmFragment +import io.novafoundation.nova.feature_governance_impl.presentation.delegation.delegation.create.confirm.NewDelegationConfirmPayload +import io.novafoundation.nova.feature_governance_impl.presentation.delegation.delegation.removeVotes.RemoveVotesFragment +import io.novafoundation.nova.feature_governance_impl.presentation.delegation.delegation.removeVotes.RemoveVotesPayload +import io.novafoundation.nova.feature_governance_impl.presentation.delegation.delegation.revoke.chooseTracks.RevokeDelegationChooseTracksFragment +import io.novafoundation.nova.feature_governance_impl.presentation.delegation.delegation.revoke.chooseTracks.RevokeDelegationChooseTracksPayload +import io.novafoundation.nova.feature_governance_impl.presentation.delegation.delegation.revoke.confirm.RevokeDelegationConfirmFragment +import io.novafoundation.nova.feature_governance_impl.presentation.delegation.delegation.revoke.confirm.RevokeDelegationConfirmPayload +import io.novafoundation.nova.feature_governance_impl.presentation.referenda.details.ReferendumDetailsFragment +import io.novafoundation.nova.feature_governance_api.presentation.referenda.details.ReferendumDetailsPayload +import io.novafoundation.nova.feature_governance_impl.presentation.common.info.ReferendumInfoFragment +import io.novafoundation.nova.feature_governance_impl.presentation.common.info.ReferendumInfoPayload +import io.novafoundation.nova.feature_governance_impl.presentation.referenda.full.ReferendumFullDetailsFragment +import io.novafoundation.nova.feature_governance_impl.presentation.referenda.full.ReferendumFullDetailsPayload +import io.novafoundation.nova.feature_governance_impl.presentation.referenda.vote.confirm.ConfirmReferendumVoteFragment +import io.novafoundation.nova.feature_governance_impl.presentation.referenda.vote.confirm.ConfirmVoteReferendumPayload +import io.novafoundation.nova.feature_governance_impl.presentation.referenda.vote.setup.common.SetupVoteFragment +import io.novafoundation.nova.feature_governance_impl.presentation.referenda.vote.setup.common.SetupVotePayload +import io.novafoundation.nova.feature_governance_impl.presentation.referenda.voters.ReferendumVotersFragment +import io.novafoundation.nova.feature_governance_impl.presentation.referenda.voters.ReferendumVotersPayload + +class GovernanceNavigator( + navigationHoldersRegistry: NavigationHoldersRegistry, + private val commonNavigator: Navigator, + private val contextManager: ContextManager +) : BaseNavigator(navigationHoldersRegistry), GovernanceRouter { + + override fun openReferendum(payload: ReferendumDetailsPayload) { + navigationBuilder().cases() + .addCase(R.id.referendumDetailsFragment, R.id.action_referendumDetailsFragment_to_referendumDetailsFragment) + .addCase(R.id.referendaSearchFragment, R.id.action_open_referendum_details_from_referenda_search) + .setFallbackCase(R.id.action_open_referendum_details) + .setArgs(ReferendumDetailsFragment.getBundle(payload)) + .navigateInFirstAttachedContext() + } + + override fun openReferendumFullDetails(payload: ReferendumFullDetailsPayload) { + navigationBuilder().action(R.id.action_referendumDetailsFragment_to_referendumFullDetailsFragment) + .setArgs(ReferendumFullDetailsFragment.getBundle(payload)) + .navigateInFirstAttachedContext() + } + + override fun openReferendumVoters(payload: ReferendumVotersPayload) { + navigationBuilder().action(R.id.action_referendumDetailsFragment_to_referendumVotersFragment) + .setArgs(ReferendumVotersFragment.getBundle(payload)) + .navigateInFirstAttachedContext() + } + + override fun openSetupReferendumVote(payload: SetupVotePayload) { + navigationBuilder().action(R.id.action_referendumDetailsFragment_to_setupVoteReferendumFragment) + .setArgs(SetupVoteFragment.getBundle(payload)) + .navigateInFirstAttachedContext() + } + + override fun openSetupTinderGovVote(payload: SetupVotePayload) { + navigationBuilder().action(R.id.action_tinderGovCards_to_setupTinderGovVoteFragment) + .setArgs(SetupVoteFragment.getBundle(payload)) + .navigateInFirstAttachedContext() + } + + override fun backToReferendumDetails() { + navigationBuilder().action(R.id.action_confirmReferendumVote_to_referendumDetailsFragment) + .navigateInFirstAttachedContext() + } + + override fun finishUnlockFlow(shouldCloseLocksScreen: Boolean) { + if (shouldCloseLocksScreen) { + navigationBuilder().action(R.id.action_confirmReferendumVote_to_mainFragment) + .navigateInFirstAttachedContext() + } else { + back() + } + } + + override fun openWalletDetails(id: Long) { + commonNavigator.openWalletDetails(id) + } + + override fun openAddDelegation() { + navigationBuilder().cases() + .addCase(R.id.mainFragment, R.id.action_mainFragment_to_delegation) + .addCase(R.id.yourDelegationsFragment, R.id.action_yourDelegations_to_delegationList) + .navigateInFirstAttachedContext() + } + + override fun openYourDelegations() { + navigationBuilder().action(R.id.action_mainFragment_to_your_delegation) + .navigateInFirstAttachedContext() + } + + override fun openBecomingDelegateTutorial() { + contextManager.getActivity()?.showBrowser(BuildConfig.DELEGATION_TUTORIAL_URL) + } + + override fun backToYourDelegations() { + navigationBuilder().action(R.id.action_back_to_your_delegations) + .navigateInFirstAttachedContext() + } + + override fun openRevokeDelegationChooseTracks(payload: RevokeDelegationChooseTracksPayload) { + navigationBuilder().action(R.id.action_delegateDetailsFragment_to_revokeDelegationChooseTracksFragment) + .setArgs(RevokeDelegationChooseTracksFragment.getBundle(payload)) + .navigateInFirstAttachedContext() + } + + override fun openRevokeDelegationsConfirm(payload: RevokeDelegationConfirmPayload) { + navigationBuilder().action(R.id.action_revokeDelegationChooseTracksFragment_to_revokeDelegationConfirmFragment) + .setArgs(RevokeDelegationConfirmFragment.getBundle(payload)) + .navigateInFirstAttachedContext() + } + + override fun openDelegateSearch() { + navigationBuilder().action(R.id.action_delegateListFragment_to_delegateSearchFragment) + .navigateInFirstAttachedContext() + } + + override fun openSelectGovernanceTracks(bundle: Bundle) { + navigationBuilder().action(R.id.action_open_select_governance_tracks) + .setArgs(bundle) + .navigateInFirstAttachedContext() + } + + override fun openTinderGovCards() { + navigationBuilder().action(R.id.action_openTinderGovCards) + .navigateInFirstAttachedContext() + } + + override fun openTinderGovBasket() { + navigationBuilder().action(R.id.action_tinderGovCards_to_tinderGovBasket) + .navigateInFirstAttachedContext() + } + + override fun openConfirmTinderGovVote() { + navigationBuilder().action(R.id.action_setupTinderGovBasket_to_confirmTinderGovVote) + .navigateInFirstAttachedContext() + } + + override fun backToTinderGovCards() { + navigationBuilder().action(R.id.action_confirmTinderGovVote_to_tinderGovCards) + .navigateInFirstAttachedContext() + } + + override fun openReferendumInfo(payload: ReferendumInfoPayload) { + navigationBuilder().cases() + .addCase(R.id.tinderGovCards, R.id.action_tinderGovCards_to_referendumInfo) + .addCase(R.id.setupTinderGovBasketFragment, R.id.action_setupTinderGovBasket_to_referendumInfo) + .setArgs(ReferendumInfoFragment.getBundle(payload)) + .navigateInFirstAttachedContext() + } + + override fun openReferendaSearch() { + navigationBuilder().action(R.id.action_open_referenda_search) + .navigateInFirstAttachedContext() + } + + override fun openReferendaFilters() { + navigationBuilder().action(R.id.action_open_referenda_filters) + .navigateInFirstAttachedContext() + } + + override fun openRemoveVotes(payload: RemoveVotesPayload) { + navigationBuilder().action(R.id.action_open_remove_votes) + .setArgs(RemoveVotesFragment.getBundle(payload)) + .navigateInFirstAttachedContext() + } + + override fun openDelegateDelegators(payload: DelegateDelegatorsPayload) { + navigationBuilder().action(R.id.action_delegateDetailsFragment_to_delegateDelegatorsFragment) + .setArgs(DelegateDelegatorsFragment.getBundle(payload)) + .navigateInFirstAttachedContext() + } + + override fun openDelegateDetails(payload: DelegateDetailsPayload) { + navigationBuilder().cases() + .addCase(R.id.delegateListFragment, R.id.action_delegateListFragment_to_delegateDetailsFragment) + .addCase(R.id.yourDelegationsFragment, R.id.action_yourDelegations_to_delegationDetails) + .addCase(R.id.delegateSearchFragment, R.id.action_delegateSearchFragment_to_delegateDetailsFragment) + .setArgs(DelegateDetailsFragment.getBundle(payload)) + .navigateInFirstAttachedContext() + } + + override fun openNewDelegationChooseTracks(payload: NewDelegationChooseTracksPayload) { + navigationBuilder().action(R.id.action_delegateDetailsFragment_to_selectDelegationTracks) + .setArgs(NewDelegationChooseTracksFragment.getBundle(payload)) + .navigateInFirstAttachedContext() + } + + override fun openNewDelegationChooseAmount(payload: NewDelegationChooseAmountPayload) { + navigationBuilder().action(R.id.action_selectDelegationTracks_to_newDelegationChooseAmountFragment) + .setArgs(NewDelegationChooseAmountFragment.getBundle(payload)) + .navigateInFirstAttachedContext() + } + + override fun openNewDelegationConfirm(payload: NewDelegationConfirmPayload) { + navigationBuilder().action(R.id.action_newDelegationChooseAmountFragment_to_newDelegationConfirmFragment) + .setArgs(NewDelegationConfirmFragment.getBundle(payload)) + .navigateInFirstAttachedContext() + } + + override fun openVotedReferenda(payload: VotedReferendaPayload) { + navigationBuilder().action(R.id.action_delegateDetailsFragment_to_votedReferendaFragment) + .setArgs(VotedReferendaFragment.getBundle(payload)) + .navigateInFirstAttachedContext() + } + + override fun openDelegateFullDescription(payload: DescriptionPayload) { + navigationBuilder().action(R.id.action_delegateDetailsFragment_to_delegateFullDescription) + .setArgs(DescriptionFragment.getBundle(payload)) + .navigateInFirstAttachedContext() + } + + override fun openDAppBrowser(url: String) { + navigationBuilder().action(R.id.action_referendumDetailsFragment_to_DAppBrowserGraph) + .setArgs(DAppBrowserFragment.getBundle(DAppBrowserPayload.Address(url))) + .navigateInFirstAttachedContext() + } + + override fun openReferendumDescription(payload: DescriptionPayload) { + navigationBuilder().action(R.id.action_referendumDetailsFragment_to_referendumDescription) + .setArgs(DescriptionFragment.getBundle(payload)) + .navigateInFirstAttachedContext() + } + + override fun openConfirmVoteReferendum(payload: ConfirmVoteReferendumPayload) { + navigationBuilder().action(R.id.action_setupVoteReferendumFragment_to_confirmReferendumVote) + .setArgs(ConfirmReferendumVoteFragment.getBundle(payload)) + .navigateInFirstAttachedContext() + } + + override fun openGovernanceLocksOverview() { + navigationBuilder().action(R.id.action_mainFragment_to_governanceLocksOverview) + .navigateInFirstAttachedContext() + } + + override fun openConfirmGovernanceUnlock() { + navigationBuilder().action(R.id.action_governanceLocksOverview_to_confirmGovernanceUnlock) + .navigateInFirstAttachedContext() + } +} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/governance/SelectTracksCommunicatorImpl.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/governance/SelectTracksCommunicatorImpl.kt similarity index 77% rename from app/src/main/java/io/novafoundation/nova/app/root/navigation/governance/SelectTracksCommunicatorImpl.kt rename to app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/governance/SelectTracksCommunicatorImpl.kt index 1f25c9ab0d..4240ff11d8 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/governance/SelectTracksCommunicatorImpl.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/governance/SelectTracksCommunicatorImpl.kt @@ -1,15 +1,15 @@ -package io.novafoundation.nova.app.root.navigation.governance +package io.novafoundation.nova.app.root.navigation.navigators.governance import io.novafoundation.nova.app.root.navigation.NavStackInterScreenCommunicator -import io.novafoundation.nova.app.root.navigation.NavigationHolder +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.list.SelectTracksCommunicator import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.list.SelectTracksRequester import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.list.SelectTracksResponder import io.novafoundation.nova.feature_governance_impl.presentation.GovernanceRouter import io.novafoundation.nova.feature_governance_impl.presentation.tracks.select.governanceTracks.SelectGovernanceTracksFragment -class SelectTracksCommunicatorImpl(private val router: GovernanceRouter, navigationHolder: NavigationHolder) : - NavStackInterScreenCommunicator(navigationHolder), +class SelectTracksCommunicatorImpl(private val router: GovernanceRouter, navigationHoldersRegistry: NavigationHoldersRegistry) : + NavStackInterScreenCommunicator(navigationHoldersRegistry), SelectTracksCommunicator { override fun openRequest(request: SelectTracksRequester.Request) { diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/governance/TinderGovVoteCommunicatorImpl.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/governance/TinderGovVoteCommunicatorImpl.kt similarity index 77% rename from app/src/main/java/io/novafoundation/nova/app/root/navigation/governance/TinderGovVoteCommunicatorImpl.kt rename to app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/governance/TinderGovVoteCommunicatorImpl.kt index a6a1c5ba9f..dacdd81848 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/governance/TinderGovVoteCommunicatorImpl.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/governance/TinderGovVoteCommunicatorImpl.kt @@ -1,8 +1,7 @@ -package io.novafoundation.nova.app.root.navigation.governance +package io.novafoundation.nova.app.root.navigation.navigators.governance import io.novafoundation.nova.app.root.navigation.NavStackInterScreenCommunicator -import io.novafoundation.nova.app.root.navigation.NavigationHolder -import io.novafoundation.nova.common.sequrity.verification.PinCodeTwoFactorVerificationResponder.Response +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry import io.novafoundation.nova.feature_governance_impl.presentation.GovernanceRouter import io.novafoundation.nova.feature_governance_impl.presentation.referenda.vote.setup.common.SetupVotePayload import io.novafoundation.nova.feature_governance_impl.presentation.referenda.vote.setup.tindergov.TinderGovVoteCommunicator @@ -10,8 +9,8 @@ import io.novafoundation.nova.feature_governance_impl.presentation.referenda.vot import io.novafoundation.nova.feature_governance_impl.presentation.referenda.vote.setup.tindergov.TinderGovVoteResponder import kotlinx.coroutines.flow.Flow -class TinderGovVoteCommunicatorImpl(private val router: GovernanceRouter, navigationHolder: NavigationHolder) : - NavStackInterScreenCommunicator(navigationHolder), +class TinderGovVoteCommunicatorImpl(private val router: GovernanceRouter, navigationHoldersRegistry: NavigationHoldersRegistry) : + NavStackInterScreenCommunicator(navigationHoldersRegistry), TinderGovVoteCommunicator { override val responseFlow: Flow diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/ledger/LedgerNavigator.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/ledger/LedgerNavigator.kt new file mode 100644 index 0000000000..4bd50b6020 --- /dev/null +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/ledger/LedgerNavigator.kt @@ -0,0 +1,86 @@ +package io.novafoundation.nova.app.root.navigation.navigators.ledger + +import io.novafoundation.nova.app.R +import io.novafoundation.nova.app.root.navigation.navigators.BaseNavigator +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry +import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter +import io.novafoundation.nova.feature_ledger_impl.presentation.LedgerRouter +import io.novafoundation.nova.feature_ledger_impl.presentation.account.addChain.selectAddress.AddLedgerChainAccountSelectAddressFragment +import io.novafoundation.nova.feature_ledger_impl.presentation.account.addChain.selectAddress.AddLedgerChainAccountSelectAddressPayload +import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectAddress.SelectAddressLedgerFragment +import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectAddress.SelectLedgerAddressPayload +import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.finish.FinishImportGenericLedgerFragment +import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.finish.FinishImportGenericLedgerPayload +import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.preview.PreviewImportGenericLedgerFragment +import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.generic.preview.PreviewImportGenericLedgerPayload +import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.legacy.finish.FinishImportLedgerFragment +import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.legacy.finish.FinishImportLedgerPayload + +class LedgerNavigator( + private val accountRouter: AccountRouter, + navigationHoldersRegistry: NavigationHoldersRegistry +) : BaseNavigator(navigationHoldersRegistry), LedgerRouter { + + override fun openImportFillWallet() { + navigationBuilder().action(R.id.action_startImportLedgerFragment_to_fillWalletImportLedgerFragment) + .navigateInFirstAttachedContext() + } + + override fun returnToImportFillWallet() { + navigationBuilder().action(R.id.action_selectAddressImportLedgerFragment_to_fillWalletImportLedgerFragment) + .navigateInFirstAttachedContext() + } + + override fun openSelectImportAddress(payload: SelectLedgerAddressPayload) { + navigationBuilder().action(R.id.action_selectLedgerImportFragment_to_selectAddressImportLedgerFragment) + .setArgs(SelectAddressLedgerFragment.getBundle(payload)) + .navigateInFirstAttachedContext() + } + + override fun openCreatePincode() { + accountRouter.openCreatePincode() + } + + override fun openMain() { + accountRouter.openMain() + } + + override fun openFinishImportLedger(payload: FinishImportLedgerPayload) { + navigationBuilder().action(R.id.action_fillWalletImportLedgerFragment_to_finishImportLedgerFragment) + .setArgs(FinishImportLedgerFragment.getBundle(payload)) + .navigateInFirstAttachedContext() + } + + override fun finishSignFlow() { + back() + } + + override fun openAddChainAccountSelectAddress(payload: AddLedgerChainAccountSelectAddressPayload) { + navigationBuilder().action(R.id.action_addChainAccountSelectLedgerFragment_to_addChainAccountSelectAddressLedgerFragment) + .setArgs(AddLedgerChainAccountSelectAddressFragment.getBundle(payload)) + .navigateInFirstAttachedContext() + } + + override fun openSelectLedgerGeneric() { + navigationBuilder().action(R.id.action_startImportGenericLedgerFragment_to_selectLedgerGenericImportFragment) + .navigateInFirstAttachedContext() + } + + override fun openSelectAddressGenericLedger(payload: SelectLedgerAddressPayload) { + navigationBuilder().action(R.id.action_selectLedgerGenericImportFragment_to_selectAddressImportGenericLedgerFragment) + .setArgs(SelectAddressLedgerFragment.getBundle(payload)) + .navigateInFirstAttachedContext() + } + + override fun openPreviewLedgerAccountsGeneric(payload: PreviewImportGenericLedgerPayload) { + navigationBuilder().action(R.id.action_selectAddressImportGenericLedgerFragment_to_previewImportGenericLedgerFragment) + .setArgs(PreviewImportGenericLedgerFragment.getBundle(payload)) + .navigateInFirstAttachedContext() + } + + override fun openFinishImportLedgerGeneric(payload: FinishImportGenericLedgerPayload) { + navigationBuilder().action(R.id.action_previewImportGenericLedgerFragment_to_finishImportGenericLedgerFragment) + .setArgs(FinishImportGenericLedgerFragment.getBundle(payload)) + .navigateInFirstAttachedContext() + } +} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/ledger/LedgerSignCommunicatorImpl.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/ledger/LedgerSignCommunicatorImpl.kt similarity index 83% rename from app/src/main/java/io/novafoundation/nova/app/root/navigation/ledger/LedgerSignCommunicatorImpl.kt rename to app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/ledger/LedgerSignCommunicatorImpl.kt index aa0dde720a..03ff422e8a 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/ledger/LedgerSignCommunicatorImpl.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/ledger/LedgerSignCommunicatorImpl.kt @@ -1,9 +1,9 @@ -package io.novafoundation.nova.app.root.navigation.ledger +package io.novafoundation.nova.app.root.navigation.navigators.ledger 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.app.root.navigation.getBackStackEntryBefore +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry import io.novafoundation.nova.feature_account_api.domain.model.LedgerVariant import io.novafoundation.nova.feature_account_api.presenatation.sign.LedgerSignCommunicator import io.novafoundation.nova.feature_account_api.presenatation.sign.SignInterScreenCommunicator.Request @@ -11,8 +11,8 @@ import io.novafoundation.nova.feature_account_api.presenatation.sign.SignInterSc import io.novafoundation.nova.feature_ledger_impl.presentation.account.sign.SignLedgerFragment import io.novafoundation.nova.feature_ledger_impl.presentation.account.sign.SignLedgerPayload -class LedgerSignCommunicatorImpl(navigationHolder: NavigationHolder) : - NavStackInterScreenCommunicator(navigationHolder), LedgerSignCommunicator { +class LedgerSignCommunicatorImpl(navigationHoldersRegistry: NavigationHoldersRegistry) : + NavStackInterScreenCommunicator(navigationHoldersRegistry), LedgerSignCommunicator { private var usedVariant: LedgerVariant? = null diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/ledger/SelectLedgerAddressCommunicatorImpl.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/ledger/SelectLedgerAddressCommunicatorImpl.kt similarity index 79% rename from app/src/main/java/io/novafoundation/nova/app/root/navigation/ledger/SelectLedgerAddressCommunicatorImpl.kt rename to app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/ledger/SelectLedgerAddressCommunicatorImpl.kt index 14346deecf..c3ecde4593 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/ledger/SelectLedgerAddressCommunicatorImpl.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/ledger/SelectLedgerAddressCommunicatorImpl.kt @@ -1,15 +1,15 @@ -package io.novafoundation.nova.app.root.navigation.ledger +package io.novafoundation.nova.app.root.navigation.navigators.ledger 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.app.root.navigation.navigators.NavigationHoldersRegistry import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectLedger.SelectLedgerFragment import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectLedger.SelectLedgerPayload import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.legacy.LedgerChainAccount import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.legacy.SelectLedgerAddressInterScreenCommunicator -class SelectLedgerAddressCommunicatorImpl(navigationHolder: NavigationHolder) : - NavStackInterScreenCommunicator(navigationHolder), +class SelectLedgerAddressCommunicatorImpl(navigationHoldersRegistry: NavigationHoldersRegistry) : + NavStackInterScreenCommunicator(navigationHoldersRegistry), SelectLedgerAddressInterScreenCommunicator { override fun openRequest(request: SelectLedgerPayload) { diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/nft/NftNavigator.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/nft/NftNavigator.kt new file mode 100644 index 0000000000..4c9ec998e3 --- /dev/null +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/nft/NftNavigator.kt @@ -0,0 +1,18 @@ +package io.novafoundation.nova.app.root.navigation.navigators.nft + +import io.novafoundation.nova.app.R +import io.novafoundation.nova.app.root.navigation.navigators.BaseNavigator +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry +import io.novafoundation.nova.feature_nft_impl.NftRouter +import io.novafoundation.nova.feature_nft_impl.presentation.nft.details.NftDetailsFragment + +class NftNavigator( + navigationHoldersRegistry: NavigationHoldersRegistry +) : BaseNavigator(navigationHoldersRegistry), NftRouter { + + override fun openNftDetails(nftId: String) { + navigationBuilder().action(R.id.action_nftListFragment_to_nftDetailsFragment) + .setArgs(NftDetailsFragment.getBundle(nftId)) + .navigateInFirstAttachedContext() + } +} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/pincode/PinCodeTwoFactorVerificationCommunicatorImpl.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/pincode/PinCodeTwoFactorVerificationCommunicatorImpl.kt similarity index 51% rename from app/src/main/java/io/novafoundation/nova/app/root/navigation/pincode/PinCodeTwoFactorVerificationCommunicatorImpl.kt rename to app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/pincode/PinCodeTwoFactorVerificationCommunicatorImpl.kt index fef901f45a..aa38c78f25 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/pincode/PinCodeTwoFactorVerificationCommunicatorImpl.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/pincode/PinCodeTwoFactorVerificationCommunicatorImpl.kt @@ -1,26 +1,25 @@ -package io.novafoundation.nova.app.root.navigation.pincode +package io.novafoundation.nova.app.root.navigation.navigators.pincode 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.app.root.navigation.FlowInterScreenCommunicator +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry +import io.novafoundation.nova.app.root.navigation.navigators.navigationBuilder import io.novafoundation.nova.common.sequrity.verification.PinCodeTwoFactorVerificationCommunicator import io.novafoundation.nova.common.sequrity.verification.PinCodeTwoFactorVerificationRequester.Request import io.novafoundation.nova.common.sequrity.verification.PinCodeTwoFactorVerificationResponder.Response import io.novafoundation.nova.feature_account_impl.presentation.pincode.PinCodeAction import io.novafoundation.nova.feature_account_impl.presentation.pincode.PincodeFragment -import kotlinx.coroutines.flow.Flow class PinCodeTwoFactorVerificationCommunicatorImpl( - navigationHolder: NavigationHolder -) : NavStackInterScreenCommunicator(navigationHolder), PinCodeTwoFactorVerificationCommunicator { + private val navigationHoldersRegistry: NavigationHoldersRegistry +) : FlowInterScreenCommunicator(), PinCodeTwoFactorVerificationCommunicator { - override val responseFlow: Flow - get() = clearedResponseFlow() - - override fun openRequest(request: Request) { - super.openRequest(request) + override fun dispatchRequest(request: Request) { val action = PinCodeAction.TwoFactorVerification(request.useBiometryIfEnabled) val bundle = PincodeFragment.getPinCodeBundle(action) - navController.navigate(R.id.action_pin_code_two_factor_verification, bundle) + + navigationHoldersRegistry.navigationBuilder().action(R.id.action_pin_code_two_factor_verification) + .setArgs(bundle) + .navigateInRoot() } } diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/push/PushGovernanceSettingsCommunicatorImpl.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/push/PushGovernanceSettingsCommunicatorImpl.kt similarity index 80% rename from app/src/main/java/io/novafoundation/nova/app/root/navigation/push/PushGovernanceSettingsCommunicatorImpl.kt rename to app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/push/PushGovernanceSettingsCommunicatorImpl.kt index fbac79f48c..517b949757 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/push/PushGovernanceSettingsCommunicatorImpl.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/push/PushGovernanceSettingsCommunicatorImpl.kt @@ -1,15 +1,15 @@ -package io.novafoundation.nova.app.root.navigation.push +package io.novafoundation.nova.app.root.navigation.navigators.push import io.novafoundation.nova.app.root.navigation.NavStackInterScreenCommunicator -import io.novafoundation.nova.app.root.navigation.NavigationHolder +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry import io.novafoundation.nova.feature_push_notifications.PushNotificationsRouter import io.novafoundation.nova.feature_push_notifications.presentation.governance.PushGovernanceSettingsCommunicator import io.novafoundation.nova.feature_push_notifications.presentation.governance.PushGovernanceSettingsFragment import io.novafoundation.nova.feature_push_notifications.presentation.governance.PushGovernanceSettingsRequester import io.novafoundation.nova.feature_push_notifications.presentation.governance.PushGovernanceSettingsResponder -class PushGovernanceSettingsCommunicatorImpl(private val router: PushNotificationsRouter, navigationHolder: NavigationHolder) : - NavStackInterScreenCommunicator(navigationHolder), +class PushGovernanceSettingsCommunicatorImpl(private val router: PushNotificationsRouter, navigationHoldersRegistry: NavigationHoldersRegistry) : + NavStackInterScreenCommunicator(navigationHoldersRegistry), PushGovernanceSettingsCommunicator { override fun openRequest(request: PushGovernanceSettingsRequester.Request) { diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/push/PushNotificationsNavigator.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/push/PushNotificationsNavigator.kt new file mode 100644 index 0000000000..4211c0da4d --- /dev/null +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/push/PushNotificationsNavigator.kt @@ -0,0 +1,29 @@ +package io.novafoundation.nova.app.root.navigation.navigators.push + +import android.os.Bundle +import io.novafoundation.nova.app.R +import io.novafoundation.nova.app.root.navigation.navigators.BaseNavigator +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry +import io.novafoundation.nova.feature_push_notifications.PushNotificationsRouter + +class PushNotificationsNavigator( + navigationHoldersRegistry: NavigationHoldersRegistry +) : BaseNavigator(navigationHoldersRegistry), PushNotificationsRouter { + + override fun openPushSettings() { + navigationBuilder().action(R.id.action_pushWelcome_to_pushSettings) + .navigateInFirstAttachedContext() + } + + override fun openPushGovernanceSettings(args: Bundle) { + navigationBuilder().action(R.id.action_pushSettings_to_governanceSettings) + .setArgs(args) + .navigateInFirstAttachedContext() + } + + override fun openPushStakingSettings(args: Bundle) { + navigationBuilder().action(R.id.action_pushSettings_to_stakingSettings) + .setArgs(args) + .navigateInFirstAttachedContext() + } +} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/push/PushStakingSettingsCommunicatorImpl.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/push/PushStakingSettingsCommunicatorImpl.kt similarity index 80% rename from app/src/main/java/io/novafoundation/nova/app/root/navigation/push/PushStakingSettingsCommunicatorImpl.kt rename to app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/push/PushStakingSettingsCommunicatorImpl.kt index 42dcb623d4..9f642ed68c 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/push/PushStakingSettingsCommunicatorImpl.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/push/PushStakingSettingsCommunicatorImpl.kt @@ -1,15 +1,15 @@ -package io.novafoundation.nova.app.root.navigation.push +package io.novafoundation.nova.app.root.navigation.navigators.push import io.novafoundation.nova.app.root.navigation.NavStackInterScreenCommunicator -import io.novafoundation.nova.app.root.navigation.NavigationHolder +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry import io.novafoundation.nova.feature_push_notifications.PushNotificationsRouter import io.novafoundation.nova.feature_push_notifications.presentation.staking.PushStakingSettingsCommunicator import io.novafoundation.nova.feature_push_notifications.presentation.staking.PushStakingSettingsFragment import io.novafoundation.nova.feature_push_notifications.presentation.staking.PushStakingSettingsRequester import io.novafoundation.nova.feature_push_notifications.presentation.staking.PushStakingSettingsResponder -class PushStakingSettingsCommunicatorImpl(private val router: PushNotificationsRouter, navigationHolder: NavigationHolder) : - NavStackInterScreenCommunicator(navigationHolder), +class PushStakingSettingsCommunicatorImpl(private val router: PushNotificationsRouter, navigationHoldersRegistry: NavigationHoldersRegistry) : + NavStackInterScreenCommunicator(navigationHoldersRegistry), PushStakingSettingsCommunicator { override fun openRequest(request: PushStakingSettingsRequester.Request) { diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/settings/SettingsNavigator.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/settings/SettingsNavigator.kt new file mode 100644 index 0000000000..a0d0095656 --- /dev/null +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/settings/SettingsNavigator.kt @@ -0,0 +1,133 @@ +package io.novafoundation.nova.app.root.navigation.navigators.settings + +import io.novafoundation.nova.app.R +import io.novafoundation.nova.app.root.navigation.navigators.BaseNavigator +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry +import io.novafoundation.nova.app.root.navigation.navigators.Navigator +import io.novafoundation.nova.app.root.presentation.RootRouter +import io.novafoundation.nova.feature_account_impl.presentation.pincode.PinCodeAction +import io.novafoundation.nova.feature_account_impl.presentation.pincode.PincodeFragment +import io.novafoundation.nova.feature_settings_impl.SettingsRouter +import io.novafoundation.nova.feature_settings_impl.presentation.networkManagement.add.main.AddNetworkMainFragment +import io.novafoundation.nova.feature_settings_impl.presentation.networkManagement.add.main.AddNetworkPayload +import io.novafoundation.nova.feature_settings_impl.presentation.networkManagement.chain.ChainNetworkManagementFragment +import io.novafoundation.nova.feature_settings_impl.presentation.networkManagement.chain.ChainNetworkManagementPayload +import io.novafoundation.nova.feature_settings_impl.presentation.networkManagement.main.NetworkManagementListFragment +import io.novafoundation.nova.feature_settings_impl.presentation.networkManagement.node.CustomNodeFragment +import io.novafoundation.nova.feature_settings_impl.presentation.networkManagement.node.CustomNodePayload +import io.novafoundation.nova.feature_wallet_connect_impl.WalletConnectRouter +import io.novafoundation.nova.feature_wallet_connect_impl.presentation.sessions.list.WalletConnectSessionsPayload + +class SettingsNavigator( + navigationHoldersRegistry: NavigationHoldersRegistry, + private val rootRouter: RootRouter, + private val walletConnectDelegate: WalletConnectRouter, + private val delegate: Navigator +) : BaseNavigator(navigationHoldersRegistry), + SettingsRouter { + + override fun returnToWallet() { + rootRouter.returnToWallet() + } + + override fun openWallets() { + delegate.openWallets() + } + + override fun openNetworks() { + navigationBuilder().action(R.id.action_open_networkManagement) + .navigateInFirstAttachedContext() + } + + override fun openNetworkDetails(payload: ChainNetworkManagementPayload) { + navigationBuilder().action(R.id.action_open_networkManagementDetails) + .setArgs(ChainNetworkManagementFragment.getBundle(payload)) + .navigateInFirstAttachedContext() + } + + override fun openCustomNode(payload: CustomNodePayload) { + navigationBuilder().action(R.id.action_open_customNode) + .setArgs(CustomNodeFragment.getBundle(payload)) + .navigateInFirstAttachedContext() + } + + override fun addNetwork() { + navigationBuilder().action(R.id.action_open_preConfiguredNetworks) + .navigateInFirstAttachedContext() + } + + override fun openCreateNetworkFlow() { + navigationBuilder().action(R.id.action_open_addNetworkFragment) + .navigateInFirstAttachedContext() + } + + override fun openCreateNetworkFlow(payload: AddNetworkPayload.Mode.Add) { + navigationBuilder().action(R.id.action_open_addNetworkFragment) + .setArgs(AddNetworkMainFragment.getBundle(AddNetworkPayload(payload))) + .navigateInFirstAttachedContext() + } + + override fun finishCreateNetworkFlow() { + navigationBuilder().action(R.id.action_finishCreateNetworkFlow) + .setArgs(NetworkManagementListFragment.getBundle(openAddedTab = true)) + .navigateInFirstAttachedContext() + } + + override fun openEditNetwork(payload: AddNetworkPayload.Mode.Edit) { + navigationBuilder().action(R.id.action_open_editNetwork) + .setArgs(AddNetworkMainFragment.getBundle(AddNetworkPayload(payload))) + .navigateInFirstAttachedContext() + } + + override fun openPushNotificationSettings() { + navigationBuilder().action(R.id.action_open_pushNotificationsSettings) + .navigateInFirstAttachedContext() + } + + override fun openCurrencies() { + navigationBuilder().action(R.id.action_mainFragment_to_currenciesFragment) + .navigateInFirstAttachedContext() + } + + override fun openLanguages() { + navigationBuilder().action(R.id.action_mainFragment_to_languagesFragment) + .navigateInFirstAttachedContext() + } + + override fun openAppearance() { + navigationBuilder().action(R.id.action_mainFragment_to_appearanceFragment) + .navigateInFirstAttachedContext() + } + + override fun openChangePinCode() { + navigationBuilder().action(R.id.action_change_pin_code) + .setArgs(PincodeFragment.getPinCodeBundle(PinCodeAction.Change)) + .navigateInFirstAttachedContext() + } + + override fun openWalletDetails(metaId: Long) { + delegate.openWalletDetails(metaId) + } + + override fun openSwitchWallet() { + delegate.openSwitchWallet() + } + + override fun openWalletConnectScan() { + walletConnectDelegate.openScanPairingQrCode() + } + + override fun openWalletConnectSessions() { + walletConnectDelegate.openWalletConnectSessions(WalletConnectSessionsPayload(metaId = null)) + } + + override fun openCloudBackupSettings() { + navigationBuilder().action(R.id.action_open_cloudBackupSettings) + .navigateInFirstAttachedContext() + } + + override fun openManualBackup() { + navigationBuilder().action(R.id.action_open_manualBackupSelectWallet) + .navigateInFirstAttachedContext() + } +} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/staking/StakingDashboardNavigator.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/staking/StakingDashboardNavigator.kt similarity index 72% rename from app/src/main/java/io/novafoundation/nova/app/root/navigation/staking/StakingDashboardNavigator.kt rename to app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/staking/StakingDashboardNavigator.kt index 6ffeac12a3..5e00d65277 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/staking/StakingDashboardNavigator.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/staking/StakingDashboardNavigator.kt @@ -1,17 +1,13 @@ -package io.novafoundation.nova.app.root.navigation.staking +package io.novafoundation.nova.app.root.navigation.navigators.staking import androidx.lifecycle.MutableLiveData import androidx.navigation.NavController import io.novafoundation.nova.app.R -import io.novafoundation.nova.app.root.navigation.BaseNavigator -import io.novafoundation.nova.app.root.navigation.NavigationHolder import io.novafoundation.nova.common.utils.Event import io.novafoundation.nova.common.utils.event import io.novafoundation.nova.feature_staking_impl.presentation.StakingDashboardRouter -class StakingDashboardNavigator( - navigationHolder: NavigationHolder, -) : BaseNavigator(navigationHolder), StakingDashboardRouter { +class StakingDashboardNavigator : StakingDashboardRouter { private var stakingTabNavController: NavController? = null private var pendingAction: Int? = null @@ -22,7 +18,7 @@ class StakingDashboardNavigator( stakingTabNavController = navController if (pendingAction != null) { - navController.performNavigation(pendingAction!!) + navController.navigate(pendingAction!!) pendingAction = null } } @@ -32,7 +28,7 @@ class StakingDashboardNavigator( } override fun openMoreStakingOptions() { - stakingTabNavController?.performNavigation(R.id.action_stakingDashboardFragment_to_moreStakingOptionsFragment) + stakingTabNavController?.navigate(R.id.action_stakingDashboardFragment_to_moreStakingOptionsFragment) } override fun backInStakingTab() { @@ -40,11 +36,11 @@ class StakingDashboardNavigator( } override fun returnToStakingDashboard() { - performNavigation(R.id.back_to_main) + stakingTabNavController?.navigate(R.id.back_to_main) + returnToStakingTabRoot() scrollToDashboardTopEvent.value = Unit.event() } - override fun openStakingDashboard() { stakingTabNavController.performNavigationOrDelay(R.id.action_open_staking) } @@ -57,7 +53,7 @@ class StakingDashboardNavigator( val controller = this if (controller != null) { - controller.performNavigation(actionId) + controller.navigate(actionId) } else { pendingAction = actionId } diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/staking/StartMultiStakingNavigator.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/staking/StartMultiStakingNavigator.kt similarity index 54% rename from app/src/main/java/io/novafoundation/nova/app/root/navigation/staking/StartMultiStakingNavigator.kt rename to app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/staking/StartMultiStakingNavigator.kt index 8ebc2c4a98..19bc797ccc 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/staking/StartMultiStakingNavigator.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/staking/StartMultiStakingNavigator.kt @@ -1,9 +1,9 @@ -package io.novafoundation.nova.app.root.navigation.staking +package io.novafoundation.nova.app.root.navigation.navigators.staking import io.novafoundation.nova.app.R -import io.novafoundation.nova.app.root.navigation.BaseNavigator -import io.novafoundation.nova.app.root.navigation.NavigationHolder -import io.novafoundation.nova.app.root.navigation.Navigator +import io.novafoundation.nova.app.root.navigation.navigators.BaseNavigator +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry +import io.novafoundation.nova.app.root.navigation.navigators.Navigator import io.novafoundation.nova.feature_staking_impl.presentation.StakingDashboardRouter import io.novafoundation.nova.feature_staking_impl.presentation.StartMultiStakingRouter import io.novafoundation.nova.feature_staking_impl.presentation.parachainStaking.start.common.StartParachainStakingMode @@ -19,38 +19,44 @@ import io.novafoundation.nova.feature_staking_impl.presentation.staking.start.se import io.novafoundation.nova.feature_staking_impl.presentation.staking.start.setupStakingType.SetupStakingTypePayload class StartMultiStakingNavigator( - navigationHolder: NavigationHolder, + navigationHoldersRegistry: NavigationHoldersRegistry, private val stakingDashboardRouter: StakingDashboardRouter, private val commonNavigationHolder: Navigator, -) : BaseNavigator(navigationHolder), StartMultiStakingRouter { +) : BaseNavigator(navigationHoldersRegistry), StartMultiStakingRouter { - override fun openStartStakingLanding(payload: StartStakingLandingPayload) = performNavigation( - actionId = R.id.action_mainFragment_to_startStackingLanding, - args = StartStakingLandingFragment.getBundle(payload) - ) + override fun openStartStakingLanding(payload: StartStakingLandingPayload) { + navigationBuilder().action(R.id.action_mainFragment_to_startStackingLanding) + .setArgs(StartStakingLandingFragment.getBundle(payload)) + .navigateInFirstAttachedContext() + } - override fun openStartParachainStaking() = performNavigation( - actionId = R.id.action_startStakingLandingFragment_to_staking_parachain_start_graph, - args = StartParachainStakingFragment.getBundle(StartParachainStakingPayload(StartParachainStakingMode.START)) - ) + override fun openStartParachainStaking() { + navigationBuilder().action(R.id.action_startStakingLandingFragment_to_staking_parachain_start_graph) + .setArgs(StartParachainStakingFragment.getBundle(StartParachainStakingPayload(StartParachainStakingMode.START))) + .navigateInFirstAttachedContext() + } - override fun openStartMultiStaking(payload: SetupAmountMultiStakingPayload) = performNavigation( - actionId = R.id.action_startStakingLandingFragment_to_start_multi_staking_nav_graph, - args = SetupAmountMultiStakingFragment.getBundle(payload) - ) + override fun openStartMultiStaking(payload: SetupAmountMultiStakingPayload) { + navigationBuilder().action(R.id.action_startStakingLandingFragment_to_start_multi_staking_nav_graph) + .setArgs(SetupAmountMultiStakingFragment.getBundle(payload)) + .navigateInFirstAttachedContext() + } - override fun openSetupStakingType(payload: SetupStakingTypePayload) = performNavigation( - R.id.action_setupAmountMultiStakingFragment_to_setupStakingType, - args = SetupStakingTypeFragment.getArguments(payload) - ) + override fun openSetupStakingType(payload: SetupStakingTypePayload) { + navigationBuilder().action(R.id.action_setupAmountMultiStakingFragment_to_setupStakingType) + .setArgs(SetupStakingTypeFragment.getArguments(payload)) + .navigateInFirstAttachedContext() + } - override fun openConfirm(payload: ConfirmMultiStakingPayload) = performNavigation( - actionId = R.id.action_setupAmountMultiStakingFragment_to_confirmMultiStakingFragment, - args = ConfirmMultiStakingFragment.getBundle(payload) - ) + override fun openConfirm(payload: ConfirmMultiStakingPayload) { + navigationBuilder().action(R.id.action_setupAmountMultiStakingFragment_to_confirmMultiStakingFragment) + .setArgs(ConfirmMultiStakingFragment.getBundle(payload)) + .navigateInFirstAttachedContext() + } override fun openSelectedValidators() { - performNavigation(R.id.action_confirmMultiStakingFragment_to_confirmNominationsFragment) + navigationBuilder().action(R.id.action_confirmMultiStakingFragment_to_confirmNominationsFragment) + .navigateInFirstAttachedContext() } override fun returnToStakingDashboard() { diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/staking/nominationPools/NominationPoolsStakingNavigator.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/staking/nominationPools/NominationPoolsStakingNavigator.kt new file mode 100644 index 0000000000..1865b8eeeb --- /dev/null +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/staking/nominationPools/NominationPoolsStakingNavigator.kt @@ -0,0 +1,53 @@ +package io.novafoundation.nova.app.root.navigation.navigators.staking.nominationPools + +import io.novafoundation.nova.app.R +import io.novafoundation.nova.app.root.navigation.navigators.BaseNavigator +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry +import io.novafoundation.nova.app.root.navigation.navigators.Navigator +import io.novafoundation.nova.feature_staking_impl.presentation.NominationPoolsRouter +import io.novafoundation.nova.feature_staking_impl.presentation.nominationPools.bondMore.confirm.NominationPoolsConfirmBondMoreFragment +import io.novafoundation.nova.feature_staking_impl.presentation.nominationPools.bondMore.confirm.NominationPoolsConfirmBondMorePayload +import io.novafoundation.nova.feature_staking_impl.presentation.nominationPools.unbond.confirm.NominationPoolsConfirmUnbondFragment +import io.novafoundation.nova.feature_staking_impl.presentation.nominationPools.unbond.confirm.NominationPoolsConfirmUnbondPayload + +class NominationPoolsStakingNavigator( + navigationHoldersRegistry: NavigationHoldersRegistry, + private val commonNavigator: Navigator, +) : BaseNavigator(navigationHoldersRegistry), NominationPoolsRouter { + + override fun openSetupBondMore() { + navigationBuilder().action(R.id.action_stakingFragment_to_PoolsBondMoreGraph).navigateInFirstAttachedContext() + } + + override fun openConfirmBondMore(payload: NominationPoolsConfirmBondMorePayload) { + navigationBuilder().action(R.id.action_nominationPoolsSetupBondMoreFragment_to_nominationPoolsConfirmBondMoreFragment) + .setArgs(NominationPoolsConfirmBondMoreFragment.getBundle(payload)) + .navigateInFirstAttachedContext() + } + + override fun openConfirmUnbond(payload: NominationPoolsConfirmUnbondPayload) { + navigationBuilder().action(R.id.action_nominationPoolsSetupUnbondFragment_to_nominationPoolsConfirmUnbondFragment) + .setArgs(NominationPoolsConfirmUnbondFragment.getBundle(payload)) + .navigateInFirstAttachedContext() + } + + override fun openRedeem() { + navigationBuilder().action(R.id.action_stakingFragment_to_PoolsRedeemFragment).navigateInFirstAttachedContext() + } + + override fun openClaimRewards() { + navigationBuilder().action(R.id.action_stakingFragment_to_PoolsClaimRewardsFragment).navigateInFirstAttachedContext() + } + + override fun openSetupUnbond() { + navigationBuilder().action(R.id.action_stakingFragment_to_PoolsUnbondGraph).navigateInFirstAttachedContext() + } + + override fun returnToStakingMain() { + navigationBuilder().action(R.id.back_to_staking_main).navigateInFirstAttachedContext() + } + + override fun returnToMain() { + commonNavigator.returnToMain() + } +} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/staking/parachain/ParachainStakingNavigator.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/staking/parachain/ParachainStakingNavigator.kt new file mode 100644 index 0000000000..a0ed67193a --- /dev/null +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/staking/parachain/ParachainStakingNavigator.kt @@ -0,0 +1,98 @@ +package io.novafoundation.nova.app.root.navigation.navigators.staking.parachain + +import io.novafoundation.nova.app.R +import io.novafoundation.nova.app.root.navigation.navigators.BaseNavigator +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry +import io.novafoundation.nova.app.root.navigation.navigators.Navigator +import io.novafoundation.nova.feature_staking_impl.presentation.ParachainStakingRouter +import io.novafoundation.nova.feature_staking_impl.presentation.parachainStaking.rebond.ParachainStakingRebondFragment +import io.novafoundation.nova.feature_staking_impl.presentation.parachainStaking.rebond.model.ParachainStakingRebondPayload +import io.novafoundation.nova.feature_staking_impl.presentation.parachainStaking.start.confirm.ConfirmStartParachainStakingFragment +import io.novafoundation.nova.feature_staking_impl.presentation.parachainStaking.start.confirm.model.ConfirmStartParachainStakingPayload +import io.novafoundation.nova.feature_staking_impl.presentation.parachainStaking.start.setup.StartParachainStakingFragment +import io.novafoundation.nova.feature_staking_impl.presentation.parachainStaking.start.setup.StartParachainStakingPayload +import io.novafoundation.nova.feature_staking_impl.presentation.parachainStaking.unbond.confirm.ParachainStakingUnbondConfirmFragment +import io.novafoundation.nova.feature_staking_impl.presentation.parachainStaking.unbond.confirm.model.ParachainStakingUnbondConfirmPayload +import io.novafoundation.nova.feature_staking_impl.presentation.parachainStaking.yieldBoost.confirm.YieldBoostConfirmFragment +import io.novafoundation.nova.feature_staking_impl.presentation.parachainStaking.yieldBoost.confirm.model.YieldBoostConfirmPayload +import io.novafoundation.nova.feature_staking_impl.presentation.validators.details.StakeTargetDetailsPayload +import io.novafoundation.nova.feature_staking_impl.presentation.validators.details.ValidatorDetailsFragment + +class ParachainStakingNavigator( + navigationHoldersRegistry: NavigationHoldersRegistry, + private val commonNavigator: Navigator, +) : BaseNavigator(navigationHoldersRegistry), ParachainStakingRouter { + + override fun openStartStaking(payload: StartParachainStakingPayload) { + navigationBuilder().action(R.id.action_open_startParachainStakingGraph) + .setArgs(StartParachainStakingFragment.getBundle(payload)) + .navigateInFirstAttachedContext() + } + + override fun openConfirmStartStaking(payload: ConfirmStartParachainStakingPayload) { + navigationBuilder().action(R.id.action_startParachainStakingFragment_to_confirmStartParachainStakingFragment) + .setArgs(ConfirmStartParachainStakingFragment.getBundle(payload)) + .navigateInFirstAttachedContext() + } + + override fun openSearchCollator() { + navigationBuilder().action(R.id.action_selectCollatorFragment_to_searchCollatorFragment) + .navigateInFirstAttachedContext() + } + + override fun openCollatorDetails(payload: StakeTargetDetailsPayload) { + navigationBuilder().action(R.id.open_validator_details) + .setArgs(ValidatorDetailsFragment.getBundle(payload)) + .navigateInFirstAttachedContext() + } + + override fun openWalletDetails(metaId: Long) { + commonNavigator.openWalletDetails(metaId) + } + + override fun returnToStakingMain() { + navigationBuilder().action(R.id.back_to_staking_main).navigateInFirstAttachedContext() + } + + override fun returnToStartStaking() { + navigationBuilder().action(R.id.action_return_to_start_staking).navigateInFirstAttachedContext() + } + + override fun openCurrentCollators() { + navigationBuilder().action(R.id.action_stakingFragment_to_currentCollatorsFragment).navigateInFirstAttachedContext() + } + + override fun openUnbond() { + navigationBuilder().action(R.id.action_open_parachainUnbondGraph).navigateInFirstAttachedContext() + } + + override fun openConfirmUnbond(payload: ParachainStakingUnbondConfirmPayload) { + navigationBuilder().action(R.id.action_parachainStakingUnbondFragment_to_parachainStakingUnbondConfirmFragment) + .setArgs(ParachainStakingUnbondConfirmFragment.getBundle(payload)) + .navigateInFirstAttachedContext() + } + + override fun openRedeem() { + navigationBuilder().action(R.id.action_stakingFragment_to_parachainStakingRedeemFragment).navigateInFirstAttachedContext() + } + + override fun openRebond(payload: ParachainStakingRebondPayload) { + navigationBuilder().action(R.id.action_stakingFragment_to_parachainStakingRebondFragment) + .setArgs(ParachainStakingRebondFragment.getBundle(payload)) + .navigateInFirstAttachedContext() + } + + override fun openSetupYieldBoost() { + navigationBuilder().action(R.id.action_stakingFragment_to_yieldBoostGraph).navigateInFirstAttachedContext() + } + + override fun openConfirmYieldBoost(payload: YieldBoostConfirmPayload) { + navigationBuilder().action(R.id.action_setupYieldBoostFragment_to_yieldBoostConfirmFragment) + .setArgs(YieldBoostConfirmFragment.getBundle(payload)) + .navigateInFirstAttachedContext() + } + + override fun openAddStakingProxy() { + navigationBuilder().action(R.id.action_open_addStakingProxyFragment).navigateInFirstAttachedContext() + } +} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/staking/parachain/SelectCollatorInterScreenCommunicatorImpl.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/staking/parachain/SelectCollatorInterScreenCommunicatorImpl.kt similarity index 82% rename from app/src/main/java/io/novafoundation/nova/app/root/navigation/staking/parachain/SelectCollatorInterScreenCommunicatorImpl.kt rename to app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/staking/parachain/SelectCollatorInterScreenCommunicatorImpl.kt index 4f971de1fa..6554ac71a2 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/staking/parachain/SelectCollatorInterScreenCommunicatorImpl.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/staking/parachain/SelectCollatorInterScreenCommunicatorImpl.kt @@ -1,15 +1,15 @@ -package io.novafoundation.nova.app.root.navigation.staking.parachain +package io.novafoundation.nova.app.root.navigation.navigators.staking.parachain 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.app.root.navigation.navigators.NavigationHoldersRegistry import io.novafoundation.nova.feature_staking_impl.presentation.parachainStaking.collator.common.SelectCollatorInterScreenCommunicator import io.novafoundation.nova.feature_staking_impl.presentation.parachainStaking.collator.common.SelectCollatorInterScreenCommunicator.Request import io.novafoundation.nova.feature_staking_impl.presentation.parachainStaking.collator.common.SelectCollatorInterScreenCommunicator.Response -class SelectCollatorInterScreenCommunicatorImpl(navigationHolder: NavigationHolder) : +class SelectCollatorInterScreenCommunicatorImpl(navigationHoldersRegistry: NavigationHoldersRegistry) : SelectCollatorInterScreenCommunicator, - NavStackInterScreenCommunicator(navigationHolder) { + NavStackInterScreenCommunicator(navigationHoldersRegistry) { override fun respond(response: Response) { val responseEntry = navController.getBackStackEntry(R.id.startParachainStakingFragment) diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/staking/parachain/SelectCollatorSettingsInterScreenCommunicatorImpl.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/staking/parachain/SelectCollatorSettingsInterScreenCommunicatorImpl.kt similarity index 82% rename from app/src/main/java/io/novafoundation/nova/app/root/navigation/staking/parachain/SelectCollatorSettingsInterScreenCommunicatorImpl.kt rename to app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/staking/parachain/SelectCollatorSettingsInterScreenCommunicatorImpl.kt index ef0d6e80f4..7841a0c416 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/staking/parachain/SelectCollatorSettingsInterScreenCommunicatorImpl.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/staking/parachain/SelectCollatorSettingsInterScreenCommunicatorImpl.kt @@ -1,16 +1,16 @@ -package io.novafoundation.nova.app.root.navigation.staking.parachain +package io.novafoundation.nova.app.root.navigation.navigators.staking.parachain 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.app.root.navigation.navigators.NavigationHoldersRegistry import io.novafoundation.nova.feature_staking_impl.presentation.parachainStaking.collator.settings.SelectCollatorSettingsFragment import io.novafoundation.nova.feature_staking_impl.presentation.parachainStaking.collator.settings.SelectCollatorSettingsInterScreenCommunicator import io.novafoundation.nova.feature_staking_impl.presentation.parachainStaking.collator.settings.SelectCollatorSettingsInterScreenCommunicator.Request import io.novafoundation.nova.feature_staking_impl.presentation.parachainStaking.collator.settings.SelectCollatorSettingsInterScreenCommunicator.Response -class SelectCollatorSettingsInterScreenCommunicatorImpl(navigationHolder: NavigationHolder) : +class SelectCollatorSettingsInterScreenCommunicatorImpl(navigationHoldersRegistry: NavigationHoldersRegistry) : SelectCollatorSettingsInterScreenCommunicator, - NavStackInterScreenCommunicator(navigationHolder) { + NavStackInterScreenCommunicator(navigationHoldersRegistry) { override fun openRequest(request: Request) { val bundle = SelectCollatorSettingsFragment.getBundle(request.currentConfig) diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/staking/relaychain/RelayStakingNavigator.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/staking/relaychain/RelayStakingNavigator.kt new file mode 100644 index 0000000000..53c1d7ad81 --- /dev/null +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/staking/relaychain/RelayStakingNavigator.kt @@ -0,0 +1,293 @@ +package io.novafoundation.nova.app.root.navigation.navigators.staking.relaychain + +import io.novafoundation.nova.app.R +import io.novafoundation.nova.app.root.navigation.navigators.BaseNavigator +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry +import io.novafoundation.nova.app.root.navigation.navigators.Navigator +import io.novafoundation.nova.feature_dapp_api.presentation.browser.main.DAppBrowserPayload +import io.novafoundation.nova.feature_dapp_impl.presentation.browser.main.DAppBrowserFragment +import io.novafoundation.nova.feature_staking_impl.domain.staking.redeem.RedeemConsequences +import io.novafoundation.nova.feature_staking_impl.presentation.StakingDashboardRouter +import io.novafoundation.nova.feature_staking_impl.presentation.StakingRouter +import io.novafoundation.nova.feature_staking_impl.presentation.payouts.confirm.ConfirmPayoutFragment +import io.novafoundation.nova.feature_staking_impl.presentation.payouts.confirm.model.ConfirmPayoutPayload +import io.novafoundation.nova.feature_staking_impl.presentation.payouts.detail.PayoutDetailsFragment +import io.novafoundation.nova.feature_staking_impl.presentation.payouts.model.PendingPayoutParcelable +import io.novafoundation.nova.feature_staking_impl.presentation.pools.common.SelectingPoolPayload +import io.novafoundation.nova.feature_staking_impl.presentation.pools.searchPool.SearchPoolFragment +import io.novafoundation.nova.feature_staking_impl.presentation.pools.selectPool.SelectPoolFragment +import io.novafoundation.nova.feature_staking_impl.presentation.staking.bond.confirm.ConfirmBondMoreFragment +import io.novafoundation.nova.feature_staking_impl.presentation.staking.bond.confirm.ConfirmBondMorePayload +import io.novafoundation.nova.feature_staking_impl.presentation.staking.bond.select.SelectBondMoreFragment +import io.novafoundation.nova.feature_staking_impl.presentation.staking.bond.select.SelectBondMorePayload +import io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.controller.confirm.ConfirmSetControllerFragment +import io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.controller.confirm.ConfirmSetControllerPayload +import io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.add.confirm.ConfirmAddStakingProxyFragment +import io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.add.confirm.ConfirmAddStakingProxyPayload +import io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.revoke.ConfirmRemoveStakingProxyFragment +import io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.revoke.ConfirmRemoveStakingProxyPayload +import io.novafoundation.nova.feature_staking_impl.presentation.staking.main.model.StakingStoryModel +import io.novafoundation.nova.feature_staking_impl.presentation.staking.rebond.confirm.ConfirmRebondFragment +import io.novafoundation.nova.feature_staking_impl.presentation.staking.rebond.confirm.ConfirmRebondPayload +import io.novafoundation.nova.feature_staking_impl.presentation.staking.redeem.RedeemFragment +import io.novafoundation.nova.feature_staking_impl.presentation.staking.redeem.RedeemPayload +import io.novafoundation.nova.feature_staking_impl.presentation.staking.rewardDestination.confirm.ConfirmRewardDestinationFragment +import io.novafoundation.nova.feature_staking_impl.presentation.staking.rewardDestination.confirm.parcel.ConfirmRewardDestinationPayload +import io.novafoundation.nova.feature_staking_impl.presentation.staking.unbond.confirm.ConfirmUnbondFragment +import io.novafoundation.nova.feature_staking_impl.presentation.staking.unbond.confirm.ConfirmUnbondPayload +import io.novafoundation.nova.feature_staking_impl.presentation.story.StoryFragment +import io.novafoundation.nova.feature_staking_impl.presentation.validators.change.custom.common.CustomValidatorsPayload +import io.novafoundation.nova.feature_staking_impl.presentation.validators.change.custom.common.CustomValidatorsPayload.FlowType +import io.novafoundation.nova.feature_staking_impl.presentation.validators.change.custom.review.ReviewCustomValidatorsFragment +import io.novafoundation.nova.feature_staking_impl.presentation.validators.change.custom.select.SelectCustomValidatorsFragment +import io.novafoundation.nova.feature_staking_impl.presentation.validators.details.StakeTargetDetailsPayload +import io.novafoundation.nova.feature_staking_impl.presentation.validators.details.ValidatorDetailsFragment + +class RelayStakingNavigator( + navigationHoldersRegistry: NavigationHoldersRegistry, + private val commonNavigator: Navigator, + private val stakingDashboardRouter: StakingDashboardRouter, +) : BaseNavigator(navigationHoldersRegistry), StakingRouter { + + override fun returnToStakingMain() { + navigationBuilder().action(R.id.back_to_staking_main) + .navigateInFirstAttachedContext() + } + + override fun openSwitchWallet() = commonNavigator.openSwitchWallet() + + override fun openWalletDetails(metaAccountId: Long) = commonNavigator.openWalletDetails(metaAccountId) + + override fun openCustomRebond() { + navigationBuilder().action(R.id.action_stakingFragment_to_customRebondFragment) + .navigateInFirstAttachedContext() + } + + override fun openCurrentValidators() { + navigationBuilder().action(R.id.action_stakingFragment_to_currentValidatorsFragment) + .navigateInFirstAttachedContext() + } + + override fun returnToCurrentValidators() { + navigationBuilder().action(R.id.action_confirmStakingFragment_back_to_currentValidatorsFragment) + .navigateInFirstAttachedContext() + } + + override fun openChangeRewardDestination() { + navigationBuilder().action(R.id.action_stakingFragment_to_selectRewardDestinationFragment) + .navigateInFirstAttachedContext() + } + + override fun openConfirmRewardDestination(payload: ConfirmRewardDestinationPayload) { + navigationBuilder().action(R.id.action_selectRewardDestinationFragment_to_confirmRewardDestinationFragment) + .setArgs(ConfirmRewardDestinationFragment.getBundle(payload)) + .navigateInFirstAttachedContext() + } + + override fun openControllerAccount() { + navigationBuilder().action(R.id.action_stakingBalanceFragment_to_setControllerAccountFragment) + .navigateInFirstAttachedContext() + } + + override fun openConfirmSetController(payload: ConfirmSetControllerPayload) { + navigationBuilder().action(R.id.action_stakingSetControllerAccountFragment_to_confirmSetControllerAccountFragment) + .setArgs(ConfirmSetControllerFragment.getBundle(payload)) + .navigateInFirstAttachedContext() + } + + override fun openRecommendedValidators() { + navigationBuilder().action(R.id.action_startChangeValidatorsFragment_to_recommendedValidatorsFragment) + .navigateInFirstAttachedContext() + } + + override fun openSelectCustomValidators() { + val flowType = when (currentDestination?.id) { + R.id.setupStakingType -> FlowType.SETUP_STAKING_VALIDATORS + else -> FlowType.CHANGE_STAKING_VALIDATORS + } + val payload = CustomValidatorsPayload(flowType) + + navigationBuilder().cases() + .addCase(R.id.setupStakingType, R.id.action_setupStakingType_to_selectCustomValidatorsFragment) + .addCase(R.id.startChangeValidatorsFragment, R.id.action_startChangeValidatorsFragment_to_selectCustomValidatorsFragment) + .setArgs(SelectCustomValidatorsFragment.getBundle(payload)) + .navigateInFirstAttachedContext() + } + + override fun openCustomValidatorsSettings() { + navigationBuilder().action(R.id.action_selectCustomValidatorsFragment_to_settingsCustomValidatorsFragment) + .navigateInFirstAttachedContext() + } + + override fun openSearchCustomValidators() { + navigationBuilder().action(R.id.action_selectCustomValidatorsFragment_to_searchCustomValidatorsFragment) + .navigateInFirstAttachedContext() + } + + override fun openReviewCustomValidators(payload: CustomValidatorsPayload) { + navigationBuilder().action(R.id.action_selectCustomValidatorsFragment_to_reviewCustomValidatorsFragment) + .setArgs(ReviewCustomValidatorsFragment.getBundle(payload)) + .navigateInFirstAttachedContext() + } + + override fun openConfirmStaking() { + navigationBuilder().action(R.id.openConfirmStakingFragment) + .navigateInFirstAttachedContext() + } + + override fun openConfirmNominations() { + navigationBuilder().action(R.id.action_confirmStakingFragment_to_confirmNominationsFragment) + .navigateInFirstAttachedContext() + } + + override fun openChainStakingMain() { + navigationBuilder().action(R.id.action_mainFragment_to_stakingGraph) + .navigateInFirstAttachedContext() + } + + override fun openStartChangeValidators() { + navigationBuilder().action(R.id.openStartChangeValidatorsFragment) + .navigateInFirstAttachedContext() + } + + override fun openStory(story: StakingStoryModel) { + navigationBuilder().action(R.id.open_staking_story) + .setArgs(StoryFragment.getBundle(story)) + .navigateInFirstAttachedContext() + } + + override fun openPayouts() { + navigationBuilder().action(R.id.action_stakingFragment_to_payoutsListFragment) + .navigateInFirstAttachedContext() + } + + override fun openPayoutDetails(payout: PendingPayoutParcelable) { + navigationBuilder().action(R.id.action_payoutsListFragment_to_payoutDetailsFragment) + .setArgs(PayoutDetailsFragment.getBundle(payout)) + .navigateInFirstAttachedContext() + } + + override fun openConfirmPayout(payload: ConfirmPayoutPayload) { + navigationBuilder().action(R.id.action_open_confirm_payout) + .setArgs(ConfirmPayoutFragment.getBundle(payload)) + .navigateInFirstAttachedContext() + } + + override fun openBondMore() { + navigationBuilder().action(R.id.action_open_selectBondMoreFragment) + .setArgs(SelectBondMoreFragment.getBundle(SelectBondMorePayload())) + .navigateInFirstAttachedContext() + } + + override fun openConfirmBondMore(payload: ConfirmBondMorePayload) { + navigationBuilder().action(R.id.action_selectBondMoreFragment_to_confirmBondMoreFragment) + .setArgs(ConfirmBondMoreFragment.getBundle(payload)) + .navigateInFirstAttachedContext() + } + + override fun openSelectUnbond() { + navigationBuilder().action(R.id.action_stakingFragment_to_selectUnbondFragment) + .navigateInFirstAttachedContext() + } + + override fun openConfirmUnbond(payload: ConfirmUnbondPayload) { + navigationBuilder().action(R.id.action_selectUnbondFragment_to_confirmUnbondFragment) + .setArgs(ConfirmUnbondFragment.getBundle(payload)) + .navigateInFirstAttachedContext() + } + + override fun openRedeem() { + navigationBuilder().action(R.id.action_open_redeemFragment) + .setArgs(RedeemFragment.getBundle(RedeemPayload())) + .navigateInFirstAttachedContext() + } + + override fun openConfirmRebond(payload: ConfirmRebondPayload) { + navigationBuilder().action(R.id.action_open_confirm_rebond) + .setArgs(ConfirmRebondFragment.getBundle(payload)) + .navigateInFirstAttachedContext() + } + + override fun openValidatorDetails(payload: StakeTargetDetailsPayload) { + navigationBuilder().action(R.id.open_validator_details) + .setArgs(ValidatorDetailsFragment.getBundle(payload)) + .navigateInFirstAttachedContext() + } + + override fun openRebag() { + navigationBuilder().action(R.id.action_stakingFragment_to_rebag) + .navigateInFirstAttachedContext() + } + + override fun openStakingPeriods() { + navigationBuilder().action(R.id.action_stakingFragment_to_staking_periods) + .navigateInFirstAttachedContext() + } + + override fun openSetupStakingType() { + navigationBuilder().action(R.id.action_setupAmountMultiStakingFragment_to_setupStakingType) + .navigateInFirstAttachedContext() + } + + override fun openSelectPool(payload: SelectingPoolPayload) { + val arguments = SelectPoolFragment.getBundle(payload) + navigationBuilder().action(R.id.action_setupStakingType_to_selectCustomPoolFragment) + .setArgs(arguments) + .navigateInFirstAttachedContext() + } + + override fun openSearchPool(payload: SelectingPoolPayload) { + val arguments = SearchPoolFragment.getBundle(payload) + navigationBuilder().action(R.id.action_selectPool_to_searchPoolFragment) + .setArgs(arguments) + .navigateInFirstAttachedContext() + } + + override fun finishSetupValidatorsFlow() { + navigationBuilder().action(R.id.action_back_to_setupAmountMultiStakingFragment) + .navigateInFirstAttachedContext() + } + + override fun finishSetupPoolFlow() { + navigationBuilder().cases() + .addCase(R.id.searchPoolFragment, R.id.action_searchPool_to_setupAmountMultiStakingFragment) + .addCase(R.id.selectPoolFragment, R.id.action_selectPool_to_setupAmountMultiStakingFragment) + .navigateInFirstAttachedContext() + } + + override fun finishRedeemFlow(redeemConsequences: RedeemConsequences) { + if (redeemConsequences.willKillStash) { + stakingDashboardRouter.returnToStakingDashboard() + } else { + returnToStakingMain() + } + } + + override fun openAddStakingProxy() { + navigationBuilder().action(R.id.action_open_addStakingProxyFragment) + .navigateInFirstAttachedContext() + } + + override fun openConfirmAddStakingProxy(payload: ConfirmAddStakingProxyPayload) { + navigationBuilder().action(R.id.action_addStakingProxyFragment_to_confirmAddStakingProxyFragment) + .setArgs(ConfirmAddStakingProxyFragment.getBundle(payload)) + .navigateInFirstAttachedContext() + } + + override fun openStakingProxyList() { + navigationBuilder().action(R.id.action_open_stakingProxyList) + .navigateInFirstAttachedContext() + } + + override fun openConfirmRemoveStakingProxy(payload: ConfirmRemoveStakingProxyPayload) { + navigationBuilder().action(R.id.action_open_confirmRemoveStakingProxyFragment) + .setArgs(ConfirmRemoveStakingProxyFragment.getBundle(payload)) + .navigateInFirstAttachedContext() + } + + override fun openDAppBrowser(url: String) { + navigationBuilder().action(R.id.action_open_dappBrowser) + .setArgs(DAppBrowserFragment.getBundle(DAppBrowserPayload.Address(url))) + .navigateInFirstAttachedContext() + } +} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/swap/SwapNavigator.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/swap/SwapNavigator.kt new file mode 100644 index 0000000000..0069a265cf --- /dev/null +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/swap/SwapNavigator.kt @@ -0,0 +1,87 @@ +package io.novafoundation.nova.app.root.navigation.navigators.swap + +import io.novafoundation.nova.app.R +import io.novafoundation.nova.app.root.navigation.navigators.BaseNavigator +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry +import io.novafoundation.nova.app.root.navigation.navigators.Navigator +import io.novafoundation.nova.feature_assets.presentation.balance.detail.BalanceDetailFragment +import io.novafoundation.nova.feature_assets.presentation.send.amount.SendPayload +import io.novafoundation.nova.feature_assets.presentation.swap.asset.AssetSwapFlowFragment +import io.novafoundation.nova.feature_assets.presentation.swap.asset.SwapFlowPayload +import io.novafoundation.nova.feature_swap_api.presentation.model.SwapSettingsPayload +import io.novafoundation.nova.feature_swap_impl.presentation.SwapRouter +import io.novafoundation.nova.feature_swap_impl.presentation.main.SwapMainSettingsFragment +import io.novafoundation.nova.feature_wallet_api.presentation.model.AssetPayload + +class SwapNavigator( + navigationHoldersRegistry: NavigationHoldersRegistry, + private val commonDelegate: Navigator +) : BaseNavigator(navigationHoldersRegistry), SwapRouter { + + override fun openSwapRoute() { + navigationBuilder().action(R.id.action_open_swapRouteFragment) + .navigateInFirstAttachedContext() + } + + override fun openSwapFee() { + navigationBuilder().action(R.id.action_open_swapFeeFragment) + .navigateInFirstAttachedContext() + } + + override fun openSwapExecution() { + navigationBuilder().action(R.id.action_swapConfirmationFragment_to_swapExecutionFragment) + .navigateInFirstAttachedContext() + } + + override fun openSwapConfirmation() { + navigationBuilder().action(R.id.action_swapMainSettingsFragment_to_swapConfirmationFragment) + .navigateInFirstAttachedContext() + } + + override fun openSwapOptions() { + navigationBuilder().action(R.id.action_swapMainSettingsFragment_to_swapOptionsFragment) + .navigateInFirstAttachedContext() + } + + override fun openRetrySwap(payload: SwapSettingsPayload) { + navigationBuilder().action(R.id.action_swapExecutionFragment_to_swapSettingsFragment) + .setArgs(SwapMainSettingsFragment.getBundle(payload)) + .navigateInFirstAttachedContext() + } + + override fun openBalanceDetails(assetPayload: AssetPayload) { + val bundle = BalanceDetailFragment.getBundle(assetPayload) + + navigationBuilder().action(R.id.action_swapExecutionFragment_to_assetDetails) + .setArgs(bundle) + .navigateInFirstAttachedContext() + } + + override fun selectAssetIn(selectedAsset: AssetPayload?) { + val payload = SwapFlowPayload.ReselectAssetIn(selectedAsset) + val bundle = AssetSwapFlowFragment.getBundle(payload) + + navigationBuilder().action(R.id.action_swapSettingsFragment_to_select_swap_token_graph) + .setArgs(bundle) + .navigateInFirstAttachedContext() + } + + override fun selectAssetOut(selectedAsset: AssetPayload?) { + val payload = SwapFlowPayload.ReselectAssetOut(selectedAsset) + val bundle = AssetSwapFlowFragment.getBundle(payload) + + navigationBuilder().action(R.id.action_swapSettingsFragment_to_select_swap_token_graph) + .setArgs(bundle) + .navigateInFirstAttachedContext() + } + + override fun openSendCrossChain(destination: AssetPayload, recipientAddress: String?) { + val payload = SendPayload.SpecifiedDestination(destination) + + commonDelegate.openSend(payload, recipientAddress) + } + + override fun openReceive(assetPayload: AssetPayload) { + commonDelegate.openReceive(assetPayload) + } +} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/versions/VersionsNavigator.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/versions/VersionsNavigator.kt new file mode 100644 index 0000000000..9fe787bdb0 --- /dev/null +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/versions/VersionsNavigator.kt @@ -0,0 +1,24 @@ +package io.novafoundation.nova.app.root.navigation.navigators.versions + +import io.novafoundation.nova.app.R +import io.novafoundation.nova.app.root.navigation.navigators.BaseNavigator +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry +import io.novafoundation.nova.common.resources.ContextManager +import io.novafoundation.nova.common.utils.showBrowser +import io.novafoundation.nova.feature_versions_api.presentation.VersionsRouter + +class VersionsNavigator( + navigationHoldersRegistry: NavigationHoldersRegistry, + private val contextManager: ContextManager, + private val updateSourceLink: String +) : BaseNavigator(navigationHoldersRegistry), VersionsRouter { + + override fun openAppUpdater() { + contextManager.getActivity()?.showBrowser(updateSourceLink, R.string.common_cannot_find_app) + } + + override fun closeUpdateNotifications() { + navigationBuilder().action(R.id.action_close_update_notifications) + .navigateInRoot() + } +} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/vote/VoteNavigator.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/vote/VoteNavigator.kt similarity index 82% rename from app/src/main/java/io/novafoundation/nova/app/root/navigation/vote/VoteNavigator.kt rename to app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/vote/VoteNavigator.kt index b32f397f54..8f6f157998 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/vote/VoteNavigator.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/vote/VoteNavigator.kt @@ -1,7 +1,7 @@ -package io.novafoundation.nova.app.root.navigation.vote +package io.novafoundation.nova.app.root.navigation.navigators.vote import androidx.fragment.app.Fragment -import io.novafoundation.nova.app.root.navigation.Navigator +import io.novafoundation.nova.app.root.navigation.navigators.Navigator import io.novafoundation.nova.feature_crowdloan_impl.presentation.main.CrowdloanFragment import io.novafoundation.nova.feature_governance_impl.presentation.referenda.list.ReferendaListFragment import io.novafoundation.nova.feature_vote.presentation.VoteRouter diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/wallet/CurrencyNavigator.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/wallet/CurrencyNavigator.kt new file mode 100644 index 0000000000..107cd8e4a5 --- /dev/null +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/wallet/CurrencyNavigator.kt @@ -0,0 +1,16 @@ +package io.novafoundation.nova.app.root.navigation.navigators.wallet + +import io.novafoundation.nova.app.root.navigation.navigators.BaseNavigator +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry +import io.novafoundation.nova.app.root.presentation.RootRouter +import io.novafoundation.nova.feature_currency_api.presentation.CurrencyRouter + +class CurrencyNavigator( + val rootRouter: RootRouter, + navigationHoldersRegistry: NavigationHoldersRegistry +) : BaseNavigator(navigationHoldersRegistry), CurrencyRouter { + + override fun returnToWallet() { + rootRouter.returnToWallet() + } +} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/walletConnect/ApproveSessionCommunicatorImpl.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/walletConnect/ApproveSessionCommunicatorImpl.kt similarity index 64% rename from app/src/main/java/io/novafoundation/nova/app/root/navigation/walletConnect/ApproveSessionCommunicatorImpl.kt rename to app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/walletConnect/ApproveSessionCommunicatorImpl.kt index eec80e7f8e..1c35992454 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/walletConnect/ApproveSessionCommunicatorImpl.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/walletConnect/ApproveSessionCommunicatorImpl.kt @@ -1,16 +1,17 @@ -package io.novafoundation.nova.app.root.navigation.walletConnect +package io.novafoundation.nova.app.root.navigation.navigators.walletConnect import com.walletconnect.web3.wallet.client.Wallet import io.novafoundation.nova.app.R import io.novafoundation.nova.app.root.navigation.FlowInterScreenCommunicator -import io.novafoundation.nova.app.root.navigation.NavigationHolder +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry +import io.novafoundation.nova.app.root.navigation.navigators.navigationBuilder import io.novafoundation.nova.common.utils.sequrity.AutomaticInteractionGate import io.novafoundation.nova.common.utils.sequrity.awaitInteractionAllowed import io.novafoundation.nova.feature_wallet_connect_impl.presentation.sessions.approve.ApproveSessionCommunicator import kotlinx.coroutines.launch class ApproveSessionCommunicatorImpl( - private val navigationHolder: NavigationHolder, + private val navigationHoldersRegistry: NavigationHoldersRegistry, private val automaticInteractionGate: AutomaticInteractionGate, ) : FlowInterScreenCommunicator(), ApproveSessionCommunicator { @@ -19,7 +20,8 @@ class ApproveSessionCommunicatorImpl( launch { automaticInteractionGate.awaitInteractionAllowed() - navigationHolder.navController!!.navigate(R.id.action_open_approve_wallet_connect_session) + navigationHoldersRegistry.navigationBuilder().action(R.id.action_open_approve_wallet_connect_session) + .navigateInFirstAttachedContext() } } } diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/walletConnect/WalletConnectNavigator.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/walletConnect/WalletConnectNavigator.kt new file mode 100644 index 0000000000..a797ef16d5 --- /dev/null +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/walletConnect/WalletConnectNavigator.kt @@ -0,0 +1,37 @@ +package io.novafoundation.nova.app.root.navigation.navigators.walletConnect + +import io.novafoundation.nova.app.R +import io.novafoundation.nova.app.root.navigation.navigators.BaseNavigator +import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry +import io.novafoundation.nova.feature_wallet_connect_impl.WalletConnectRouter +import io.novafoundation.nova.feature_wallet_connect_impl.presentation.sessions.details.WalletConnectSessionDetailsFragment +import io.novafoundation.nova.feature_wallet_connect_impl.presentation.sessions.details.WalletConnectSessionDetailsPayload +import io.novafoundation.nova.feature_wallet_connect_impl.presentation.sessions.list.WalletConnectSessionsFragment +import io.novafoundation.nova.feature_wallet_connect_impl.presentation.sessions.list.WalletConnectSessionsPayload + +class WalletConnectNavigator( + navigationHoldersRegistry: NavigationHoldersRegistry +) : BaseNavigator(navigationHoldersRegistry), WalletConnectRouter { + + override fun openSessionDetails(payload: WalletConnectSessionDetailsPayload) { + navigationBuilder().action(R.id.action_walletConnectSessionsFragment_to_walletConnectSessionDetailsFragment) + .setArgs(WalletConnectSessionDetailsFragment.getBundle(payload)) + .navigateInFirstAttachedContext() + } + + override fun openScanPairingQrCode() { + navigationBuilder().action(R.id.action_open_scanWalletConnect) + .navigateInFirstAttachedContext() + } + + override fun backToSettings() { + navigationBuilder().action(R.id.walletConnectSessionDetailsFragment_to_settings) + .navigateInFirstAttachedContext() + } + + override fun openWalletConnectSessions(payload: WalletConnectSessionsPayload) { + navigationBuilder().action(R.id.action_mainFragment_to_walletConnectGraph) + .setArgs(WalletConnectSessionsFragment.getBundle(payload)) + .navigateInFirstAttachedContext() + } +} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/nft/NftNavigator.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/nft/NftNavigator.kt deleted file mode 100644 index b00a111c45..0000000000 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/nft/NftNavigator.kt +++ /dev/null @@ -1,17 +0,0 @@ -package io.novafoundation.nova.app.root.navigation.nft - -import io.novafoundation.nova.app.R -import io.novafoundation.nova.app.root.navigation.BaseNavigator -import io.novafoundation.nova.app.root.navigation.NavigationHolder -import io.novafoundation.nova.feature_nft_impl.NftRouter -import io.novafoundation.nova.feature_nft_impl.presentation.nft.details.NftDetailsFragment - -class NftNavigator( - navigationHolder: NavigationHolder, -) : BaseNavigator(navigationHolder), NftRouter { - - override fun openNftDetails(nftId: String) = performNavigation( - actionId = R.id.action_nftListFragment_to_nftDetailsFragment, - args = NftDetailsFragment.getBundle(nftId) - ) -} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/push/PushNotificationsNavigator.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/push/PushNotificationsNavigator.kt deleted file mode 100644 index c66430d5ca..0000000000 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/push/PushNotificationsNavigator.kt +++ /dev/null @@ -1,24 +0,0 @@ -package io.novafoundation.nova.app.root.navigation.push - -import android.os.Bundle -import io.novafoundation.nova.app.R -import io.novafoundation.nova.app.root.navigation.BaseNavigator -import io.novafoundation.nova.app.root.navigation.NavigationHolder -import io.novafoundation.nova.feature_push_notifications.PushNotificationsRouter - -class PushNotificationsNavigator( - navigationHolder: NavigationHolder, -) : BaseNavigator(navigationHolder), PushNotificationsRouter { - - override fun openPushSettings() { - performNavigation(R.id.action_pushWelcome_to_pushSettings) - } - - override fun openPushGovernanceSettings(args: Bundle) { - performNavigation(R.id.action_pushSettings_to_governanceSettings, args) - } - - override fun openPushStakingSettings(args: Bundle) { - performNavigation(R.id.action_pushSettings_to_stakingSettings, args) - } -} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/settings/SettingsNavigator.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/settings/SettingsNavigator.kt deleted file mode 100644 index 92a5ac4d00..0000000000 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/settings/SettingsNavigator.kt +++ /dev/null @@ -1,119 +0,0 @@ -package io.novafoundation.nova.app.root.navigation.settings - -import io.novafoundation.nova.app.R -import io.novafoundation.nova.app.root.navigation.BaseNavigator -import io.novafoundation.nova.app.root.navigation.NavigationHolder -import io.novafoundation.nova.app.root.navigation.Navigator -import io.novafoundation.nova.app.root.presentation.RootRouter -import io.novafoundation.nova.feature_account_impl.presentation.pincode.PinCodeAction -import io.novafoundation.nova.feature_account_impl.presentation.pincode.PincodeFragment -import io.novafoundation.nova.feature_settings_impl.SettingsRouter -import io.novafoundation.nova.feature_settings_impl.presentation.networkManagement.add.main.AddNetworkMainFragment -import io.novafoundation.nova.feature_settings_impl.presentation.networkManagement.add.main.AddNetworkPayload -import io.novafoundation.nova.feature_settings_impl.presentation.networkManagement.chain.ChainNetworkManagementFragment -import io.novafoundation.nova.feature_settings_impl.presentation.networkManagement.chain.ChainNetworkManagementPayload -import io.novafoundation.nova.feature_settings_impl.presentation.networkManagement.main.NetworkManagementListFragment -import io.novafoundation.nova.feature_settings_impl.presentation.networkManagement.node.CustomNodeFragment -import io.novafoundation.nova.feature_settings_impl.presentation.networkManagement.node.CustomNodePayload -import io.novafoundation.nova.feature_wallet_connect_impl.WalletConnectRouter -import io.novafoundation.nova.feature_wallet_connect_impl.presentation.sessions.list.WalletConnectSessionsPayload - -class SettingsNavigator( - navigationHolder: NavigationHolder, - private val rootRouter: RootRouter, - private val walletConnectDelegate: WalletConnectRouter, - private val delegate: Navigator -) : BaseNavigator(navigationHolder), - SettingsRouter { - - override fun returnToWallet() { - rootRouter.returnToWallet() - } - - override fun openWallets() { - delegate.openWallets() - } - - override fun openNetworks() { - performNavigation(R.id.action_open_networkManagement) - } - - override fun openNetworkDetails(payload: ChainNetworkManagementPayload) { - performNavigation( - R.id.action_open_networkManagementDetails, - args = ChainNetworkManagementFragment.getBundle(payload) - ) - } - - override fun openCustomNode(payload: CustomNodePayload) { - performNavigation( - R.id.action_open_customNode, - args = CustomNodeFragment.getBundle(payload) - ) - } - - override fun addNetwork() { - performNavigation(R.id.action_open_preConfiguredNetworks) - } - - override fun openCreateNetworkFlow() { - performNavigation(R.id.action_open_addNetworkFragment) - } - - override fun openCreateNetworkFlow(payload: AddNetworkPayload.Mode.Add) { - performNavigation( - R.id.action_open_addNetworkFragment, - args = AddNetworkMainFragment.getBundle(AddNetworkPayload(payload)) - ) - } - - override fun finishCreateNetworkFlow() { - performNavigation(R.id.action_finishCreateNetworkFlow, args = NetworkManagementListFragment.getBundle(openAddedTab = true)) - } - - override fun openEditNetwork(payload: AddNetworkPayload.Mode.Edit) { - performNavigation( - R.id.action_open_editNetwork, - args = AddNetworkMainFragment.getBundle(AddNetworkPayload(payload)) - ) - } - - override fun openPushNotificationSettings() { - performNavigation(R.id.action_open_pushNotificationsSettings) - } - - override fun openCurrencies() = performNavigation(R.id.action_mainFragment_to_currenciesFragment) - - override fun openLanguages() = performNavigation(R.id.action_mainFragment_to_languagesFragment) - - override fun openAppearance() = performNavigation(R.id.action_mainFragment_to_appearanceFragment) - - override fun openChangePinCode() = performNavigation( - actionId = R.id.action_change_pin_code, - args = PincodeFragment.getPinCodeBundle(PinCodeAction.Change) - ) - - override fun openWalletDetails(metaId: Long) { - delegate.openWalletDetails(metaId) - } - - override fun openSwitchWallet() { - delegate.openSwitchWallet() - } - - override fun openWalletConnectScan() { - walletConnectDelegate.openScanPairingQrCode() - } - - override fun openWalletConnectSessions() { - walletConnectDelegate.openWalletConnectSessions(WalletConnectSessionsPayload(metaId = null)) - } - - override fun openCloudBackupSettings() { - performNavigation(R.id.action_open_cloudBackupSettings) - } - - override fun openManualBackup() { - performNavigation(R.id.action_open_manualBackupSelectWallet) - } -} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/staking/nominationPools/NominationPoolsStakingNavigator.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/staking/nominationPools/NominationPoolsStakingNavigator.kt deleted file mode 100644 index 43cd6f6fb3..0000000000 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/staking/nominationPools/NominationPoolsStakingNavigator.kt +++ /dev/null @@ -1,41 +0,0 @@ -package io.novafoundation.nova.app.root.navigation.staking.nominationPools - -import io.novafoundation.nova.app.R -import io.novafoundation.nova.app.root.navigation.BaseNavigator -import io.novafoundation.nova.app.root.navigation.NavigationHolder -import io.novafoundation.nova.app.root.navigation.Navigator -import io.novafoundation.nova.feature_staking_impl.presentation.NominationPoolsRouter -import io.novafoundation.nova.feature_staking_impl.presentation.nominationPools.bondMore.confirm.NominationPoolsConfirmBondMoreFragment -import io.novafoundation.nova.feature_staking_impl.presentation.nominationPools.bondMore.confirm.NominationPoolsConfirmBondMorePayload -import io.novafoundation.nova.feature_staking_impl.presentation.nominationPools.unbond.confirm.NominationPoolsConfirmUnbondFragment -import io.novafoundation.nova.feature_staking_impl.presentation.nominationPools.unbond.confirm.NominationPoolsConfirmUnbondPayload - -class NominationPoolsStakingNavigator( - navigationHolder: NavigationHolder, - private val commonNavigator: Navigator, -) : BaseNavigator(navigationHolder), NominationPoolsRouter { - - override fun openSetupBondMore() = performNavigation(R.id.action_stakingFragment_to_PoolsBondMoreGraph) - - override fun openConfirmBondMore(payload: NominationPoolsConfirmBondMorePayload) = performNavigation( - actionId = R.id.action_nominationPoolsSetupBondMoreFragment_to_nominationPoolsConfirmBondMoreFragment, - args = NominationPoolsConfirmBondMoreFragment.getBundle(payload) - ) - - override fun openConfirmUnbond(payload: NominationPoolsConfirmUnbondPayload) = performNavigation( - actionId = R.id.action_nominationPoolsSetupUnbondFragment_to_nominationPoolsConfirmUnbondFragment, - args = NominationPoolsConfirmUnbondFragment.getBundle(payload) - ) - - override fun openRedeem() = performNavigation(R.id.action_stakingFragment_to_PoolsRedeemFragment) - - override fun openClaimRewards() = performNavigation(R.id.action_stakingFragment_to_PoolsClaimRewardsFragment) - - override fun openSetupUnbond() = performNavigation(R.id.action_stakingFragment_to_PoolsUnbondGraph) - - override fun returnToStakingMain() = performNavigation(R.id.back_to_staking_main) - - override fun returnToMain() { - commonNavigator.returnToMain() - } -} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/staking/parachain/ParachainStakingNavigator.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/staking/parachain/ParachainStakingNavigator.kt deleted file mode 100644 index 38be483e81..0000000000 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/staking/parachain/ParachainStakingNavigator.kt +++ /dev/null @@ -1,79 +0,0 @@ -package io.novafoundation.nova.app.root.navigation.staking.parachain - -import io.novafoundation.nova.app.R -import io.novafoundation.nova.app.root.navigation.BaseNavigator -import io.novafoundation.nova.app.root.navigation.NavigationHolder -import io.novafoundation.nova.app.root.navigation.Navigator -import io.novafoundation.nova.feature_staking_impl.presentation.ParachainStakingRouter -import io.novafoundation.nova.feature_staking_impl.presentation.parachainStaking.rebond.ParachainStakingRebondFragment -import io.novafoundation.nova.feature_staking_impl.presentation.parachainStaking.rebond.model.ParachainStakingRebondPayload -import io.novafoundation.nova.feature_staking_impl.presentation.parachainStaking.start.confirm.ConfirmStartParachainStakingFragment -import io.novafoundation.nova.feature_staking_impl.presentation.parachainStaking.start.confirm.model.ConfirmStartParachainStakingPayload -import io.novafoundation.nova.feature_staking_impl.presentation.parachainStaking.start.setup.StartParachainStakingFragment -import io.novafoundation.nova.feature_staking_impl.presentation.parachainStaking.start.setup.StartParachainStakingPayload -import io.novafoundation.nova.feature_staking_impl.presentation.parachainStaking.unbond.confirm.ParachainStakingUnbondConfirmFragment -import io.novafoundation.nova.feature_staking_impl.presentation.parachainStaking.unbond.confirm.model.ParachainStakingUnbondConfirmPayload -import io.novafoundation.nova.feature_staking_impl.presentation.parachainStaking.yieldBoost.confirm.YieldBoostConfirmFragment -import io.novafoundation.nova.feature_staking_impl.presentation.parachainStaking.yieldBoost.confirm.model.YieldBoostConfirmPayload -import io.novafoundation.nova.feature_staking_impl.presentation.validators.details.StakeTargetDetailsPayload -import io.novafoundation.nova.feature_staking_impl.presentation.validators.details.ValidatorDetailsFragment - -class ParachainStakingNavigator( - navigationHolder: NavigationHolder, - private val commonNavigator: Navigator, -) : BaseNavigator(navigationHolder), ParachainStakingRouter { - - override fun openStartStaking(payload: StartParachainStakingPayload) = performNavigation( - actionId = R.id.action_open_startParachainStakingGraph, - args = StartParachainStakingFragment.getBundle(payload) - ) - - override fun openConfirmStartStaking(payload: ConfirmStartParachainStakingPayload) = performNavigation( - actionId = R.id.action_startParachainStakingFragment_to_confirmStartParachainStakingFragment, - args = ConfirmStartParachainStakingFragment.getBundle(payload) - ) - - override fun openSearchCollator() = performNavigation(R.id.action_selectCollatorFragment_to_searchCollatorFragment) - - override fun openCollatorDetails(payload: StakeTargetDetailsPayload) = performNavigation( - actionId = R.id.open_validator_details, - args = ValidatorDetailsFragment.getBundle(payload) - ) - - override fun openWalletDetails(metaId: Long) { - commonNavigator.openWalletDetails(metaId) - } - - override fun returnToStakingMain() = performNavigation(R.id.back_to_staking_main) - - override fun returnToStartStaking() = performNavigation(R.id.action_return_to_start_staking) - - override fun openCurrentCollators() = performNavigation(R.id.action_stakingFragment_to_currentCollatorsFragment) - - override fun openUnbond() = performNavigation(R.id.action_open_parachainUnbondGraph) - - override fun openConfirmUnbond(payload: ParachainStakingUnbondConfirmPayload) = performNavigation( - actionId = R.id.action_parachainStakingUnbondFragment_to_parachainStakingUnbondConfirmFragment, - args = ParachainStakingUnbondConfirmFragment.getBundle(payload) - ) - - override fun openRedeem() = performNavigation(R.id.action_stakingFragment_to_parachainStakingRedeemFragment) - - override fun openRebond(payload: ParachainStakingRebondPayload) = performNavigation( - actionId = R.id.action_stakingFragment_to_parachainStakingRebondFragment, - args = ParachainStakingRebondFragment.getBundle(payload) - ) - - override fun openSetupYieldBoost() = performNavigation(R.id.action_stakingFragment_to_yieldBoostGraph) - - override fun openConfirmYieldBoost( - payload: YieldBoostConfirmPayload - ) = performNavigation( - actionId = R.id.action_setupYieldBoostFragment_to_yieldBoostConfirmFragment, - args = YieldBoostConfirmFragment.getBundle(payload) - ) - - override fun openAddStakingProxy() { - performNavigation(R.id.action_open_addStakingProxyFragment) - } -} 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 deleted file mode 100644 index 1cf4a04603..0000000000 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/staking/relaychain/RelayStakingNavigator.kt +++ /dev/null @@ -1,249 +0,0 @@ -package io.novafoundation.nova.app.root.navigation.staking.relaychain - -import io.novafoundation.nova.app.R -import io.novafoundation.nova.app.root.navigation.BaseNavigator -import io.novafoundation.nova.app.root.navigation.NavigationHolder -import io.novafoundation.nova.app.root.navigation.Navigator -import io.novafoundation.nova.feature_dapp_impl.presentation.browser.main.DAppBrowserFragment -import io.novafoundation.nova.feature_staking_impl.domain.staking.redeem.RedeemConsequences -import io.novafoundation.nova.feature_staking_impl.presentation.StakingDashboardRouter -import io.novafoundation.nova.feature_staking_impl.presentation.StakingRouter -import io.novafoundation.nova.feature_staking_impl.presentation.payouts.confirm.ConfirmPayoutFragment -import io.novafoundation.nova.feature_staking_impl.presentation.payouts.confirm.model.ConfirmPayoutPayload -import io.novafoundation.nova.feature_staking_impl.presentation.payouts.detail.PayoutDetailsFragment -import io.novafoundation.nova.feature_staking_impl.presentation.payouts.model.PendingPayoutParcelable -import io.novafoundation.nova.feature_staking_impl.presentation.pools.common.SelectingPoolPayload -import io.novafoundation.nova.feature_staking_impl.presentation.pools.searchPool.SearchPoolFragment -import io.novafoundation.nova.feature_staking_impl.presentation.pools.selectPool.SelectPoolFragment -import io.novafoundation.nova.feature_staking_impl.presentation.staking.bond.confirm.ConfirmBondMoreFragment -import io.novafoundation.nova.feature_staking_impl.presentation.staking.bond.confirm.ConfirmBondMorePayload -import io.novafoundation.nova.feature_staking_impl.presentation.staking.bond.select.SelectBondMoreFragment -import io.novafoundation.nova.feature_staking_impl.presentation.staking.bond.select.SelectBondMorePayload -import io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.controller.confirm.ConfirmSetControllerFragment -import io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.controller.confirm.ConfirmSetControllerPayload -import io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.add.confirm.ConfirmAddStakingProxyFragment -import io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.add.confirm.ConfirmAddStakingProxyPayload -import io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.revoke.ConfirmRemoveStakingProxyFragment -import io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.revoke.ConfirmRemoveStakingProxyPayload -import io.novafoundation.nova.feature_staking_impl.presentation.staking.main.model.StakingStoryModel -import io.novafoundation.nova.feature_staking_impl.presentation.staking.rebond.confirm.ConfirmRebondFragment -import io.novafoundation.nova.feature_staking_impl.presentation.staking.rebond.confirm.ConfirmRebondPayload -import io.novafoundation.nova.feature_staking_impl.presentation.staking.redeem.RedeemFragment -import io.novafoundation.nova.feature_staking_impl.presentation.staking.redeem.RedeemPayload -import io.novafoundation.nova.feature_staking_impl.presentation.staking.rewardDestination.confirm.ConfirmRewardDestinationFragment -import io.novafoundation.nova.feature_staking_impl.presentation.staking.rewardDestination.confirm.parcel.ConfirmRewardDestinationPayload -import io.novafoundation.nova.feature_staking_impl.presentation.staking.unbond.confirm.ConfirmUnbondFragment -import io.novafoundation.nova.feature_staking_impl.presentation.staking.unbond.confirm.ConfirmUnbondPayload -import io.novafoundation.nova.feature_staking_impl.presentation.story.StoryFragment -import io.novafoundation.nova.feature_staking_impl.presentation.validators.change.custom.common.CustomValidatorsPayload -import io.novafoundation.nova.feature_staking_impl.presentation.validators.change.custom.common.CustomValidatorsPayload.FlowType -import io.novafoundation.nova.feature_staking_impl.presentation.validators.change.custom.review.ReviewCustomValidatorsFragment -import io.novafoundation.nova.feature_staking_impl.presentation.validators.change.custom.select.SelectCustomValidatorsFragment -import io.novafoundation.nova.feature_staking_impl.presentation.validators.details.StakeTargetDetailsPayload -import io.novafoundation.nova.feature_staking_impl.presentation.validators.details.ValidatorDetailsFragment - -class RelayStakingNavigator( - private val navigationHolder: NavigationHolder, - private val commonNavigator: Navigator, - private val stakingDashboardRouter: StakingDashboardRouter, -) : BaseNavigator(navigationHolder), StakingRouter { - - override fun returnToStakingMain() = performNavigation(R.id.back_to_staking_main) - - override fun openSwitchWallet() = commonNavigator.openSwitchWallet() - - override fun openWalletDetails(metaAccountId: Long) = commonNavigator.openWalletDetails(metaAccountId) - - override fun openCustomRebond() { - performNavigation(R.id.action_stakingFragment_to_customRebondFragment) - } - - override fun openCurrentValidators() { - performNavigation(R.id.action_stakingFragment_to_currentValidatorsFragment) - } - - override fun returnToCurrentValidators() { - performNavigation(R.id.action_confirmStakingFragment_back_to_currentValidatorsFragment) - } - - override fun openChangeRewardDestination() { - performNavigation(R.id.action_stakingFragment_to_selectRewardDestinationFragment) - } - - override fun openConfirmRewardDestination(payload: ConfirmRewardDestinationPayload) { - performNavigation( - R.id.action_selectRewardDestinationFragment_to_confirmRewardDestinationFragment, - ConfirmRewardDestinationFragment.getBundle(payload) - ) - } - - override fun openControllerAccount() { - performNavigation(R.id.action_stakingBalanceFragment_to_setControllerAccountFragment) - } - - override fun openConfirmSetController(payload: ConfirmSetControllerPayload) { - performNavigation( - R.id.action_stakingSetControllerAccountFragment_to_confirmSetControllerAccountFragment, - ConfirmSetControllerFragment.getBundle(payload) - ) - } - - override fun openRecommendedValidators() { - performNavigation(R.id.action_startChangeValidatorsFragment_to_recommendedValidatorsFragment) - } - - override fun openSelectCustomValidators() { - val flowType = when (navigationHolder.navController?.currentDestination?.id) { - R.id.setupStakingType -> FlowType.SETUP_STAKING_VALIDATORS - else -> FlowType.CHANGE_STAKING_VALIDATORS - } - val payload = CustomValidatorsPayload(flowType) - - performNavigation( - cases = arrayOf( - R.id.setupStakingType to R.id.action_setupStakingType_to_selectCustomValidatorsFragment, - R.id.startChangeValidatorsFragment to R.id.action_startChangeValidatorsFragment_to_selectCustomValidatorsFragment, - ), - args = SelectCustomValidatorsFragment.getBundle(payload) - ) - } - - override fun openCustomValidatorsSettings() { - performNavigation(R.id.action_selectCustomValidatorsFragment_to_settingsCustomValidatorsFragment) - } - - override fun openSearchCustomValidators() { - performNavigation(R.id.action_selectCustomValidatorsFragment_to_searchCustomValidatorsFragment) - } - - override fun openReviewCustomValidators(payload: CustomValidatorsPayload) { - performNavigation( - R.id.action_selectCustomValidatorsFragment_to_reviewCustomValidatorsFragment, - args = ReviewCustomValidatorsFragment.getBundle(payload) - ) - } - - override fun openConfirmStaking() { - performNavigation(R.id.openConfirmStakingFragment) - } - - override fun openConfirmNominations() { - performNavigation(R.id.action_confirmStakingFragment_to_confirmNominationsFragment) - } - - override fun openChainStakingMain() = performNavigation(R.id.action_mainFragment_to_stakingGraph) - - override fun openStartChangeValidators() { - performNavigation(R.id.openStartChangeValidatorsFragment) - } - - override fun openStory(story: StakingStoryModel) { - performNavigation(R.id.open_staking_story, StoryFragment.getBundle(story)) - } - - override fun openPayouts() { - performNavigation(R.id.action_stakingFragment_to_payoutsListFragment) - } - - override fun openPayoutDetails(payout: PendingPayoutParcelable) { - performNavigation(R.id.action_payoutsListFragment_to_payoutDetailsFragment, PayoutDetailsFragment.getBundle(payout)) - } - - override fun openConfirmPayout(payload: ConfirmPayoutPayload) { - performNavigation(R.id.action_open_confirm_payout, ConfirmPayoutFragment.getBundle(payload)) - } - - override fun openBondMore() { - performNavigation(R.id.action_open_selectBondMoreFragment, SelectBondMoreFragment.getBundle(SelectBondMorePayload())) - } - - override fun openConfirmBondMore(payload: ConfirmBondMorePayload) { - performNavigation(R.id.action_selectBondMoreFragment_to_confirmBondMoreFragment, ConfirmBondMoreFragment.getBundle(payload)) - } - - override fun openSelectUnbond() { - performNavigation(R.id.action_stakingFragment_to_selectUnbondFragment) - } - - override fun openConfirmUnbond(payload: ConfirmUnbondPayload) { - performNavigation(R.id.action_selectUnbondFragment_to_confirmUnbondFragment, ConfirmUnbondFragment.getBundle(payload)) - } - - override fun openRedeem() { - performNavigation(R.id.action_open_redeemFragment, RedeemFragment.getBundle(RedeemPayload())) - } - - override fun openConfirmRebond(payload: ConfirmRebondPayload) { - performNavigation(R.id.action_open_confirm_rebond, ConfirmRebondFragment.getBundle(payload)) - } - - override fun openValidatorDetails(payload: StakeTargetDetailsPayload) { - performNavigation(R.id.open_validator_details, ValidatorDetailsFragment.getBundle(payload)) - } - - override fun openRebag() = performNavigation(R.id.action_stakingFragment_to_rebag) - - override fun openDAppBrowser(url: String) = performNavigation( - actionId = R.id.action_dappBrowserGraph, - args = DAppBrowserFragment.getBundle(url) - ) - - override fun openStakingPeriods() { - performNavigation(R.id.action_stakingFragment_to_staking_periods) - } - - override fun openSetupStakingType() { - performNavigation(R.id.action_setupAmountMultiStakingFragment_to_setupStakingType) - } - - override fun openSelectPool(payload: SelectingPoolPayload) { - val arguments = SelectPoolFragment.getBundle(payload) - performNavigation(R.id.action_setupStakingType_to_selectCustomPoolFragment, args = arguments) - } - - override fun openSearchPool(payload: SelectingPoolPayload) { - val arguments = SearchPoolFragment.getBundle(payload) - performNavigation(R.id.action_selectPool_to_searchPoolFragment, args = arguments) - } - - override fun finishSetupValidatorsFlow() { - performNavigation(R.id.action_back_to_setupAmountMultiStakingFragment) - } - - override fun finishSetupPoolFlow() { - performNavigation( - cases = arrayOf( - R.id.searchPoolFragment to R.id.action_searchPool_to_setupAmountMultiStakingFragment, - R.id.selectPoolFragment to R.id.action_selectPool_to_setupAmountMultiStakingFragment, - ) - ) - } - - override fun finishRedeemFlow(redeemConsequences: RedeemConsequences) { - if (redeemConsequences.willKillStash) { - stakingDashboardRouter.returnToStakingDashboard() - } else { - returnToStakingMain() - } - } - - override fun openAddStakingProxy() { - performNavigation(R.id.action_open_addStakingProxyFragment) - } - - override fun openConfirmAddStakingProxy(payload: ConfirmAddStakingProxyPayload) { - performNavigation( - R.id.action_addStakingProxyFragment_to_confirmAddStakingProxyFragment, - ConfirmAddStakingProxyFragment.getBundle(payload) - ) - } - - override fun openStakingProxyList() { - performNavigation(R.id.action_open_stakingProxyList) - } - - override fun openConfirmRemoveStakingProxy(payload: ConfirmRemoveStakingProxyPayload) { - val arguments = ConfirmRemoveStakingProxyFragment.getBundle(payload) - performNavigation(R.id.action_open_confirmRemoveStakingProxyFragment, arguments) - } -} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/swap/SwapNavigator.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/swap/SwapNavigator.kt deleted file mode 100644 index 4c37b61c92..0000000000 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/swap/SwapNavigator.kt +++ /dev/null @@ -1,61 +0,0 @@ -package io.novafoundation.nova.app.root.navigation.swap - -import io.novafoundation.nova.app.R -import io.novafoundation.nova.app.root.navigation.BaseNavigator -import io.novafoundation.nova.app.root.navigation.NavigationHolder -import io.novafoundation.nova.app.root.navigation.Navigator -import io.novafoundation.nova.feature_assets.presentation.balance.detail.BalanceDetailFragment -import io.novafoundation.nova.feature_assets.presentation.send.amount.SendPayload -import io.novafoundation.nova.feature_assets.presentation.swap.asset.AssetSwapFlowFragment -import io.novafoundation.nova.feature_assets.presentation.swap.asset.SwapFlowPayload -import io.novafoundation.nova.feature_swap_api.presentation.model.SwapSettingsPayload -import io.novafoundation.nova.feature_swap_impl.presentation.SwapRouter -import io.novafoundation.nova.feature_swap_impl.presentation.main.SwapMainSettingsFragment -import io.novafoundation.nova.feature_wallet_api.presentation.model.AssetPayload - -class SwapNavigator( - private val navigationHolder: NavigationHolder, - private val commonDelegate: Navigator -) : BaseNavigator(navigationHolder), SwapRouter { - - override fun openSwapConfirmation() = performNavigation(R.id.action_swapMainSettingsFragment_to_swapConfirmationFragment) - - override fun openSwapRoute() = performNavigation(R.id.action_open_swapRouteFragment) - - override fun openSwapFee() = performNavigation(R.id.action_open_swapFeeFragment) - - override fun openSwapExecution() = performNavigation(R.id.action_swapConfirmationFragment_to_swapExecutionFragment) - - override fun openSwapOptions() { - navigationHolder.navController?.navigate(R.id.action_swapMainSettingsFragment_to_swapOptionsFragment) - } - - override fun openRetrySwap(payload: SwapSettingsPayload) = performNavigation( - actionId = R.id.action_swapExecutionFragment_to_swapSettingsFragment, - args = SwapMainSettingsFragment.getBundle(payload) - ) - - override fun openBalanceDetails(assetPayload: AssetPayload) { - navigationHolder.navController?.navigate(R.id.action_swapExecutionFragment_to_assetDetails, BalanceDetailFragment.getBundle(assetPayload)) - } - - override fun selectAssetIn(selectedAsset: AssetPayload?) { - val payload = SwapFlowPayload.ReselectAssetIn(selectedAsset) - navigationHolder.navController?.navigate(R.id.action_swapSettingsFragment_to_select_swap_token_graph, AssetSwapFlowFragment.getBundle(payload)) - } - - override fun selectAssetOut(selectedAsset: AssetPayload?) { - val payload = SwapFlowPayload.ReselectAssetOut(selectedAsset) - navigationHolder.navController?.navigate(R.id.action_swapSettingsFragment_to_select_swap_token_graph, AssetSwapFlowFragment.getBundle(payload)) - } - - override fun openSendCrossChain(destination: AssetPayload, recipientAddress: String?) { - val payload = SendPayload.SpecifiedDestination(destination) - - commonDelegate.openSend(payload, recipientAddress) - } - - override fun openReceive(assetPayload: AssetPayload) { - commonDelegate.openReceive(assetPayload) - } -} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/versions/VersionsNavigator.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/versions/VersionsNavigator.kt deleted file mode 100644 index 3238faa013..0000000000 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/versions/VersionsNavigator.kt +++ /dev/null @@ -1,20 +0,0 @@ -package io.novafoundation.nova.app.root.navigation.versions - -import io.novafoundation.nova.app.R -import io.novafoundation.nova.app.root.navigation.NavigationHolder -import io.novafoundation.nova.common.utils.showBrowser -import io.novafoundation.nova.feature_versions_api.presentation.VersionsRouter - -class VersionsNavigator( - private val navigationHolder: NavigationHolder, - private val updateSourceLink: String -) : VersionsRouter { - - override fun openAppUpdater() { - navigationHolder.contextManager.getActivity()?.showBrowser(updateSourceLink, R.string.common_cannot_find_app) - } - - override fun back() { - navigationHolder.navController?.popBackStack() - } -} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/wallet/CurrencyNavigator.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/wallet/CurrencyNavigator.kt deleted file mode 100644 index d02f1551d4..0000000000 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/wallet/CurrencyNavigator.kt +++ /dev/null @@ -1,13 +0,0 @@ -package io.novafoundation.nova.app.root.navigation.wallet - -import io.novafoundation.nova.app.root.navigation.BaseNavigator -import io.novafoundation.nova.app.root.navigation.NavigationHolder -import io.novafoundation.nova.app.root.presentation.RootRouter -import io.novafoundation.nova.feature_currency_api.presentation.CurrencyRouter - -class CurrencyNavigator(val rootRouter: RootRouter, navigationHolder: NavigationHolder) : BaseNavigator(navigationHolder), CurrencyRouter { - - override fun returnToWallet() { - rootRouter.returnToWallet() - } -} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/walletConnect/WalletConnectNavigator.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/walletConnect/WalletConnectNavigator.kt deleted file mode 100644 index e4f9cd4b1f..0000000000 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/walletConnect/WalletConnectNavigator.kt +++ /dev/null @@ -1,26 +0,0 @@ -package io.novafoundation.nova.app.root.navigation.walletConnect - -import io.novafoundation.nova.app.R -import io.novafoundation.nova.app.root.navigation.BaseNavigator -import io.novafoundation.nova.app.root.navigation.NavigationHolder -import io.novafoundation.nova.feature_wallet_connect_impl.WalletConnectRouter -import io.novafoundation.nova.feature_wallet_connect_impl.presentation.sessions.details.WalletConnectSessionDetailsFragment -import io.novafoundation.nova.feature_wallet_connect_impl.presentation.sessions.details.WalletConnectSessionDetailsPayload -import io.novafoundation.nova.feature_wallet_connect_impl.presentation.sessions.list.WalletConnectSessionsFragment -import io.novafoundation.nova.feature_wallet_connect_impl.presentation.sessions.list.WalletConnectSessionsPayload - -class WalletConnectNavigator(navigationHolder: NavigationHolder) : BaseNavigator(navigationHolder), WalletConnectRouter { - override fun openSessionDetails(payload: WalletConnectSessionDetailsPayload) = performNavigation( - actionId = R.id.action_walletConnectSessionsFragment_to_walletConnectSessionDetailsFragment, - args = WalletConnectSessionDetailsFragment.getBundle(payload) - ) - - override fun openScanPairingQrCode() = performNavigation(R.id.action_open_scanWalletConnect) - - override fun backToSettings() = performNavigation(R.id.walletConnectSessionDetailsFragment_to_settings) - - override fun openWalletConnectSessions(payload: WalletConnectSessionsPayload) = performNavigation( - actionId = R.id.action_mainFragment_to_walletConnectGraph, - args = WalletConnectSessionsFragment.getBundle(payload) - ) -} 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 dc4abfbdb1..8b54a4de3f 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 @@ -7,7 +7,7 @@ import androidx.navigation.fragment.NavHostFragment import io.novafoundation.nova.app.R import io.novafoundation.nova.app.root.di.RootApi import io.novafoundation.nova.app.root.di.RootComponent -import io.novafoundation.nova.app.root.navigation.NavigationHolder +import io.novafoundation.nova.app.root.navigation.holders.RootNavigationHolder import io.novafoundation.nova.common.base.BaseActivity import io.novafoundation.nova.common.di.FeatureUtils import io.novafoundation.nova.common.resources.ContextManager @@ -24,7 +24,7 @@ import javax.inject.Inject class RootActivity : BaseActivity(), SplashBackgroundHolder { @Inject - lateinit var navigationHolder: NavigationHolder + lateinit var rootNavigationHolder: RootNavigationHolder @Inject lateinit var systemCallExecutor: SystemCallExecutor @@ -55,7 +55,8 @@ class RootActivity : BaseActivity(), SplashBackgroundHolder { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - navigationHolder.attach(navController) + rootNavigationHolder.attach(rootNavController) + contextManager.attachActivity(this) rootNetworkBar.setOnApplyWindowInsetsListener { view, insets -> @@ -74,7 +75,7 @@ class RootActivity : BaseActivity(), SplashBackgroundHolder { super.onDestroy() contextManager.detachActivity() - navigationHolder.detach() + rootNavigationHolder.detach() } override fun layoutResource(): Int { @@ -143,9 +144,8 @@ class RootActivity : BaseActivity(), SplashBackgroundHolder { // } // } - private val navController: NavController by lazy { - val navHostFragment = - supportFragmentManager.findFragmentById(R.id.navHost) as NavHostFragment + private val rootNavController: NavController by lazy { + val navHostFragment = supportFragmentManager.findFragmentById(R.id.rootNavHost) as NavHostFragment navHostFragment.navController } 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 72c15e26c5..b41f20685a 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 @@ -49,7 +49,7 @@ class RootViewModel( private val compoundRequestBusHandler: CompoundRequestBusHandler, private val pushNotificationsInteractor: PushNotificationsInteractor, private val externalServiceInitializer: ExternalServiceInitializer, - private val actionBottomSheetLauncher: ActionBottomSheetLauncher, + private val actionBottomSheetLauncher: ActionBottomSheetLauncher ) : BaseViewModel(), NetworkStateUi by networkStateMixin, ActionBottomSheetLauncher by actionBottomSheetLauncher { 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 1f45f1fc9f..9eecc413db 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 @@ -13,7 +13,7 @@ import androidx.navigation.ui.setupWithNavController import io.novafoundation.nova.app.R import io.novafoundation.nova.app.root.di.RootApi import io.novafoundation.nova.app.root.di.RootComponent -import io.novafoundation.nova.app.root.navigation.staking.StakingDashboardNavigator +import io.novafoundation.nova.app.root.navigation.navigators.staking.StakingDashboardNavigator import io.novafoundation.nova.common.base.BaseFragment import io.novafoundation.nova.common.di.FeatureUtils import io.novafoundation.nova.common.utils.blur.SweetBlur diff --git a/app/src/main/java/io/novafoundation/nova/app/root/presentation/splitScreen/SplitScreenFragment.kt b/app/src/main/java/io/novafoundation/nova/app/root/presentation/splitScreen/SplitScreenFragment.kt new file mode 100644 index 0000000000..acd69c525a --- /dev/null +++ b/app/src/main/java/io/novafoundation/nova/app/root/presentation/splitScreen/SplitScreenFragment.kt @@ -0,0 +1,138 @@ +package io.novafoundation.nova.app.root.presentation.splitScreen + +import android.graphics.Rect +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.graphics.Insets +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.isVisible +import androidx.core.view.marginTop +import androidx.navigation.NavController +import androidx.navigation.fragment.NavHostFragment +import coil.ImageLoader +import io.novafoundation.nova.app.R +import io.novafoundation.nova.app.root.di.RootApi +import io.novafoundation.nova.app.root.di.RootComponent +import io.novafoundation.nova.app.root.navigation.holders.SplitScreenNavigationHolder +import io.novafoundation.nova.common.base.BaseFragment +import io.novafoundation.nova.common.di.FeatureUtils +import io.novafoundation.nova.common.utils.FragmentPayloadCreator +import io.novafoundation.nova.common.utils.PayloadCreator +import io.novafoundation.nova.common.utils.RoundCornersOutlineProvider +import io.novafoundation.nova.common.utils.images.setIcon +import io.novafoundation.nova.common.utils.letOrHide +import io.novafoundation.nova.common.utils.payloadOrElse +import io.novafoundation.nova.feature_dapp_impl.presentation.tab.setupCloseAllDappTabsDialogue +import javax.inject.Inject +import kotlinx.android.synthetic.main.fragment_split_screen.dappEntryPoint +import kotlinx.android.synthetic.main.fragment_split_screen.dappEntryPointClose +import kotlinx.android.synthetic.main.fragment_split_screen.dappEntryPointIcon +import kotlinx.android.synthetic.main.fragment_split_screen.dappEntryPointText +import kotlinx.android.synthetic.main.fragment_split_screen.mainNavHost + +class SplitScreenFragment : BaseFragment() { + + companion object : PayloadCreator by FragmentPayloadCreator() + + @Inject + lateinit var splitScreenNavigationHolder: SplitScreenNavigationHolder + + @Inject + lateinit var imageLoader: ImageLoader + + private val mainNavController: NavController by lazy { + val navHostFragment = childFragmentManager.findFragmentById(R.id.mainNavHost) as NavHostFragment + + navHostFragment.navController + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.fragment_split_screen, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + splitScreenNavigationHolder.attach(mainNavController) + + viewModel.onNavigationAttached() + } + + override fun onDestroyView() { + splitScreenNavigationHolder.detachNavController(mainNavController) + + super.onDestroyView() + } + + override fun inject() { + FeatureUtils.getFeature(this, RootApi::class.java) + .splitScreenFragmentComponentFactory() + .create(this, payloadOrElse { SplitScreenPayload.NoNavigation }) + .inject(this) + } + + override fun initViews() { + val outlineMargin = Rect(0, (-12).dp, 0, 0) // To avoid round corners at top + mainNavHost.outlineProvider = RoundCornersOutlineProvider(12.dpF, margin = outlineMargin) + + dappEntryPoint.setOnClickListener { viewModel.onTabsClicked() } + dappEntryPointClose.setOnClickListener { viewModel.onTabsCloseClicked() } + } + + override fun subscribe(viewModel: SplitScreenViewModel) { + setupCloseAllDappTabsDialogue(viewModel.closeAllTabsConfirmation) + + viewModel.dappTabsVisible.observe { shouldBeVisible -> + mainNavHost.clipToOutline = shouldBeVisible + dappEntryPoint.isVisible = shouldBeVisible + } + + viewModel.tabsTitle.observe { model -> + dappEntryPointIcon.letOrHide(model.icon) { + dappEntryPointIcon.setIcon(it, imageLoader) + } + dappEntryPointText.text = model.title + } + manageImeInsets() + } + + /** + * Since we have a dAppEntryPoint we must change ime insets for main container and its children + * to avoid extra bottom space when keyboard is shown + */ + private fun manageImeInsets() { + // We change this value when dappEntryPoint is shown/hidden + var dappEntryPointHeight = 0 + + // Inset listener that provides a custom insets to its children + ViewCompat.setOnApplyWindowInsetsListener(mainNavHost) { _, insets -> + val imeInsets = insets.getInsets(WindowInsetsCompat.Type.ime()) + + val changedImeInsets = Insets.of( + imeInsets.left, + imeInsets.top, + imeInsets.right, + (imeInsets.bottom - dappEntryPointHeight).coerceAtLeast(0) + ) + + WindowInsetsCompat.Builder(insets) + .setInsets(WindowInsetsCompat.Type.ime(), changedImeInsets) + .build() + } + + // Subscribe to get dAppEntryPoint height + viewModel.dappTabsVisible.observe { + dappEntryPoint.post { + dappEntryPointHeight = if (dappEntryPoint.isVisible) { + dappEntryPoint.height + dappEntryPoint.marginTop + } else { + 0 + } + ViewCompat.requestApplyInsets(mainNavHost) // Request new insets to trigger inset listener + } + } + } +} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/presentation/splitScreen/SplitScreenPayload.kt b/app/src/main/java/io/novafoundation/nova/app/root/presentation/splitScreen/SplitScreenPayload.kt new file mode 100644 index 0000000000..9a8e8577d2 --- /dev/null +++ b/app/src/main/java/io/novafoundation/nova/app/root/presentation/splitScreen/SplitScreenPayload.kt @@ -0,0 +1,16 @@ +package io.novafoundation.nova.app.root.presentation.splitScreen + +import android.os.Parcelable +import io.novafoundation.nova.common.navigation.DelayedNavigation +import kotlinx.android.parcel.Parcelize + +sealed interface SplitScreenPayload : Parcelable { + + @Parcelize + object NoNavigation : SplitScreenPayload + + @Parcelize + class InstantNavigationOnAttach( + val delayedNavigation: DelayedNavigation + ) : SplitScreenPayload +} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/presentation/splitScreen/SplitScreenViewModel.kt b/app/src/main/java/io/novafoundation/nova/app/root/presentation/splitScreen/SplitScreenViewModel.kt new file mode 100644 index 0000000000..db43a8947e --- /dev/null +++ b/app/src/main/java/io/novafoundation/nova/app/root/presentation/splitScreen/SplitScreenViewModel.kt @@ -0,0 +1,98 @@ +package io.novafoundation.nova.app.root.presentation.splitScreen + +import io.novafoundation.nova.app.R +import io.novafoundation.nova.app.root.domain.SplitScreenInteractor +import io.novafoundation.nova.common.base.BaseViewModel +import io.novafoundation.nova.common.mixin.actionAwaitable.ActionAwaitableMixin +import io.novafoundation.nova.common.mixin.actionAwaitable.awaitAction +import io.novafoundation.nova.common.mixin.actionAwaitable.confirmingAction +import io.novafoundation.nova.common.navigation.DelayedNavigationRouter +import io.novafoundation.nova.common.resources.ResourceManager +import io.novafoundation.nova.common.utils.Consumer +import io.novafoundation.nova.common.utils.images.Icon +import io.novafoundation.nova.common.utils.images.asFileIcon +import io.novafoundation.nova.common.utils.images.asUrlIcon +import io.novafoundation.nova.feature_dapp_api.data.model.SimpleTabModel +import io.novafoundation.nova.feature_dapp_impl.presentation.DAppRouter +import io.novafoundation.nova.feature_dapp_api.presentation.browser.main.DAppBrowserPayload +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch + +data class TabsTitleModel( + val title: String, + val icon: Icon? +) + +class SplitScreenViewModel( + private val interactor: SplitScreenInteractor, + private val router: DAppRouter, + private val delayedNavigationRouter: DelayedNavigationRouter, + private val actionAwaitableMixinFactory: ActionAwaitableMixin.Factory, + private val resourceManager: ResourceManager, + private val payload: SplitScreenPayload +) : BaseViewModel() { + + private val consumablePayload = Consumer(payload) + + val closeAllTabsConfirmation = actionAwaitableMixinFactory.confirmingAction() + + private val tabsFlow = interactor.observeTabNamesById() + .shareInBackground() + + val tabsTitle = tabsFlow.map { tabs -> + if (tabs.size == 1) { + singleTabTitle(tabs.single()) + } else { + tabSizeTitle(tabs.size) + } + }.distinctUntilChanged() + + val dappTabsVisible = tabsFlow.map { it.isNotEmpty() } + .distinctUntilChanged() + + fun onTabsClicked() = launch { + val tabs = tabsFlow.first() + + if (tabs.size == 1) { + val payload = DAppBrowserPayload.Tab(tabs.single().tabId) + router.openDAppBrowser(payload) + } else { + router.openTabs() + } + } + + fun onTabsCloseClicked() = launch { + closeAllTabsConfirmation.awaitAction() + + interactor.removeAllTabs() + } + + private fun singleTabTitle(tab: SimpleTabModel): TabsTitleModel { + return tab.title?.let { + TabsTitleModel(it, tab.knownDAppIconUrl?.asUrlIcon() ?: tab.faviconPath?.asFileIcon()) + } ?: tabSizeTitle(1) + } + + private fun tabSizeTitle(size: Int): TabsTitleModel { + return TabsTitleModel( + resourceManager.getString(R.string.dapp_entry_point_title, size), + null + ) + } + + fun onNavigationAttached() { + consumablePayload.useOnce { + when (it) { + is SplitScreenPayload.InstantNavigationOnAttach -> { + delayedNavigationRouter.runDelayedNavigation(it.delayedNavigation) + } + + SplitScreenPayload.NoNavigation, + null -> { + } // Do nothing + } + } + } +} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/presentation/splitScreen/di/SplitScreenFragmentComponent.kt b/app/src/main/java/io/novafoundation/nova/app/root/presentation/splitScreen/di/SplitScreenFragmentComponent.kt new file mode 100644 index 0000000000..c7f490c135 --- /dev/null +++ b/app/src/main/java/io/novafoundation/nova/app/root/presentation/splitScreen/di/SplitScreenFragmentComponent.kt @@ -0,0 +1,28 @@ +package io.novafoundation.nova.app.root.presentation.splitScreen.di + +import androidx.fragment.app.Fragment +import dagger.BindsInstance +import dagger.Subcomponent +import io.novafoundation.nova.app.root.presentation.splitScreen.SplitScreenFragment +import io.novafoundation.nova.app.root.presentation.splitScreen.SplitScreenPayload +import io.novafoundation.nova.common.di.scope.ScreenScope + +@Subcomponent( + modules = [ + SplitScreenFragmentModule::class + ] +) +@ScreenScope +interface SplitScreenFragmentComponent { + + @Subcomponent.Factory + interface Factory { + + fun create( + @BindsInstance fragment: Fragment, + @BindsInstance payload: SplitScreenPayload + ): SplitScreenFragmentComponent + } + + fun inject(fragment: SplitScreenFragment) +} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/presentation/splitScreen/di/SplitScreenFragmentModule.kt b/app/src/main/java/io/novafoundation/nova/app/root/presentation/splitScreen/di/SplitScreenFragmentModule.kt new file mode 100644 index 0000000000..e44dd9682a --- /dev/null +++ b/app/src/main/java/io/novafoundation/nova/app/root/presentation/splitScreen/di/SplitScreenFragmentModule.kt @@ -0,0 +1,57 @@ +package io.novafoundation.nova.app.root.presentation.splitScreen.di + +import androidx.fragment.app.Fragment +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import dagger.Module +import dagger.Provides +import dagger.multibindings.IntoMap +import io.novafoundation.nova.app.root.domain.SplitScreenInteractor +import io.novafoundation.nova.app.root.presentation.splitScreen.SplitScreenPayload +import io.novafoundation.nova.app.root.presentation.splitScreen.SplitScreenViewModel +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.navigation.DelayedNavigationRouter +import io.novafoundation.nova.common.resources.ResourceManager +import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository +import io.novafoundation.nova.feature_dapp_api.data.repository.BrowserTabExternalRepository +import io.novafoundation.nova.feature_dapp_impl.presentation.DAppRouter + +@Module( + includes = [ + ViewModelModule::class + ] +) +class SplitScreenFragmentModule { + + @Provides + fun provideInteractor( + repository: BrowserTabExternalRepository, + accountRepository: AccountRepository + ): SplitScreenInteractor { + return SplitScreenInteractor(repository, accountRepository) + } + + @Provides + @IntoMap + @ViewModelKey(SplitScreenViewModel::class) + fun provideViewModel( + interactor: SplitScreenInteractor, + dAppRouter: DAppRouter, + delayedNavigationRouter: DelayedNavigationRouter, + actionAwaitableMixinFactory: ActionAwaitableMixin.Factory, + resourceManager: ResourceManager, + payload: SplitScreenPayload + ): ViewModel { + return SplitScreenViewModel(interactor, dAppRouter, delayedNavigationRouter, actionAwaitableMixinFactory, resourceManager, payload) + } + + @Provides + fun provideViewModelCreator( + fragment: Fragment, + viewModelFactory: ViewModelProvider.Factory + ): SplitScreenViewModel { + return ViewModelProvider(fragment, viewModelFactory).get(SplitScreenViewModel::class.java) + } +} diff --git a/app/src/main/res/layout/activity_root.xml b/app/src/main/res/layout/activity_root.xml index c85b778455..7ac67c6475 100644 --- a/app/src/main/res/layout/activity_root.xml +++ b/app/src/main/res/layout/activity_root.xml @@ -1,14 +1,13 @@ \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_split_screen.xml b/app/src/main/res/layout/fragment_split_screen.xml new file mode 100644 index 0000000000..2a6be6e067 --- /dev/null +++ b/app/src/main/res/layout/fragment_split_screen.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/dapp_browser_graph.xml b/app/src/main/res/navigation/dapp_browser_graph.xml index 5388845118..e24f460596 100644 --- a/app/src/main/res/navigation/dapp_browser_graph.xml +++ b/app/src/main/res/navigation/dapp_browser_graph.xml @@ -3,26 +3,16 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/dapp_browser_graph" - app:startDestination="@id/DAppBrowserFragment"> + app:startDestination="@id/dappBrowserFragment"> - - - - + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/dapp_search_graph.xml b/app/src/main/res/navigation/dapp_search_graph.xml index 40463b83eb..4204e481af 100644 --- a/app/src/main/res/navigation/dapp_search_graph.xml +++ b/app/src/main/res/navigation/dapp_search_graph.xml @@ -9,18 +9,6 @@ android:id="@+id/dappSearchFragment" android:name="io.novafoundation.nova.feature_dapp_impl.presentation.search.DappSearchFragment" android:label="DappSearchFragment" - tools:layout="@layout/fragment_search_dapp"> + tools:layout="@layout/fragment_search_dapp" /> - - - - \ No newline at end of file diff --git a/app/src/main/res/navigation/dapp_tabs_graph.xml b/app/src/main/res/navigation/dapp_tabs_graph.xml new file mode 100644 index 0000000000..e568a3323c --- /dev/null +++ b/app/src/main/res/navigation/dapp_tabs_graph.xml @@ -0,0 +1,19 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/import_parity_signer_graph.xml b/app/src/main/res/navigation/import_parity_signer_graph.xml index 36258abbfd..ca3661c3d1 100644 --- a/app/src/main/res/navigation/import_parity_signer_graph.xml +++ b/app/src/main/res/navigation/import_parity_signer_graph.xml @@ -1,54 +1,53 @@ + android:label="StartImportParitySignerFragment" + tools:layout="@layout/fragment_import_parity_signer_start"> + app:popExitAnim="@anim/fragment_close_exit" /> + android:label="ScanImportParitySignerFragment" + tools:layout="@layout/fragment_import_parity_signer_scan"> + app:popExitAnim="@anim/fragment_close_exit" /> - + android:label="PreviewImportParitySignerFragment" + tools:layout="@layout/fragment_chain_account_preview"> + app:popExitAnim="@anim/fragment_close_exit" /> diff --git a/app/src/main/res/navigation/referendum_details_graph.xml b/app/src/main/res/navigation/referendum_details_graph.xml index d72afa220b..a2f4eb2114 100644 --- a/app/src/main/res/navigation/referendum_details_graph.xml +++ b/app/src/main/res/navigation/referendum_details_graph.xml @@ -24,10 +24,10 @@ + app:popExitAnim="@anim/fragment_slide_out" /> - - + + + + + + + + + + + + + + + + + + + + - + tools:layout="@layout/fragment_update_notifications"> + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/main_nav_graph.xml b/app/src/main/res/navigation/split_screen_nav_graph.xml similarity index 96% rename from app/src/main/res/navigation/main_nav_graph.xml rename to app/src/main/res/navigation/split_screen_nav_graph.xml index 36bf78a30d..c78cdf527e 100644 --- a/app/src/main/res/navigation/main_nav_graph.xml +++ b/app/src/main/res/navigation/split_screen_nav_graph.xml @@ -2,9 +2,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + app:popUpTo="@id/split_screen_nav_graph" /> + + - + + - - - - @@ -966,8 +1025,7 @@ + android:label="WalletConnectApproveSessionFragment" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 637bbc5c48..3b059030ce 100644 --- a/build.gradle +++ b/build.gradle @@ -1,8 +1,8 @@ buildscript { ext { // App version - versionName = '9.1.2' - versionCode = 167 + versionName = '9.2.0' + versionCode = 168 applicationId = "io.novafoundation.nova" releaseApplicationSuffix = "market" diff --git a/common/src/main/java/io/novafoundation/nova/common/data/FileProviderImpl.kt b/common/src/main/java/io/novafoundation/nova/common/data/FileProviderImpl.kt index 2103760167..20198c3d3c 100644 --- a/common/src/main/java/io/novafoundation/nova/common/data/FileProviderImpl.kt +++ b/common/src/main/java/io/novafoundation/nova/common/data/FileProviderImpl.kt @@ -3,8 +3,6 @@ package io.novafoundation.nova.common.data import android.content.Context import android.net.Uri import io.novafoundation.nova.common.interfaces.FileProvider -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext import java.io.File import java.util.UUID import androidx.core.content.FileProvider as AndroidFileProvider @@ -13,32 +11,26 @@ class FileProviderImpl( private val context: Context ) : FileProvider { - override suspend fun getFileInExternalCacheStorage(fileName: String): File { - return withContext(Dispatchers.IO) { - val cacheDir = context.externalCacheDir?.absolutePath ?: directoryNotAvailable() + override fun getFileInExternalCacheStorage(fileName: String): File { + val cacheDir = context.externalCacheDir?.absolutePath ?: directoryNotAvailable() - File(cacheDir, fileName) - } + return File(cacheDir, fileName) } - override suspend fun getFileInInternalCacheStorage(fileName: String): File { - return withContext(Dispatchers.IO) { - val cacheDir = context.cacheDir?.absolutePath ?: directoryNotAvailable() + override fun getFileInInternalCacheStorage(fileName: String): File { + val cacheDir = context.cacheDir?.absolutePath ?: directoryNotAvailable() - File(cacheDir, fileName) - } + return File(cacheDir, fileName) } - override suspend fun generateTempFile(fixedName: String?): File { + override fun generateTempFile(fixedName: String?): File { val name = fixedName ?: UUID.randomUUID().toString() return getFileInExternalCacheStorage(name) } - override suspend fun uriOf(file: File): Uri { - return withContext(Dispatchers.IO) { - AndroidFileProvider.getUriForFile(context, "${context.packageName}.provider", file) - } + override fun uriOf(file: File): Uri { + return AndroidFileProvider.getUriForFile(context, "${context.packageName}.provider", file) } private fun directoryNotAvailable(): Nothing { diff --git a/common/src/main/java/io/novafoundation/nova/common/interfaces/FileProvider.kt b/common/src/main/java/io/novafoundation/nova/common/interfaces/FileProvider.kt index 8f4dd596f1..2b8b390e1d 100644 --- a/common/src/main/java/io/novafoundation/nova/common/interfaces/FileProvider.kt +++ b/common/src/main/java/io/novafoundation/nova/common/interfaces/FileProvider.kt @@ -5,11 +5,11 @@ import java.io.File interface FileProvider { - suspend fun getFileInExternalCacheStorage(fileName: String): File + fun getFileInExternalCacheStorage(fileName: String): File - suspend fun getFileInInternalCacheStorage(fileName: String): File + fun getFileInInternalCacheStorage(fileName: String): File - suspend fun generateTempFile(fixedName: String? = null): File + fun generateTempFile(fixedName: String? = null): File - suspend fun uriOf(file: File): Uri + fun uriOf(file: File): Uri } diff --git a/common/src/main/java/io/novafoundation/nova/common/navigation/DelayedNavigation.kt b/common/src/main/java/io/novafoundation/nova/common/navigation/DelayedNavigation.kt new file mode 100644 index 0000000000..5a36928737 --- /dev/null +++ b/common/src/main/java/io/novafoundation/nova/common/navigation/DelayedNavigation.kt @@ -0,0 +1,5 @@ +package io.novafoundation.nova.common.navigation + +import android.os.Parcelable + +interface DelayedNavigation : Parcelable diff --git a/common/src/main/java/io/novafoundation/nova/common/navigation/DelayedNavigationRouter.kt b/common/src/main/java/io/novafoundation/nova/common/navigation/DelayedNavigationRouter.kt new file mode 100644 index 0000000000..062ff07875 --- /dev/null +++ b/common/src/main/java/io/novafoundation/nova/common/navigation/DelayedNavigationRouter.kt @@ -0,0 +1,6 @@ +package io.novafoundation.nova.common.navigation + +interface DelayedNavigationRouter { + + fun runDelayedNavigation(delayedNavigation: DelayedNavigation) +} diff --git a/common/src/main/java/io/novafoundation/nova/common/navigation/SecureRouter.kt b/common/src/main/java/io/novafoundation/nova/common/navigation/SecureRouter.kt index 8dac7f8df6..29e38f030c 100644 --- a/common/src/main/java/io/novafoundation/nova/common/navigation/SecureRouter.kt +++ b/common/src/main/java/io/novafoundation/nova/common/navigation/SecureRouter.kt @@ -1,12 +1,8 @@ package io.novafoundation.nova.common.navigation -import android.os.Parcelable - @Retention(AnnotationRetention.SOURCE) annotation class PinRequired -interface DelayedNavigation : Parcelable - interface SecureRouter { fun withPinCodeCheckRequired( diff --git a/common/src/main/java/io/novafoundation/nova/common/presentation/AssetIconProvider.kt b/common/src/main/java/io/novafoundation/nova/common/presentation/AssetIconProvider.kt index d34692a2a0..6075c76cb3 100644 --- a/common/src/main/java/io/novafoundation/nova/common/presentation/AssetIconProvider.kt +++ b/common/src/main/java/io/novafoundation/nova/common/presentation/AssetIconProvider.kt @@ -5,6 +5,7 @@ import io.novafoundation.nova.common.data.model.AssetIconMode import io.novafoundation.nova.common.data.repository.AssetsIconModeRepository import io.novafoundation.nova.common.utils.images.Icon import io.novafoundation.nova.common.utils.images.asIcon +import io.novafoundation.nova.common.utils.images.asUrlIcon interface AssetIconProvider { @@ -31,7 +32,7 @@ class RealAssetIconProvider( AssetIconMode.WHITE -> "$whiteBaseUrl/$iconName" } - return iconUrl.asIcon() + return iconUrl.asUrlIcon() } } diff --git a/common/src/main/java/io/novafoundation/nova/common/resources/LanguagesHolder.kt b/common/src/main/java/io/novafoundation/nova/common/resources/LanguagesHolder.kt index 8e8acccfd5..4c24f6a4c4 100644 --- a/common/src/main/java/io/novafoundation/nova/common/resources/LanguagesHolder.kt +++ b/common/src/main/java/io/novafoundation/nova/common/resources/LanguagesHolder.kt @@ -21,6 +21,7 @@ class LanguagesHolder { private val JAPANESE = Language("ja", "JAPANESE") private val VIETNAMESE = Language("vi", "VIETNAMESE") private val KOREAN = Language("ko", "KOREAN") + private val HUNGARIAN = Language("hu", "HUNGARIAN") } fun getDefaultLanguage(): Language { @@ -29,7 +30,22 @@ class LanguagesHolder { fun getLanguages(): List { val defaultLanguage = listOf(getDefaultLanguage()) - val otherLanguages = listOf(CHINESE, FRENCH, INDONESIAN, ITALIAN, JAPANESE, KOREAN, POLISH, PORTUGUESE, RUSSIAN, SPANISH, TURKISH, VIETNAMESE) + val otherLanguages = listOf( + CHINESE, + FRENCH, + HUNGARIAN, + INDONESIAN, + ITALIAN, + JAPANESE, + KOREAN, + POLISH, + PORTUGUESE, + RUSSIAN, + SPANISH, + TURKISH, + VIETNAMESE + ) + return defaultLanguage + otherLanguages.sortedBy { it.name } } } diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/CallbackLruCache.kt b/common/src/main/java/io/novafoundation/nova/common/utils/CallbackLruCache.kt new file mode 100644 index 0000000000..3eea1ba192 --- /dev/null +++ b/common/src/main/java/io/novafoundation/nova/common/utils/CallbackLruCache.kt @@ -0,0 +1,20 @@ +package io.novafoundation.nova.common.utils + +import android.util.LruCache + +class CallbackLruCache(maxSize: Int) : LruCache(maxSize) { + + private var entryRemovedCallback: ((V) -> Unit)? = null + + fun setOnEntryRemovedCallback(callback: (V) -> Unit) { + this.entryRemovedCallback = callback + } + + fun removeAll() { + trimToSize(0) + } + + override fun entryRemoved(evicted: Boolean, key: K, oldValue: V, newValue: V) { + entryRemovedCallback?.invoke(oldValue) + } +} diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/Consumer.kt b/common/src/main/java/io/novafoundation/nova/common/utils/Consumer.kt new file mode 100644 index 0000000000..2083a1b0e3 --- /dev/null +++ b/common/src/main/java/io/novafoundation/nova/common/utils/Consumer.kt @@ -0,0 +1,14 @@ +package io.novafoundation.nova.common.utils + +class Consumer(private var target: T?) { + + fun getOnce(): T? { + return target.also { + target = null + } + } + + fun useOnce(block: (T) -> Unit) { + getOnce()?.let(block) + } +} diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/ImageLoaderExt.kt b/common/src/main/java/io/novafoundation/nova/common/utils/ImageLoaderExt.kt new file mode 100644 index 0000000000..3c9989dd26 --- /dev/null +++ b/common/src/main/java/io/novafoundation/nova/common/utils/ImageLoaderExt.kt @@ -0,0 +1,21 @@ +package io.novafoundation.nova.common.utils + +import android.widget.ImageView +import coil.ImageLoader +import coil.imageLoader +import coil.loadAny +import coil.request.ImageRequest + +fun ImageView.loadOrHide( + any: Any?, + imageLoader: ImageLoader = context.imageLoader, + builder: ImageRequest.Builder.() -> Unit = {} +) { + loadAny(any, imageLoader) { + listener( + onSuccess = { _, _ -> makeVisible() }, + onError = { _, _ -> makeGone() } + ) + builder() + } +} diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/ImageMonitor.kt b/common/src/main/java/io/novafoundation/nova/common/utils/ImageMonitor.kt new file mode 100644 index 0000000000..d4e857546a --- /dev/null +++ b/common/src/main/java/io/novafoundation/nova/common/utils/ImageMonitor.kt @@ -0,0 +1,57 @@ +package io.novafoundation.nova.common.utils + +import android.os.FileObserver +import android.widget.ImageView +import coil.ImageLoader +import coil.load +import java.io.File + +class ImageMonitor( + private val imageView: ImageView, + private val imageLoader: ImageLoader +) { + + private var fileObserver: FileObserver? = null + + fun startMonitoring(filePath: String) { + val file = File(filePath) + if (!file.exists()) { + return + } + + // Stop watching previous file + stopMonitoring() + + // Initialize FileObserver to monitor changes to the file + fileObserver = object : FileObserver(filePath, MODIFY) { + override fun onEvent(event: Int, path: String?) { + if (event == MODIFY) { + // When file is updated, invalidate the cache and reload + reloadImage(filePath) + } + } + } + + fileObserver?.startWatching() + + // Load the initial image + reloadImage(filePath) + } + + fun stopMonitoring() { + fileObserver?.stopWatching() + fileObserver = null + } + + private fun reloadImage(filePath: String) { + imageView.load(File(filePath), imageLoader) + } +} + +fun ImageMonitor.setPathOrStopWatching(filePath: String?) { + if (filePath == null) { + stopMonitoring() + } else { + startMonitoring(filePath) + } +} diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/InformationSize.kt b/common/src/main/java/io/novafoundation/nova/common/utils/InformationSize.kt index 1b4a99be5b..dcdee0a05a 100644 --- a/common/src/main/java/io/novafoundation/nova/common/utils/InformationSize.kt +++ b/common/src/main/java/io/novafoundation/nova/common/utils/InformationSize.kt @@ -18,6 +18,9 @@ value class InformationSize(private val sizeInBytes: Long) : Comparable : PayloadCreator { fun BaseFragment<*>.payload(): T { return requireArguments().getParcelable(KEY_PAYLOAD)!! } + +fun BaseFragment<*>.payloadOrNull(): T? { + return arguments?.getParcelable(KEY_PAYLOAD) as? T +} + +fun BaseFragment<*>.payloadOrElse(fallback: () -> T): T { + return payloadOrNull() ?: fallback() +} diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/RoundCornersOutlineProvider.kt b/common/src/main/java/io/novafoundation/nova/common/utils/RoundCornersOutlineProvider.kt new file mode 100644 index 0000000000..7d69989574 --- /dev/null +++ b/common/src/main/java/io/novafoundation/nova/common/utils/RoundCornersOutlineProvider.kt @@ -0,0 +1,22 @@ +package io.novafoundation.nova.common.utils + +import android.graphics.Outline +import android.graphics.Rect +import android.view.View +import android.view.ViewOutlineProvider + +class RoundCornersOutlineProvider( + private val cornerRadius: Float, + private val margin: Rect = Rect() +) : ViewOutlineProvider() { + + override fun getOutline(view: View, outline: Outline) { + outline.setRoundRect( + 0 + margin.left, + 0 + margin.top, + view.width - margin.right, + view.height - margin.bottom, + cornerRadius + ) + } +} diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/coil/transformation/TopCropTransformation.kt b/common/src/main/java/io/novafoundation/nova/common/utils/coil/transformation/TopCropTransformation.kt new file mode 100644 index 0000000000..95f8046e8b --- /dev/null +++ b/common/src/main/java/io/novafoundation/nova/common/utils/coil/transformation/TopCropTransformation.kt @@ -0,0 +1,43 @@ +package io.novafoundation.nova.common.utils.coil.transformation + +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.Matrix +import android.graphics.Paint +import coil.bitmap.BitmapPool +import coil.size.Size +import coil.size.PixelSize +import coil.transform.Transformation + +class TopCropTransformation : Transformation { + + override fun key(): String { + return "TopCropTransformation" + } + + override suspend fun transform(pool: BitmapPool, input: Bitmap, size: Size): Bitmap { + val (width, height) = if (size is PixelSize) { + size.width to size.height + } else { + input.width to input.height + } + + val scale = width / input.width.toFloat() + + val targetWidth = (input.width * scale).toInt() + val targetHeight = height + + val output = Bitmap.createBitmap(targetWidth, targetHeight, input.config) + + val canvas = Canvas(output) + val paint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.FILTER_BITMAP_FLAG) + + val matrix = Matrix() + matrix.setScale(scale, scale) + matrix.postTranslate(0f, 0f) + + canvas.drawBitmap(input, matrix, paint) + + return output + } +} diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/images/Icon.kt b/common/src/main/java/io/novafoundation/nova/common/utils/images/Icon.kt index 1f78a20199..6ec18747e7 100644 --- a/common/src/main/java/io/novafoundation/nova/common/utils/images/Icon.kt +++ b/common/src/main/java/io/novafoundation/nova/common/utils/images/Icon.kt @@ -8,11 +8,14 @@ import coil.load import coil.request.ImageRequest import io.novafoundation.nova.common.utils.makeGone import io.novafoundation.nova.common.utils.makeVisible +import java.io.File sealed class Icon { data class FromLink(val data: String) : Icon() + data class FromFile(val data: File) : Icon() + data class FromDrawable(val data: Drawable) : Icon() data class FromDrawableRes(@DrawableRes val res: Int) : Icon() @@ -25,6 +28,7 @@ fun ImageView.setIcon(icon: Icon, imageLoader: ImageLoader, builder: ExtraImageR is Icon.FromDrawable -> load(icon.data, imageLoader, builder) is Icon.FromLink -> load(icon.data, imageLoader, builder) is Icon.FromDrawableRes -> load(icon.res, imageLoader, builder) + is Icon.FromFile -> load(icon.data, imageLoader, builder) } } @@ -39,10 +43,13 @@ fun ImageView.setIconOrMakeGone(icon: Icon?, imageLoader: ImageLoader, builder: fun Drawable.asIcon() = Icon.FromDrawable(this) fun @receiver:DrawableRes Int.asIcon() = Icon.FromDrawableRes(this) -fun String.asIcon() = Icon.FromLink(this) +fun String.asUrlIcon() = Icon.FromLink(this) +fun String.asFileIcon() = Icon.FromFile(File(this)) +fun File.asIcon() = Icon.FromFile(this) fun ImageLoader.Companion.formatIcon(icon: Icon): Any = when (icon) { is Icon.FromDrawable -> icon.data is Icon.FromDrawableRes -> icon.res is Icon.FromLink -> icon.data + is Icon.FromFile -> icon.data } diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/recyclerView/dragging/Extensions.kt b/common/src/main/java/io/novafoundation/nova/common/utils/recyclerView/dragging/Extensions.kt new file mode 100644 index 0000000000..dc3c325e9c --- /dev/null +++ b/common/src/main/java/io/novafoundation/nova/common/utils/recyclerView/dragging/Extensions.kt @@ -0,0 +1,16 @@ +package io.novafoundation.nova.common.utils.recyclerView.dragging + +import android.annotation.SuppressLint +import android.view.MotionEvent +import android.view.View +import androidx.recyclerview.widget.RecyclerView.ViewHolder + +@SuppressLint("ClickableViewAccessibility") +fun View.prepareForDragging(viewHolder: ViewHolder, startDragListener: StartDragListener) { + this.setOnTouchListener { _, event -> + if (event.action == MotionEvent.ACTION_DOWN) { + startDragListener.requestDrag(viewHolder) + } + false + } +} diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/recyclerView/dragging/OnItemDragCallback.kt b/common/src/main/java/io/novafoundation/nova/common/utils/recyclerView/dragging/OnItemDragCallback.kt new file mode 100644 index 0000000000..b28b9465a8 --- /dev/null +++ b/common/src/main/java/io/novafoundation/nova/common/utils/recyclerView/dragging/OnItemDragCallback.kt @@ -0,0 +1,5 @@ +package io.novafoundation.nova.common.utils.recyclerView.dragging + +interface OnItemDragCallback { + fun onItemMove(fromPosition: Int, toPosition: Int) +} diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/recyclerView/dragging/SimpleItemDragHelperCallback.kt b/common/src/main/java/io/novafoundation/nova/common/utils/recyclerView/dragging/SimpleItemDragHelperCallback.kt new file mode 100644 index 0000000000..e592a4a534 --- /dev/null +++ b/common/src/main/java/io/novafoundation/nova/common/utils/recyclerView/dragging/SimpleItemDragHelperCallback.kt @@ -0,0 +1,29 @@ +package io.novafoundation.nova.common.utils.recyclerView.dragging + +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.RecyclerView + +class SimpleItemDragHelperCallback( + private val onItemDragCallback: OnItemDragCallback +) : ItemTouchHelper.Callback() { + + override fun isLongPressDragEnabled(): Boolean { + return true + } + + override fun isItemViewSwipeEnabled(): Boolean { + return false + } + + override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int { + val dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN + return makeMovementFlags(dragFlags, 0) + } + + override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean { + onItemDragCallback.onItemMove(viewHolder.bindingAdapterPosition, target.bindingAdapterPosition) + return true + } + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {} +} diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/recyclerView/dragging/StartDragListener.kt b/common/src/main/java/io/novafoundation/nova/common/utils/recyclerView/dragging/StartDragListener.kt new file mode 100644 index 0000000000..b5532ff471 --- /dev/null +++ b/common/src/main/java/io/novafoundation/nova/common/utils/recyclerView/dragging/StartDragListener.kt @@ -0,0 +1,7 @@ +package io.novafoundation.nova.common.utils.recyclerView.dragging + +import androidx.recyclerview.widget.RecyclerView.ViewHolder + +interface StartDragListener { + fun requestDrag(viewHolder: ViewHolder) +} diff --git a/common/src/main/java/io/novafoundation/nova/common/view/TopCropImageView.kt b/common/src/main/java/io/novafoundation/nova/common/view/TopCropImageView.kt new file mode 100644 index 0000000000..4c58c0af38 --- /dev/null +++ b/common/src/main/java/io/novafoundation/nova/common/view/TopCropImageView.kt @@ -0,0 +1,56 @@ +package io.novafoundation.nova.common.view + +import android.content.Context +import android.graphics.drawable.Drawable +import android.util.AttributeSet +import androidx.appcompat.widget.AppCompatImageView + +class TopCropImageView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyle: Int = 0, +) : AppCompatImageView(context, attrs, defStyle) { + + init { + scaleType = ScaleType.MATRIX + } + + override fun setImageDrawable(drawable: Drawable?) { + super.setImageDrawable(drawable) + updateMatrix(drawable) + } + + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + super.onSizeChanged(w, h, oldw, oldh) + drawable?.let { updateMatrix(it) } + } + + private fun updateMatrix(drawable: Drawable?) { + if (drawable == null) return + + val dwidth = drawable.intrinsicWidth + val dheight = drawable.intrinsicHeight + val vwidth = width + val vheight = height + + if (vwidth == 0 || vheight == 0) return // Skip if size is not ready + + val matrix = imageMatrix + val scale: Float + var dx = 0f + val dy = 0f + + if (dwidth * vheight > vwidth * dheight) { + scale = vheight.toFloat() / dheight.toFloat() + dx = (vwidth - dwidth * scale) * 0.5f + } else { + scale = vwidth.toFloat() / dwidth.toFloat() + } + + matrix.setScale(scale, scale) + matrix.postTranslate(dx, dy) + + imageMatrix = matrix + invalidate() + } +} diff --git a/common/src/main/res/anim/fragment_slide_in.xml b/common/src/main/res/anim/fragment_slide_in.xml new file mode 100644 index 0000000000..fb1717eb6d --- /dev/null +++ b/common/src/main/res/anim/fragment_slide_in.xml @@ -0,0 +1,24 @@ + + + + + + \ No newline at end of file diff --git a/common/src/main/res/anim/fragment_slide_out.xml b/common/src/main/res/anim/fragment_slide_out.xml new file mode 100644 index 0000000000..a84cb659fd --- /dev/null +++ b/common/src/main/res/anim/fragment_slide_out.xml @@ -0,0 +1,23 @@ + + + + + \ No newline at end of file diff --git a/common/src/main/res/drawable/bg_browser_tabs_outline.xml b/common/src/main/res/drawable/bg_browser_tabs_outline.xml new file mode 100644 index 0000000000..c535992186 --- /dev/null +++ b/common/src/main/res/drawable/bg_browser_tabs_outline.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/common/src/main/res/drawable/bg_dapp_entry_point.xml b/common/src/main/res/drawable/bg_dapp_entry_point.xml new file mode 100644 index 0000000000..3caefb951a --- /dev/null +++ b/common/src/main/res/drawable/bg_dapp_entry_point.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/common/src/main/res/drawable/ic_favorite_heart_filled.xml b/common/src/main/res/drawable/ic_favorite_heart_filled.xml index f6c6e297a0..674d63afa6 100644 --- a/common/src/main/res/drawable/ic_favorite_heart_filled.xml +++ b/common/src/main/res/drawable/ic_favorite_heart_filled.xml @@ -1,9 +1,9 @@ - + android:width="25dp" + android:height="24dp" + android:viewportWidth="25" + android:viewportHeight="24"> + diff --git a/common/src/main/res/drawable/ic_favorite_heart_filled_20.xml b/common/src/main/res/drawable/ic_favorite_heart_filled_20.xml new file mode 100644 index 0000000000..f6c6e297a0 --- /dev/null +++ b/common/src/main/res/drawable/ic_favorite_heart_filled_20.xml @@ -0,0 +1,9 @@ + + + diff --git a/common/src/main/res/drawable/ic_favorite_heart_outline.xml b/common/src/main/res/drawable/ic_favorite_heart_outline.xml index c6281aa2d0..2ff9b2f831 100644 --- a/common/src/main/res/drawable/ic_favorite_heart_outline.xml +++ b/common/src/main/res/drawable/ic_favorite_heart_outline.xml @@ -1,10 +1,10 @@ + android:width="25dp" + android:height="24dp" + android:viewportWidth="25" + android:viewportHeight="24"> diff --git a/common/src/main/res/drawable/ic_favorite_heart_outline_20.xml b/common/src/main/res/drawable/ic_favorite_heart_outline_20.xml new file mode 100644 index 0000000000..c6281aa2d0 --- /dev/null +++ b/common/src/main/res/drawable/ic_favorite_heart_outline_20.xml @@ -0,0 +1,10 @@ + + + diff --git a/common/src/main/res/drawable/ic_list_drag.xml b/common/src/main/res/drawable/ic_list_drag.xml index 6b08b56187..07501b69ff 100644 --- a/common/src/main/res/drawable/ic_list_drag.xml +++ b/common/src/main/res/drawable/ic_list_drag.xml @@ -4,7 +4,7 @@ android:viewportWidth="24" android:viewportHeight="24"> diff --git a/common/src/main/res/drawable/ic_siri_paw.xml b/common/src/main/res/drawable/ic_siri_paw.xml new file mode 100644 index 0000000000..1331cb199a --- /dev/null +++ b/common/src/main/res/drawable/ic_siri_paw.xml @@ -0,0 +1,25 @@ + + + + + + + + + + diff --git a/common/src/main/res/drawable/ic_tab_close.xml b/common/src/main/res/drawable/ic_tab_close.xml new file mode 100644 index 0000000000..885526b913 --- /dev/null +++ b/common/src/main/res/drawable/ic_tab_close.xml @@ -0,0 +1,13 @@ + + + + diff --git a/common/src/main/res/values-es/strings.xml b/common/src/main/res/values-es/strings.xml index 4fe3f8fd36..721d0bc074 100644 --- a/common/src/main/res/values-es/strings.xml +++ b/common/src/main/res/values-es/strings.xml @@ -241,6 +241,7 @@ Respaldar Respaldar Autenticación biométrica + Cerrar todo ¡Compra iniciada! Por favor, espere hasta 60 minutos. Puede seguir el estado en el correo electrónico. Seleccionar red para comprar %s Para continuar la compra será redirigido desde la aplicación Nova Wallet a %s @@ -257,6 +258,8 @@ Tasa de transferencia La red no responde A + Todas las pestañas abiertas en el navegador DApp se cerrarán. + ¿Cerrar todas las DApps? %s y recuerda siempre mantenerlos fuera de línea para restaurarlos en cualquier momento. Puedes hacer esto en la Configuración de Respaldo. Por favor, escribe todas las Frases de Contraseña de tu billetera antes de continuar El respaldo será eliminado de Google Drive @@ -480,6 +483,7 @@ %d segundos Ruta de derivación secreta + Ver todo Configuraciones Compartir Iniciar sesión @@ -642,6 +646,8 @@ Apruebe esta solicitud si confía en la aplicación.\nVerifique los detalles de la transacción. DApp DApps + %d DApps + Favoritos Favoritos Agregar a favoritos La DApp “%s” será eliminada de Favoritos diff --git a/common/src/main/res/values-fr-rFR/strings.xml b/common/src/main/res/values-fr-rFR/strings.xml index 08f40424f4..adc087d453 100644 --- a/common/src/main/res/values-fr-rFR/strings.xml +++ b/common/src/main/res/values-fr-rFR/strings.xml @@ -241,6 +241,7 @@ Sauvegarde Sauvegarde Auth biométrique + Fermer tout L\'achat est lancé ! Veuillez patienter jusqu\'à 60 minutes. Vous pouvez suivre l\'état d\'avancement sur l\'e-mail. Sélectionner le réseau pour acheter %s Pour poursuivre l\'achat, vous serez redirigé de l\'application Nova Wallet vers %s @@ -257,6 +258,8 @@ Frais de transfert Le réseau ne répond pas À + Toutes les onglets ouverts dans le navigateur DApp seront fermés. + Fermer toutes les DApps ? %s et n\'oubliez pas de toujours les conserver hors ligne pour les restaurer à tout moment. Vous pouvez le faire dans les Réglages de sauvegarde. Veuillez noter toutes les phrases de passe de votre portefeuille avant de continuer La sauvegarde sera supprimée de Google Drive @@ -480,6 +483,7 @@ %d secondes Chemin de dérivation du secret + Voir tout Paramètres Partager Se connecter @@ -642,6 +646,8 @@ Approuvez cette demande si vous avez confiance en l\'application.\nVérifiez les détails de la transaction. DApp DApps + %d DApps + Favoris Favoris Ajouter aux favoris La dApp «%s» sera retirée des favoris diff --git a/common/src/main/res/values-hu/strings.xml b/common/src/main/res/values-hu/strings.xml new file mode 100644 index 0000000000..1192aeec68 --- /dev/null +++ b/common/src/main/res/values-hu/strings.xml @@ -0,0 +1,1784 @@ + + + Kapcsolat + Github + Adatvédelmi irányelvek + Értékelj minket + Telegram + Általános Szerződési Feltételek + Általános Szerződési Feltételek + Rólunk + Alkalmazás verzió + Weboldal + Érvényes %s cím megadása... + Evm címnek érvényesnek vagy üresnek kell lennie... + Valós Substrate cím megadása... + Fiók hozzáadása + A fiók már létezik. Kérlek, próbálj meg egy másikat. + Bármely tárca követése cím alapján + Csak nézhető tárca hozzáadása + Írd le a titkos kulcsod és tedd biztonságos helyre + Kérlek, ügyelj arra, hogy a mondatot helyesen és olvashatóan írd le. + %s cím + Mnemonika megerősítése + Ellenőrizd még egyszer + Válaszd ki a szavakat a megfelelő sorrendben + Új fiók létrehozása + Ne használd a vágólapot vagy a képernyőképeket a mobileszközödön, próbálj meg egy biztonságosabb megoldást találni (pl. papír) + A nevet kizárólag az alkalmazásban használjuk. A későbbiekben megváltoztatható. + Tárca nevének megadása + Mnemonika mentése + Hozzon létre egy új tárcát + A mnemonicát a fiókhoz való hozzáférés helyreállítására használják. Jegyezd le, nélküle nem tudjuk helyreállítani a fiókodat! + Fiókok megváltoztatott titkos kulccsal + Elfelejt + A folytatás előtt győződj meg arról, hogy tárcádat már exportáltad. + Tárca felejtése? + Érvénytelen Ethereum származtatási útvonal + Érvénytelen Substrate származtatási útvonal + Ez a tárca %1$s által van párosítva. Bármilyen művelet létrehozható majd Nova-ban, de %1$s szükséges majd azok alárírásához. + %s nem támogatja + Ez csak egy nézhető tárca, aminek egyenlegét és egyéb információit láthatod Nova-ban, de semmilyen tranzakciót nem hajthatsz végre róla + Tárca becenév megadása... + Kérlek, próbálj meg egy másikat. + Ethereum kulcspár titkosítási típusa + Ethereum titkos származtatási útvonal + Fiók exportálása + Exportálás + Létező importálása + Ez az adatok titkosításához és a JSON-fájl mentéséhez szükséges. + Új jelszó beállítása + Mentsd le a titkos kulcsod és tedd biztonságos helyre + Írd le a titkos kulcsod és tedd biztonságos helyre + Érvénytelen visszaállítási JSON. Kérlek, győződj meg arról,hogy a JSON fájl évényes adatokat tartalmaz. + Érvénytelen seed. Kérlek, győződj meg arról, hogy a bevitel 64 hexadecimális szimbólumot tartalmaz. + A JSON nem tartalmaz hálózati információkat. Kérlek, add meg az alábbiakban. + Add meg a visszaállítási JSON-t + Általában 12 szavas kifejezés (de lehet 15, 18, 21 vagy 24) + A szavakat külön-külön, egy szóközzel, vessző vagy egyéb jelek nélkül kell beírni. + Írd be a szavakat a megfelelő sorrendben + Jelszó + 0xAB + Add meg a nyers seed-et + A Nova minden alkalmazással kompatibilis + Tárca importálása + Fiók + A származtatási útvonal nem támogatott szimbólumokat tartalmaz vagy nem megfelelő a felépítése + Érvénytelen származtatási útvonal + JSON fájl + Bizonyosodjon meg róla, hogy %s a Ledger eszközére a Ledger Live alkalmazás használatával + Polkadot alkalmazás telepítve van + %s az Ön Ledger eszközén + Nyissa meg a Polkadot alkalmazást + Győzödj meg róla, hogy a hálózati alkalmazás telepítve van azon a Ledger eszközödön, amelyik a Ledger Live-ot használja. Nyisd meg a hálózati alkalmazást a Ledger eszközödön. + Legalább egy fiókot hozzá kell adni + Fiókok hozzáadása a tárcához + A Ledger hivatalos Bluetooth-kapcsolati útmutatója + Győzödj meg róla, hogy a hálózati alkalmazás telepítve van azon a Ledger eszközödön, amelyik a Ledger Live-ot használja + Hálózati alkalmazás telepítve van + Nyisd meg a hálózati alkalmazást a Ledger eszközödön + Nyissa meg a hálózati alkalmazást + Engedélyezed a Bluetooth-t és helymeghatározást a Nova Wallet számára + hozzáférés a Bluetooth-hoz és a Helyhez + Válaszd ki a fiókot a hozzáadáshoz + Fiók kiválasztása + A Ledger Nano X csatlakoztatása + Az új, Általános Ledger alkalmazásra történő aláírás és a fiókok migrálása érdekében telepítse és nyissa meg a Migration alkalmazást. A legújabb és Migration Ledger alkalmazásokat nem támogatják a jövőben. + Új Ledger alkalmazás kiadva + Polkadot Migráció + A közeljövőben a Migration alkalmazás nem lesz elérhető. Használja azt fiókjainak az új Ledger alkalmazásra történő migrálásához, hogy elkerülje az alapok elvesztését. + Polkadot + Ledger Nano X + Ledger Nano X (Általános Polkadot alkalmazás) + Ledger Nano X (Örökség) + Kérlek, kapcsold be a Bluetooth-t a telefonodon és a Ledger eszközön. Old fel a Ledger eszközt és nyisd meg a(z) %s alkalmazást. + Válaszd ki Ledger eszközöd + 12, 15, 18, 21 vagy 24 szavas kifejezés + Nem rendelkezel fiókkal ezen a hálózaton, hozzáadhatsz vagy importálhatsz egyet. + Fiók szükséges + A fiók nem található + Parity Signer + %s nem támogatja a következő: %s + A következő fiókok sikeresen be lettek olvasva innen: %s + Itt vannak a fiókjaid + Érvénytelen QR-kód. Kérlek, győződj meg arról, hogy a QR-kódot a következőből olvasod be: %s + Ügyelj arra, hogy a legfelsőt válaszd + Nyisd meg a Parity Signer alkalmazást okostelefonodon + Nyissa meg a Parity Signer-t + Menj a \"Keys\" fülre. Válaszd a seed-et, majd a fiókot amit hozzá szeretnél adni a Nova Wallet-hez. + Navigáljon a „Kulcsok” lapra. Válassza ki a magot, majd a fiókot + A Parity Signer egy QR-kódot fog adni a beolvasáshoz + QR-kód a beolvasáshoz + %s tárca hozzáadása + A(z) %s nem támogat tetszőleges üzenetek aláírását - csak a tranzakciókét. + Az aláírás nem támogatott + QR-kód beolvasása innen: %s + QR-kód beolvasása ezzel: %s + A(z) %s hibát mutat + A QR-kód lejárt + Biztonsági okokból a generált művelet %s-ig érvényes. Kérlek, generálj egy új QR-kódot és írd alá a(z) %s segítségével + A QR-kód %s-ig érvényes + Kérlek, ellenőrizd, hogy a QR-kód az aktuális aláírási művelethez tartozik-e + %s aláírás + Polkadot Vault + Ügyelj arra, hogy a származtatási útvonal nevének üresnek kell lennie + Nyisd meg a Polkadot Vault alkalmazást okostelefonodon + Nyisd meg a Polkadot Vault-ot + %s Válaszd a \"your Key set-et\", majd válaszd ki a fiókot, amit hozzá szeretnél adni Nova Wallet-hez + Menj a \"Key Sets\" fülre. + A Polkadot Vault egy %s + QR-kód a beolvasáshoz + Privát kulcs + Hozzád delegálva (Proxieds) + Bármilyen + Aukció + Proxy megszakító + Kormányzás + Identitás megítélő + Nomináló Pool-ok + Nem átutaló + Stake-elés + Már rendelkezem fiókkal + 64 hexadecimális szimbólum + Válaszd ki a fizikai tárcát + Válaszd ki a titkos kulcs típusát + Tárca kiválasztása + Fiókok közös titkos kulccsal + Substrate kulcspár titkosítási típusa + Substrate titkos származtatási útvonal + Tárca beceneve + Moonbeam, Moonriver és egyéb hálózatok + EVM-cím (opcionális) + Előre beállított tárcák + Polkadot, Kusama, Karura, KILT és még 50+ hálózat + Kövess nyomon bármilyen tárcát anélkül, hogy a megadnád a privát kulcsodat Nova Wallet-be + A tárcád csak egy nézhető tárca, ami azt jelenti, hogy nem kezdeményezhetsz rajta semmilyen műveletet + Hoppá! Hiányzik a kulcs + csak nézhető + Polkadot Vault, Ledger vagy Parity Signer használata + Hardver tárca csatlakoztatása + %s fiók hozzáadása + Tárca hozzáadása + %s fiók módosítása + Fiók módosítása + Ledger (Örökség) + Egyéni csomópont hozzáadása + Hozzá kell adj egy %s fiókot a tárcádhoz, ahhoz hogy delegálhass + Adja meg a hálózati adatokat + Delegálás ide + Delegáló fiók + Delegáló tárca + Hozzáférés típusa + A letét a számládon marad a proxy eltávolításáig. + Elérted a felvehető proxy-k számát, ami %s a %s láncon. Törölj proxy-kat, hogy újakat vehess fel. + A proxy-k száma elérte a maximumot + Itt jelennek meg\na hozzáadott egyéni hálózatok + Színes + Megjelenés + token ikonjai + Fehér + A megadott szerződési cím már %s tokenként szerepel a Novában. + A megadott szerződési cím már %s tokenként fel van véve Nova-ba. Biztosan módosítani szeretnéd? + A token már hozzá van adva + Győzödj meg róla, hogy a megadott URL formátum a következő: www.coingecko.com/en/coins/tether. + Érvénytelen CoinGecko hivatkozás + A megadott szerződési cím nem egy %s ERC-20-as szerződés. + Érvénytelen szerződési cím + A tizedeseknek legalább 0-nak kell lenniük, és nem lehetnek többek, mint 36. + Érvénytelen tizedesjegy + Szerződési cím megadása + Tizedesjegyek megadása + Szimbólum megadása + Hálózatok + Tokenek + Token hozzáadása + Szerződés cím + Tizedesjegyek + Szimbólum + ERC-20 token részleteinek megadása + Válaszd ki a hálózatot az ERC-20 token hozzáadásához + Közösségi hitelek + Kormányzás v1 + OpenGov + Választások + Stake-elés + Vesting + Tokenek vásárlása + Visszakaptad DOT-odat a közösségi hitelekből? Kezd el stake-elni még ma, hogy megkapd a maximális jutalmakat. + Maximalizálja DOT jutalmait 🚀 + Tokenek szűrése + Minden hálózat + Tokenek kezelése + Ne küldj %s-ot a Ledger által vezérelt fiókra, mivel az nem támogatja a(z) %s küldését. emiatt ezen a fiókon fognak ragadni + A Ledger nem támogatja ezt a tokent + Keresés hálózat vagy token alapján + Nem található hálózat vagy token\na megadott névvel + Keresés token alapján + Tárcáid + Nincs elegendő tokened a küldéshez.\nVásárolj vagy Fogadj tokent\nfiókodra. + Token a fizetéshez + Fogadandó token + A változtatások végrehajtása előtt %s a módosított és eltávolított pénztárcákhoz! + biztosítsd, hogy megmentetted a titkos kifejezéseket + Alkalmazza a Biztonsági Másolat Frissítéseit? + Készülj fel a pénztárcád mentésére! + Ez a titkos kifejezés teljes és állandó hozzáférést biztosít minden összekapcsolt pénztárcához és az azokban lévő pénzhez.\n%s + NE OSZD MEG. + Ne írd be a titkos kifejezésedet semmilyen formába vagy weboldalra.\n%s + A PÉNZEK ÖRÖKRE ELVESZHETNEK. + Az ügyfélszolgálat vagy az adminok soha, semmilyen körülmények között nem fogják kérni a titkos kifejezésedet.\n%s + VIGYÁZZ AZ IMITÁTOROKRA. + Áttekintés és elfogadás a folytatáshoz + Biztonsági mentés törlése + Kézzel is készíthet biztonsági másolatot a jelmondatáról, hogy biztosítsa a hozzáférést a pénztárcájának pénzeszközeihez, ha elveszíti a hozzáférést ehhez az eszközhöz. + Biztonsági mentés kézzel + Kézi + Nem adott hozzá pénztárcát jelmondattal. + Nincs pénztárca a biztonsági mentéshez + Beállíthatja a Google Drive biztonsági mentéseket, hogy titkosított másolatokat tároljon az összes pénztárcájáról, amelyeket egy jelszó véd, amit Ön állít be. + Biztonsági mentés a Google Drive-ra + Google Drive + Biztonsági mentés + Biztonsági mentés + Biometrikus hitelesítés + Összes bezárása + Kérlek várj, a vásárlás megkezdődött, ami eltarthat akár 60 percig is. Az állapotot nyomon követheted az e-mailben. + Válasszon hálózatot a vásárláshoz %s + A vásárlás folytatásához átirányítunk a Nova Wallet alkalmazásból ide: %s + Folytatás a böngészőben? + Automatikus csomópontok egyensúlyozása + Kapcsolat engedélyezése + Kérjük, adjon meg egy jelszót, amely a pénztárcái felhőből történő visszaállításához lesz használva. Ezt a jelszót a jövőben nem lehet visszaállítani, ezért feltétlenül jegyezze meg! + Biztonsági mentés jelszavának frissítése + Eszköz + Elérhető egyenleg + Sajnáljuk, de az egyenleget nem tudtuk leellenőrizni. Kérlek, próbáld meg újra később. + Sajnáljuk, de nem tudunk kapcsolatba lépni az átutalási szolgáltatóval. Kérlek, próbáld meg újra később. + Sajnáljuk, de egyenleged nem elegendő a megadott összeg küldéséhez. + Átutalási díj + A hálózat nem elérhető + Ide + Az összes megnyitott fül a DApp böngészőben be lesz zárva. + Összes DApp bezárása? + %s és ne felejtse el mindig offline módban tartani őket, hogy bármikor visszaállíthassa őket. Ezt megteheti a Biztonsági Mentés Beállításokban. + Kérjük, írja le az összes pénztárca jelszót, mielőtt folytatná + A biztonsági mentés törölve lesz a Google Drive-ból + Az aktuális biztonsági mentés a pénztárcáival véglegesen törölve lesz! + Biztosan törölni szeretné az Felhőalapú Biztonsági Mentést? + Biztonsági mentés törlése + Jelenleg a biztonsági mentése nincs szinkronizálva. Kérjük, tekintse meg ezeket a frissítéseket. + Felhőbiztonsági mentés változásai észlelve + Frissítések áttekintése + Ha nem jegyezte fel manuálisan a pénztárcák jelszavát, amelyeket el fognak távolítani, akkor ezek a pénztárcák és minden eszközük véglegesen és visszavonhatatlanul el fognak veszni. + Biztosan alkalmazni akarja ezeket a változásokat? + Probléma áttekintése + Jelenleg a biztonsági mentése nincs szinkronizálva. Kérjük, tekintse meg a problémát. + A pénztárca változásai nem frissültek a felhőbiztonsági mentésben + Kérjük, győződjön meg róla, hogy be van jelentkezve a Google-fiókjába a megfelelő hitelesítő adatokkal, és engedélyezte a Nova Wallet számára a Google Drive elérését. + Google Drive hitelesítés sikertelen + Nincs elegendő szabad Google Drive tárhelye. + Nincs elegendő tárhely + Sajnos a Google Drive nem működik a Google Play szolgáltatások nélkül, amelyek hiányoznak az eszközéről. Próbáljon meg hozzáférést szerezni a Google Play szolgáltatásokhoz. + Google Play szolgáltatások nem találhatók + Nem sikerült biztonsági mentést készíteni a pénztárcáiról a Google Drive-ra. Kérjük, ellenőrizze, hogy engedélyezte-e a Nova Wallet számára a Google Drive használatát, és hogy van-e elegendő szabad tárhelye, majd próbálja újra. + Google Drive hiba + Kérjük, ellenőrizze a jelszó helyességét, és próbálja újra. + Érvénytelen jelszó + Sajnálattal közöljük, hogy nem találtunk biztonsági mentést a pénztárcák visszaállításához + Nincs talált biztonsági mentés + Győződjön meg arról, hogy elmentette a pénztárca előkészítő kódját, mielőtt folytatná. + A pénztárca törölve lesz a felhőalapú biztonsági mentésből + Biztonsági mentés hiba átnézése + Biztonsági mentés frissítés átnézése + Biztonsági mentés jelszó beírása + Engedélyezze a pénztárcák biztonsági mentését a Google Drive-ra + Utolsó szinkronizálás: %s ekkor: %s + Jelentkezzen be a Google Drive-ra + Google Drive probléma átnézése + Biztonsági mentés letiltva + Biztonsági mentés szinkronizálva + Biztonsági mentés szinkronizálása... + Biztonsági mentés nincs szinkronizálva + Új pénztárcák automatikusan hozzáadódnak a felhőalapú biztonsági mentéshez. Kikapcsolhatja a felhőalapú biztonsági mentést a beállításokban. + A pénztárcák változásai frissítve lesznek a felhőalapú biztonsági mentésben + Feltételek elfogadása... + Fiók + Fiók címe + Aktív + Hozzáadás + Delegálás hozzáadása + Hálózat hozzáadása + Cím + Haladó + Mind + Engedélyez + Összeg + Az összeg túl kevés + Az összeg túl nagy + Alkalmazott + Alkalmaz + Újra kérdezés + Figyelem! + Elérhető: %s + Átlag + Egyenleg + Bónusz + Hívás + Mégsem + Biztosan megszakítja ezt a műveletet? + Sajnáljuk, de az alkalmaás nem megfelelő a kérés végrehajtásához. + A hivatkozás nem nyitható meg + Legfeljebb %s használható fel, mivel ki kell fizetned %s hálózati díjat. + Lánc + Változtatás + Jelszó megváltoztatása + Automatikus folytatás a jövőben + Hálózat kiválasztása + Törlés + Bezárás + Biztos, hogy be akarod záni a képernyőt?\nA módosításaid el fognak veszni. + Felhő biztonsági mentés + Befejezett + Befejezett ( %s ) + Megerősít + Megerősítés + Biztos vagy benne? + Megerősítve + %d ms + csatlakozás... + Kérjük, ellenőrizze a kapcsolódását vagy próbálkozzon később + Kapcsolat sikertelen + Folytatás + Vágólapra másolva + Cím másolása + Id másolása + Kulcspár titkosítási típusa + Dátum + %s és %s + Törlés + Részletek + Letiltva + Kapcsolat bontása + Ne zárja be az alkalmazást! + Kész + Ne mutasd ezt mégegyszer + Szerkeszt + %s (további +%s) + Válaszd ki az e-mail alkalmazást + Engedélyez + Cím megadása... + Összeg megadása... + Adja meg az adatokat + Adja meg a jelszót + Hiba + Nincs elegendő token + Esemény + EVM + EVM cím + A fiókod el lesz távolítva a blokkláncról a művelet után, mivel a fennmaradó egyenleg kevesebb lesz, mint a minimum + A művelet el fogja távolítani a fiókot + Lejárt + Felfedezés + Sikertelen + A becsült hálózati díj %s, ami jóval magasabb, mint az alapértelmezett hálózati díj (%s). Ennek oka lehet az ideiglenes hálózati túlterheltség. Frissítheted, hogy egy alacsonyabb hálózati díjat kaphass. + A hálózati díj túl magas + Díj: %s + Rendezés: + Szűrők + További információ + Elfelejtette a jelszót? + + naponta + %s naponta + + naponta + minden nap + %s beszerzése + Értem + Kormányzás + Hexadecimális szöveg + + %d óra + %d óra + + Hogyan működik + Megértettem + Információ + A QR-kód érvénytelen + Tudj meg többet + További információ + Ledger + %s van vissza + Maximum + %s maximum + + perc + perc + + A(z) %s fiók hiányzik + Módosít + Modul + Hálózat + Ethereum + %s nem támogatott + Polkadot + Hálózatok + + Hálózat + Hálózat + + Következő + Nem + A fájlimportáló alkalmazás nem található az eszközön. Kérlek, telepítés után próbáld újra + Nem található megfelelő alkalmazás a szándék kezelésére az eszközön + Nincs változás + Meg fogjuk mutatni a mnemonikus jelmondatodat. Győződj meg róla, hogy senki sem láthatja a képernyődet és ne készíts róla képernyőképet — ezeket harmadik féltől származó rosszindulatú programok begyűjthetik. + Egyik sem + Nem elérhető + Sajnáljuk, de egyenleged nem elegendő a hálózati díj kifizetéséhez. + Fedezethiány + Nincs elegendő egyenlege a hálózati díj %s fizetésére. Aktuális egyenleg %s + Most nem + Ki + OK + Oké, vissza + Be + Folyamatban lévő + Opcionális + Lehetőségek választása + Jelszó kifejezés + Beillesztés + / év + %s / év + évente + % + A képernyő használatához szükség van a kért engedélyekre. A beállításokban be tudod őket kapcsolni. + Hozzáférés megtagadva + A képernyő használatához szükség van a kért engedélyekre. + Szükséges engedélyek + Ár + Adatvédelmi irányelvek + Folytatás + Proxy letét + Hozzáférés visszavonása + Push értesítések + Bővebben + Ajánlott + Frissítési díj + Elutasít + Eltávolít + Kötelező + Visszaállítás + Újra + Hiba történt. Kérlek, próbáld meg újra. + Visszavon + Mentés + QR-kód beolvasása + Keresés + Itt fog megjelenni a keresés eredménye + Keresési eredmények: %d + mp + + %d másodperc + %d másodpercek + + Titkos származtatási útvonal + Összes megtekintése + Beállítások + Megosztás + Bejelentkezés + Aláírási kérelem + Érvénytelen aláírás + Kihagyás + Folyamat kihagyása + Hiba történt néhány tranzakció elküldésekor. Szeretnéd újra megpróbálni? + Néhány tranzakció elküldése nem sikerült + Hiba történt + Rendezés + Állapot + Substrate + Substrate cím + Koppintson a megjelenítéshez + Általános Szerződési Feltételek + Teszt hálózat + Hátralévő idő + Cím + Beállítások megnyitása + Az egyenleged túl kevés + Összes + Teljes díj + Tranzakció ID + Tranzakció elküldve + Próbálja újra + Típus + Kérlek, próbálkozz újra. Amennyiben a hiba ismét megjelenik, fordulj ügyfélszolgálatunkhoz. + Ismeretlen + + %s nem támogatott + %s nem támogatott + + Korlátlan + Frissítés + Használ + Használja a maximumot + A címzettnek érvényes %s címnek kell lennie + Érvénytelen címzett + Várakozik + Tárca + Figyelmeztetés + Igen + Az összegnek pozitívnak kell lennie + Kérjük, adja meg a jelszót, amit a biztonsági mentés során létrehozott + Írja be az aktuális biztonsági mentés jelszavát + Válassza ki a szavakat... + Érvénytelen titkos kifejezés, kérjük ellenőrizze még egyszer a szavak sorrendjét + Referendum + Szavazás + Kategória + Ez a csomópont már létezik. Kérlek, próbálj meg egy másikat. + A csomópont nem elérhető. Kérlek, próbálj meg egy másikat. + Sajnos a hálózat nem támogatott. Kérlek, próbáld meg a következők egyikét: %s. + A(z) %s törlésének megerősítése. + Hálózat törlése? + Kérlek, ellenőrizd a kapcsolatot, vagy próbáld meg újra később + Egyéni + Alapértelmezett + Hálózatok + Kapcsolat hozzáadása + QR-kód beolvasása + Gond merült fel a biztonsági mentésével. Lehetősége van törölni a jelenlegi biztonsági mentést, és létrehozni egy újat. %s mielőtt folytatná. + Győződjön meg róla, hogy minden pénztárcához elmentette a titkos kifejezéseket + Biztonsági mentés található, de üres vagy sérült + A jövőben, a biztonsági mentés jelszava nélkül nem lehet helyreállítani a pénztárcákat a felhő alapú mentésből.\n%s + Ez a jelszó nem helyreállítható. + Emlékezz a Biztonsági Mentés Jelszavára + Jelszó megerősítése + Biztonsági mentés jelszava + Betűk + Min. 8 karakter + Számok + Jelszavak egyeznek + Kérjük, adjon meg egy jelszót a biztonsági mentés bárhogyan történő eléréséhez. A jelszó nem állítható helyre, ezért biztosan emlékezzen rá! + Hozza létre a biztonsági mentés jelszavát + A bevitt láncazonosító nem egyezik az RPC URL hálózatával. + Érvénytelen Láncazonosító + A privát közösségi hitelek még nem támogatottak. + Privát közösségi hitel + A közösségi hitelekről + Közvetlen + Tudj meg többet az Acala-nak nyújtott különféle hozzájárulásokról + Likvid + Aktív ( %s ) + Elfogadom az Általános Szerződési Feltételeket + Nova Wallet bónusz (%s) + Az Astar ajánlói kódnak egy valós Polkadot címnek kell lennie. + Nem tudod a kiválasztott összeget hozzáadni, mert azzal a közösségi hitel túllépné a felső határát. A maximum megengedett keret: %s. + Nem tudsz hozzájárulni a kiválasztott közösségi hitelhez, mivel az már elérte a felső határt. + A közösségi hitel túllépte a felső határt + Hozzájárulás a közösségi hitelekhez + Hozzájárulás + Hozzájárulásai: %s + Likvid + Parallel + Itt fognak megjelenni\n a hozzájárulásaid + Vissza kerül %s múlva + A parachain által visszaküldendő + %s (%s-on) + Közösségi hitelek + Szerezz különleges jutalmat. + Itt fognak megjelenni a közösségi hitelek + Nem tudsz hozzájárulni a kiválasztott közösségi hitelhez, mivel már véget ért. + A közösségi hitel véget ért + Add meg az ajánlói kódodat + Közösségi hitel információ + Ismerd meg a(z) %s közösségi hitelt + %s közösségi hitel weboldala + Lízing időszak + Válassz parachain-t, hogy hozzájárulj %s tokeneddel. Felajánlott tokenjeidet vissza fogod kapni, illetve, ha a parachain elnyeri a helyet, az aukció végén további jutalmakat is kapni fogsz. + A hozzájáruláshoz hozzá kell adnod egy %s fiókot a tárcádhoz + Bónusz alkalmazása + Amennyiben nem rendelkezel ajánlói kóddal, használd a Nova ajánlókódot, hogy bónuszt kaphass hozzájárulásodért + Nem adtál hozzá bónuszt. + A Moonbeam közösségi hitel csak a SR25519 vagy ED25519 kriptotípusú számlákat támogatja. Kérlek, egy másik számlád használj ha hozzá szeretné járulásni. + Ezzel a fiókkal nem lehet hozzájárulni + Add hozzá Moonbeam fiókodat tárcádhoz, hogy részt vehess a Moonbeam közösségi hitelekben. + A Moonbeam fiók hiányzik + Ez a közösségi hitel nem elérhető a tartózkodási helyeden. + A régiód nem támogatott + %s jutalom célhelye + Megállapodás benyújtása + A folytatáshoz a blokkláncon kell benyújtanod az Általános Szerződési Feltételekhez való hozzájárulást. Ezt csak egyszer kell megtenned az összes további Moonbeam hozzájárulás esetében. + Elolvastam és elfogadom az Általános Szerződési Feltételeket + Összegyűlt + Ajánlói kód + Hibás ajánlói kód. Kérlek, próbálj meg egy másikat. + %s Általános Szerződési Feltételei + A hozzájárulás minimálisan megengedett összege %s. + A hozzájárulás összege túl alacsony + %s tokenjeid a lízingidőszak lejárta után kerülnek vissza. + Hozzájárulásaid + Összegyűlt: %s / %s + A megadott RPC URL már szerepel a Nova-ban mint %s egyéni hálózat. Biztosan módosítani szeretné? + https://networkscan.io + Blokk böngésző URL (Opcionális) + 012345 + Láncazonosító + TOKEN + Pénznem szimbóluma + Hálózat neve + Csomópont hozzáadása + Felhasználói csomópont hozzáadása a + Adataok megadása + Mentés + Felhasználói csomópont szerkesztése a + Név + Csomópont neve + wss:// + Csomópont URL + RPC URL + DApp-ok, amelyeknek hozzáférést biztosítottál a címed megtekintéséhez, amikor használod őket + \"%s\" el lesz távolítva az engedélyezett DApp-ok közül + Eltávolítod az engedélyezettek közül? + Engedélyezett DApp-ok + Katalógus + Engedélyezd a kérést, ha megbízol ebben az alkalmazásban + Engedélyezed a(z) \"%s\" hozzáférjen a fiók címeidhez? + Csak akkor engedélyezd a kérést, ha megbízol ebben az alkalmazásban.\nEllenőrizd a tranzakció részleteit. + DApp + DApps + %d DApp + Kedvencek + Kedvencek + Hozzáadás a kedvencekhez + A „%s” DApp el lesz távolítva a Kedvencek közül + Eltávolítod a kedvencek közül? + Itt fog megjelenni a DApp-ok listája + Böngészd az internetet Nova Wallet fiókjaiddal + Üdvözöl a Nova Browser + Hozzáadás a kedvencekhez + Asztali mód + Eltávolítás a kedvencekből + Oldalbeállítások + Oké, hagyjuk + A Nova Wallet úgy véli, hogy ez a weboldal veszélyt jelenthet fiókodra és tokenjeidre. + Adathalászat észlelve + Keresés névvel vagy URL-vel + Nem támogatott lánc, %s genesis hash-el + Győződj meg arról, hogy a művelet helyes + Nem sikerült aláírni a kért műveletet + Mégis megnyitás + A rosszindulatú DAppok ki tudják vonni az összes pénzét. Mindig végezzen saját kutatást, mielőtt DAppot használ, engedélyt ad, vagy pénzt küld el.\n\nHa valaki sürgeti, hogy látogassa meg ezt a DAppot, valószínűleg átverés. Ha kétségei vannak, kérjük, vegye fel a kapcsolatot a Nova Wallet támogatással: %s. + Figyelem! A DApp ismeretlen + A lánc nem található + %s hivatkozásról származó domain nem engedélyezett + Nem definiált kormányzási típus + Nem támogatott kormányzási típus + Érvénytelen kriptotípus + Érvénytelen származtatási útvonal + Érvénytelen mnemonika + Érvénytelen URL + A megadott RPC URL már szerepel a Nova-ban mint %s hálózat. + Alapértelmezett Értesítési Csatorna + +%d + Keresés cím vagy név alapján + Nem megfelelő cím formátum. Győzödj meg róla, hogy a cím a megfelelő hálózathoz tartozik. + keresési eredmények: %d + A Nova Wallet automatikusan hozzáadja a delegált hatásköröket (Proxy) egy külön kategóriához. A tárcákat bármikor módosíthatod a Beállításokban. + Értesítés delegált fiókokról + Összes szavazat + Delegálás + Minden fiók + Egyének + Szervezetek + A delegálás visszavonásának időszaka, a delegálás visszavonása után kezdődik + Szavazataid automatikusan a delegáltak szavazataival együtt fognak szavazni + Delegált információ + Egyéni + Szervezet + Delegált szavazatok + Delegációk + Delegálás szerkesztése + Nem delegálhatsz saját magadnak, kérlek, válassz egy másik címet + Nem delegálhatsz saját magadnak + Mesélj magadról, hogy a Nova felhasználók jobban megismerhessenek. + Delegált vagy? + Mutasd be magad + %s kategórián át + Utolsó %s szavazatai + Szavazataid a(z) %s által + Szavazataid: %s a(z) %s által + Szavazatok eltávolítása + Delegálás visszavonása + A delegálás feloldási időszak lejárata után fel kell oldanod a tokenjeidet. + Delegált szavazatok + Delegációk + Utolsó %s szavazatai + Kategóriák + Mindet kiválaszt + Legalább 1 kategóriát kell kiválasztani... + Nincsenek delegálható kategóriák + Fellowship + Kormányzás + Kincstár + Delegálás visszavonási időszakasz + Delegálásod + Delegálásaid + Mutat + Biztonsági másolat törlése... + A biztonsági másolat jelszava korábban frissült. A Felhőbiztonsági mentés folytatása érdekében, %s + kérjük, adja meg az új biztonsági mentési jelszót. + A biztonsági másolat jelszava megváltozott + Nem tudsz tranzakciókat aláírni letiltott hálózatok esetén. Engedélyezd a %s beállításokban, és próbálkozz újra + %s le van tiltva + Már delegálsz a következő fiókhoz: %s + A delegálás már létezik + (BTC/ETH kompatibilis) + ECDSA + ed25519 (alternatív) + Edwards + Biztonsági mentés jelszó + Nincs elegendő token a díj megfizetéséhez + Szerződés + Szerződés hívás + Funkció + Tárcák helyreállítása + %s Az összes tárcád biztonságosan el lesz mentve a Google Drive-ra. + Szeretnéd visszaállítani a tárcáidat? + Meglévő felhőmentés található + Restore JSON letöltése + Jelszó megerősítése + A jelszavak nem egyeznek + Jelszó beállítása + Hálózat: %s\nMnemonikus: %s\nSzármaztatott útvonal: %s + Hálózat: %s\nMnemonikus: %s + Kérlek, várj a díj kiszámításáig + A díjszámítás folyamatban van + Delegálás megadása %s stake-eléshez + Váltás részletei + Max: + Fizetendő + Fogadandó + Token kiválasztása + Kormányzási Értesítési Csatorna + + Válassz legalább %d kategóriát + Válassz legalább %d kategóriát + + Felold + Előzmények + E-mail + Hivatalos név + Elem neve + Identitás + Web + A megadott JSON fájlt egy másik hálozaton hozták létre. + Kérlek, győződj meg arról, hogy a megadott egy érvényes JSON fájl. + A visszaállítási JSON érvénytelen + Kérlek, ellenőrizd a jelszó helyességét, és próbáld meg újra. + Sikertelen kulcstár visszafejtés + JSON beillesztése + Nem támogatott titkosítási típus + A Substrate titkos kulccsal rendelkező fiókot nem lehet Ethereum titkosítással importálni a hálózatba + Az Ethereum titkos kulccsal rendelkező fiókot nem lehet Substrate titkosítással importálni a hálózatba + A mnemonikád érvénytelen + Kérlek, győződj meg arról, hogy a megadott szöveg 64 hexadecimális szimbólumot tartalmaz. + A seed érvénytelen + Sajnáljuk, a tárcáidat tartalmazó biztonsági mentés nincs meg. + Biztonsági mentés nem található + Tárcák helyreállítása a Google Drive-ról + Használd a 12, 15, 18, 21 vagy 24 szóból álló kifejezésedet + Válaszd ki, hogyan szeretnéd importálni a tárcádat + Csak megfigyelés + Integráld a fejlesztés alatt álló hálózat összes funkcióját a Nova Walletbe, hogy mindenki számára elérhető legyen. + Integráld a hálózatod + Fejlesztés Polkadot számára? + A proxy címnek érvényes %s címnek kell lennie + Érvénytelen proxy cím + A megadott pénznem szimbóluma (%1$s) nem egyezik meg a hálózattal (%2$s). Használni szeretnéd a helyes pénznem szimbólumot? + Érvénytelen pénznemszimbólum + A QR-kód nem dekódolható + QR-kód + Feltöltés a galériából + JSON fájl exportálása + Nyelv + A Ledger nem támogatja a következőt: %s + A műveletet az eszköz megszakította. Győzödj meg róla, hogy feloldottad a Ledger eszközöd. + Művelet megszakítva + Nyísd meg a(z) %s alkalmazást a Ledger eszközödön + A %s alkalmazás nem indult el + A művelet hibával végződött. Kérlek, próbáld meg újra később. + Ledger műveleti hiba + További fiókok betöltése + Ellenőrzés és jóváhagyás + Nyomd le mindkét gombot a %s eszközön a tranzakció jóváhagyásához + Kérlek, frissítsd a %s-ot a Ledger Live-val + A metaadatok elavultak + A Ledger nem támogat tetszőleges üzenetek aláírását - csak a tranzakciókét. + Kérlek, ellenőrizd, hogy a megfelelő Ledger eszközt választottad az aktuális művelethez + Biztonsági okokból a generált művelet %s-ig érvényes. Kérlek, próbáld újra és hagyd jóvá Ledger-vel + A tranzakció lejárt + A tranzakció érvényes: %s + A Ledger nem támogatja ezt a tranzakciót. + A tranzakció nem támogatott + Nyomd le mindkét gombot a %s eszközön a cím jóváhagyásához + Ez a tárca a Ledger-rel van párosítva. A Nova segít bármilyen művelet létrehozásában és azok aláírása a Ledger segítségével történik majd. + Válaszd ki a fiókot a hozzáadáshoz + Hálózati információ betöltése... + Biztonsági mentés keresése... + Kifizetési tranzakció elküldve + Token hozzáadása + Biztonsági mentés kezelése + Hálózat törlése + Hálózat szerkesztése + Hozzáadott hálózat kezelése + Nem fogja tudni megtekinteni token egyenlegeit azon a hálózaton az Eszközök képernyőn + Hálózat törlése? + Csomópont törlése + Csomópont szerkesztése + Hozzáadott csomópont kezelése + Csomópont \"%s\" törölve lesz + Csomópont törlése? + Egyedi kulcs + Alapértelmezett kulcs + Ne osszon meg senkivel semmilyen információt - Ha ezt megteszi, véglegesen és visszavonhatatlanul elveszíti minden eszközét + Fiókok egyedi kulccsal + Alapértelmezett fiókok + %s, +%d egyéb + Fiókok alapértelmezett kulccsal + Válassza ki a mentendő kulcsot + Válasszon ki egy pénztárcát a mentéshez + Kérjük, olvassa el figyelmesen a következőket, mielőtt megtekinti a biztonsági mentését + Ne ossza meg jelszavát! + Győződjön meg róla, hogy senki sem látja a képernyőjét,\nés ne készítsen képernyőképeket + Kérjük %s bárki + ne ossza meg senkivel + Kérlek, próbálj ki egy másikat. + Érvénytelen mnemonikus jelszó, kérlek, ellenőrizd a szavak sorrendjét. + Nem választhatsz %d tárcánál többet + + Legalább %d tárcát kell választani + Legalább %d tárcát kell választani + + Ez a node már létezik + Hálózati díj + Csomópont címe + Csomópont információ + Hozzáadva + egyéni node-ok + alapértelmezett node-ok + Alapértelmezett + Csatlakozás… + Gyűjtemény + Készítette + %s / %s + %s / %s egység + #%s / %s kiadás + Korlátlan sorozat + Tulajdonos + Nem listázott + Saját NFT-k + Az Ön által megadott URL már létezik \"%s\" csomópontként. + Ez a csomópont már létezik + Az Ön által megadott csomópont URL-je vagy nem válaszol, vagy hibás formátumú. Az URL formátumának \"wss://\"-kel kell kezdődnie. + Csomópont hiba + Az Ön által megadott URL nem felel meg a %1$s csomópontnak.\nKérjük, adja meg az érvényes %1$s csomópont URL-jét. + Hibás hálózat + Jutalmak igénylése + Tokeneid vissza fognak kerülni a stake-be + Direkt + Pool stake-elési információ + Jutalmaid (%s) ki lesznek kérve, majd hozzá lesznek adva az egyenlegedhez + Pool + A műveletet nem lehet végrehajtani, mert a pool megsemmisülés alatt áll. Hamarosan be for zárni. + A pool megsemmisül + Jeleneg nincs szabad hely a pool-od unstake-elési sorában. Kérlek, próbáld újra %s múlva + Túl sokan unstake-elnek a pool-odból + A pool-od + A pool-od (#%s) + Fiók létrehozása + Új tárca létrehozása + Adatvédelmi irányelvek + Fiók importálása + Már rendelkezem tárcával + A folytatással elfogadod a %1$s-et és az %2$s-et + Felhasználási feltételek + Váltás + Egy a kollátoraid közül nem generál jutalmakat + Egy a kollátoraid közül nem lett kiválaszta a jelenlegi körben + A(z) %s unstake-elési időszak véget ért. Ne felejtsd el kiváltani tokenjeid + Nem lehet stake-elni ezzel a kollátorral + Kollátor módosítása + Nem lehet stake-et hozzáadni ehhez a kollátorhoz + Kollátorok kezelése + A kiválasztott kollátor úgy néz ki, hogy nem fog részt venni a stake-elésben. + Nem adhatsz stake-et ahhoz a kollátorhoz, melynél unstake-eled az összes tokened. + A stake-ed (%s) kevesebb lesz, mint a minimális stake ennél a kollátornál. + A fennmaradó stake-elési egyenleg a minimális hálózati érték alá fog esni (%s) és az unstake-elési összeghez fog hozzáadódni + Nincs jogosultságod. Kérlek próbáld újra. + Biometrika használata engedélyezéshez + A Nova Wallet biometrikus hitelesítéssel korlátozza a jogosulatlan felhasználók hozzáférését az alkalmazáshoz. + Biometria + A PIN-kód sikeresen megváltozott + Erősítsd meg a PIN-kódodat + PIN-kód létrehozása + PIN-kód megadása + Add meg PIN-kódodat + Nem csatlakozhatsz a pool-hoz, mert az elérte a tagok maximális számát + A pool megtelt + Nem csatlakozhatsz olyan pool-hoz, amelyik nincs nyitva. Kérlek, vedd fel a kapcsolatot a pool tulajdonosával. + A pool nincs nyitva + Már nem használhatja egy fióknál egyszerre a közvetlen stakelést és a pool stakelést. Ahhoz, hogy a pool stakelést kezelje, előbb ki kell vonnia tokenjeit a közvetlen stakelésből. + Pool működés nem elérhető + Hálózat hozzáadása manuálisan + Hálózatok listájának betöltése... + Keresés hálózati név alapján + Hálózat hozzáadása + Fiókok + Tárcák + Nyelv + PIN-kód módosítása + Jóváhagyás PIN-kóddal + Biztonságos mód + Beállítások + Ez a fiók tranzakciók végrehajtásához adott hozzáférést a következőnek: + Már nem érvényes + Mi az a proxy? + Stake-elési műveletek + A delegált fiók %s nem rendelkezik elegendő egyenleggel a hálózati díj megfizetéséhez, ami %s. Az elérhető egyenleg: %s + A proxy tárcák csak a tranzakciók aláírását támogatják, egyéb üzenetekét nem. + %1$s nem delegálta a(z) %2$s fiókot + %1$s delegálta a(z) %2$s, de csak %3$s joggal + Hoppá! Nincs megfelelő jogosultság + A tranzakciót a(z) %s delegált fiók fogja kezdeményezni. A hálózati díjat szintén a delegált fiók fogja megfizetni. + Ez egy Delegáló (Proxied) fiók + %s proxy + A delegált szavazott + Új Referendum + Referendum frissítés + %s Referendum #%s szavazása elindult! + 🗳️ Új Referendum + Töltsd le a Nova Wallet v%s verzióját, hogy elérd az összes új funkciót! + Új frissítés érhető el a Nova Wallet-hez! + %s Referendum #%s szavazása lezárult és el lett fogadva 🎉 + ✅ Referendum elfogadva! + %s Referendum #%s státusza megváltozott: \"%s\" helyett \"%s\" + %s Referendum #%s szavazása lezárult és el lett utasítva! + ❌ Referendum elutasítva! + 🗳️ A referendum státusza megváltozott + %s Referendum #%s státusza megváltozott: %s + Nova közlemények + Egyenlegek + Értesítések engedélyezése + Nem fogsz értesítést kapni tárca tevékenységekről (Egyenlegek, Stake-elés), mivel nem választottál ki egy tárcát sem. + Egyebek + Érkezett tokenek + Elküldött tokenek + Stake-elési jutalmak + Tárcák + ⭐️ Új jutalom %s + %s érkezett %s stake-elésből + ⭐️ Új jutalom + Nova Wallet • most + Érkezett +0.6068 KSM ($20.35) a Kusama stake-elésből. + %s érkezett %s láncra + ⬇️ Érkezett + ⬇️ Érkezett %s + %s elküldve %s címre, %s láncon + 💸 Elküldve + 💸 Elküldve %s + Tárca aktivitási értesítésekhez legfeljebb 3 tárcát lehet kiválasztani. + Push értesítések engedélyezése + Értesítéseket kaphatsz a tárcában történő műveletekről, Kormányzási frissítésekről, Stake-elés aktivitásról és Biztonsági frissítésekről, így mindig napra kész lehetsz. + A push értesítések engedélyezésével elfogadod a %s-et és az %s-et + Kérlek, próbáld meg bekapcsolni később a Beállítások fül alatt, az értesítéseknél. + Ne maradj le semmiről! + Válassza ki a hálózatot a %s fogadásához + Cím másolása + JSON beillesztése vagy fájl feltöltése… + Fájl feltöltése + JSON visszaállítása + Mnemonikus jelmondat + Nyers seed + Forrás típus + Minden referendum + Mutat: + Nem szavaztak + Szavazott + A megadott szűrőkhöz nincs megfelelő referendum + Itt fog megjelenni információ a referendumról, ahogy elkezdődik + Nem található referendum a megadott címmel\nvagy azonosítóval + Keresés a referendum címével vagy azonosítóval + %d népszavazás + Csúsztassa el, hogy a népszavazásokra AI összefoglalók segítségével szavazzon. Gyors és könnyű! + Referendum + A referendum nem található + A tartózkodó szavazatok csak 0.1x meggyőződés mellett lehetségesek. Szavazás 0.1x meggyőződéssel? + Meggyőződés frissítése + Tartózkodó szavazatok + Igen: %s + Nova DApp böngésző használata + A címet és leírást csak a javaslattevő szerkesztheti. Amennyiben az ajánlattevő fiók a sajátod, látogass el a Polkassembly oldalra és töltsd fel információkkal a javaslatot. + Minden részlet + Igényelt összeg + Idővonal + Szavazatod: + Jóváhagyási görbe + Kedvezményezett + Hash másolása + Letét + Választók + Hívási hash + Túl hosszú az előnézethez + JSON paraméterek + Javaslattevő + Támogatási görbe + Eredmény + Szavazatküszöb + Pozíció: %s / %s + A szavazáshoz hozzá kell adnod a következő fiókot a tárcádhoz: %s + Referendum %s + Nem: %s + Nem szavazatok + %s szavazat %s által + Igen szavazatok + Stake-elés + Jóváhagyott + Megszakított + Döntés alatt + Döntés %s múlva + Végrehajtott + Várakozás alatt + Várakozás alatt (%s / %s) + Elpusztított + Elutasítás alatt + Elfogadás + Előkészítés alatt + Elutasított + Jóváhagyás %s múlva + Végrehajtás %s múlva + Kifut az időből %s múlva + Elutasítás %s múlva + Kifutott az időből + Letétre várva + Küszöbérték: %s / %s + Szavazás eredménye: Jóváhagyott + Megszakított + Létrehozott + Szavazás: Döntés alatt + Végrehajtott + Szavazás: Várkozás alatt + Elpusztított + Szavazás: Elutasítás alatt + Szavazás: Elfogadás alatt + Szavazás: Felkészülés alatt + Szavazás eredménye: Elutasított + Kifutott az időből + Szavazás: Várakozás a letétre + Elfogadáshoz: %s + Közösségi hitelek + Kincstár: nagy kiadás + Kincstár: nagy borravaló + Fellowship: admin + Kormányzás: regisztrátor + Kormányzás: bérlet + Kincstár: közepes kiadás + Kormányzás: visszavonó + Kormányzás: pusztító + Fő napirend + Kincstár: kicsi kiadás + Kincstár: kicsi borravaló + Kincstár: bármi + zárolva marad itt: %s + Feloldható + Tartózkodik + Igen + Minden lekötött újrafelhasználása: %s + Kormányzásba zártak újrafelhasználása: %s + Kormányzati zár + Zárolási időszak + Nem + Szavazatok szorzása a zárolási időszak növelésével + Szavazás erre: %s + A zárolási időszak után ne felejtsd el feloldani a tokeneket + Szavazott referendum + %s szavazat + %s × %s x + Itt fog megjelenni a szavazók listája + %s szavazat + Fellowship: fehérlista + Szavazatod: %s szavazat + A referendum befejeződött és a szavazás véget ért + A referendum befejeződött + Szavataidat már delegálod a kiválasztott referendum kategóriákba. Kérlek kérd meg a delegáltad, hogy szavazzon vagy vond vissza delegálásod, hogy közvetlenül szavazhass. + A szavazatok már delegálás alatt vannak + Elérted a maximum %s szavazatot ehhez a kategóriához + Maximum ennyi szavazat lehet + Nem rendelkezel elegendő tokennel, ahhoz hogy szavazhass. Szavazáshoz elérhető: %s + Hozzáférés típusának visszavonása + Delegálás visszavonása + Szavazatok eltávolítása + + %d kategóriában már szavaztál referendumokra. Ahhoz, hogy az adott kategória elérhető legyen delegáláshoz, törölnöd kell a meglévő szavazataidat. + %d kategóriában már szavaztál referendumokra. Ahhoz, hogy az adott kategória elérhető legyen delegáláshoz, törölnöd kell a meglévő szavazataidat. + + Eltávolítod a szavazataid előzményeid? + %s Ez kizárólag a tiéd, biztonságosan tárolva, mások számára nem hozzáférhető. A biztonsági mentés jelszava nélkül a pénztárcák helyreállítása a Google Drive-ról lehetetlen. Ha elveszett, töröld a jelenlegi biztonsági mentést, hogy új jelszóval újat hozz létre. %s + Sajnálatos módon a jelszavad nem állítható vissza. + Egyébként használj titkos kifejezést a helyreállításhoz. + Elvesztetted a jelszavad? + Biztonsági mentés jelszava korábban frissítve lett. A Cloud Backup használatához kérlek írd be az új biztonsági mentés jelszavát. + Kérlek add meg a biztonsági mentés során létrehozott jelszót + Add meg a biztonsági mentés jelszavát + Nem sikerült frissíteni a blokklánc információt. Előfordulhat, hogy néhány funkció nem fog működni. + Runtime frissítési hiba + Kapcsolatok + fiókjaim + Nem található pool a megadott névvel\nvagy azonosítóval. Győzödj meg róla,\nhogy a megadott adat helyes + Fiókcím vagy fióknév + Itt fognak megjelenni a keresési eredmények + Keresési eredmények + Aktív pool-ok: %d + tagok + Pool kiválasztása + Kategóriák választása delegáláshoz + Elérhető kategóriák + Kérlek, válaszd ki azokat a kategóriákat, amelyekre szavazati jogot kívánsz adni. + Kategóriák választása delegálás szerkesztéséhez + Kategóriák választána delegálás visszavonásához + Nem elérhető kategóriák + A Nova-nak szüksége van a helymeghatározáshoz, hogy megtalálhassa a Ledger eszköt bluetooth-on keresztül + Kérlek, engedélyezd a helymeghatározást a készülék beállításaiban + Hálózat választása + Kategóriák választása: + %d / %d + Cím vagy w3n + Válassz hálózatot az elküldéshez %s + A címzett egy rendszerfiók. Nem áll vállalat vagy magánszemély irányítása alatt.\nBiztos, hogy még mindig el akarod indítani ezt az utalást? + A tokenek el fognak veszni + Meghatalmazott + Kérlek, ellenőrizd, hogy a biometrikus adatok engedélyezve vannak-e a Beállításokban + A biometrikus adatok le vannak tiltva a Beállításokban + Közösség + E-mail + Általános + Minden aláírási művelethez, mely a tárcában kulcspárral rendelkezik, (Nova Wallet-ben készített vagy importált) PIN-kódos jóvahagyás szükséges, még az aláírási folyamat előtt. + Hitelesítés kérése a műveletek aláírásához + Tulajdonságok + A push értesítések csak a Google Play-ről letöltött Nova Wallet érhetőek el. + A push értesítések csak a Google-szolgáltatásokkal rendelkező eszközökön érhetőek el. + A képernyőfelvétel és a képernyőképek nem lesznek elérhetőek. A minimalizált alkalmazás nem fog tartalmat megjeleníti + Biztonságos mód + Biztonság + Segítség és visszajelzés + Twitter + Wiki & Súgóközpont + Youtube + A megítélés 0.1x-re lesz állítva, ha tartózkodsz. + Nem lehet közvetlenül és jelölési poolokkal egyidejűleg lekötni + Már le van kötve + Fejlett stake-elés menedzsment + A stake-elési típus nem módosítható + Már directben stake-elsz + Direkt stake-elés + Kevesebbet adtál meg, mint %s, ami a minimum ahhoz, hogy %s alatt jutalmakat szerezhess. Jutalmak szerzéséhez érdemes lenne megfontolnod a Pool stake-elést. + Tokenek újrafelhasználása a Kormányzásban + Minimális stake: %s + Jutalmak: Automatikus kifizetés + Jutalmak: Manuális igénylés + Már stake-elsz egy pool-ban + Pool stake-elés + A stake-ed kevesebb, mint a jutalmak megszerzéséhez szükséges minimum + Nem támogatott stake-elési típus + sr25519 (ajánlott) + Schnorrkel + A kiválasztott fiók már használatban van, mint vezérlő + Delegált hatáskör hozzáadása (Proxy) + Delegációid + Aktív delegálók + A művelet végrehajtásához vegyél fel egy vezérlő fiókot %s az alkalmazásba. + Delegálás hozzáadása + A stake-ed kevesebb, mint %s, ami a minimum.\nA minimálisnál kevesebb stake, nagy eséllyel nem fog jutalmat generálni. + Stake-elj még több tokent + Változtasd meg a validátoraidat. + Jelenleg minden rendben van. Itt fognak majd megjelenni a figyelmeztetések. + A validátorhoz rendelt elavult stake-elési pozíció felfüggesztheti a jutalmakat. + Stake-elési fejlesztések + Kiváltható tokenek igénylése + Kérlek, várd meg a következő korszak kezdetét + Figyelmeztetések + Ez már egy vezérlő fiók + Itt rendelkezel már stake-el: %s + Stake-elési egyenleg + Egyenleg + Stake növelése + Nem nominálsz és nem is validálsz + Vezérlő módosítása + Validátorok módosítása + %s / %s + Kiválasztott validátorok + Vezérlő + Vezérlő fiók + Ezen a fiókon nincsenek szabad tokenek, biztos, hogy meg akarod változtatni a vezérlőt? + A vezérlő megszüntetheti a stake-et, kiválthatja, visszaküldheti a stake-be, megváltoztathatja a jutalmak érkezési helyét és a validátorokat. + A vezérlő általában unstake-elni, kiváltani, vissza stake-elni, validátorokat változtatni és a jutalmak érkezési helyének beállítására képesek + A vezérlő megváltozott + Ez a validátor blokkolva van, emiatt nem lehet kiválasztani. Kérlek, próbáld meg a következő korszakban. + Szűrők törlése + Összes kijelölés törlése + A maradék kijelölése az ajánlottak közül + Validátorok: %d / %d + Validátorok kiválasztása (max. %d) + Kijelöltek mutatása: %d (max %d ) + Validátorok kiválasztása + Becsült jutalmak (% APR) + Becsült jutalom (% APY) + Lista frissítése + Stake-elés a Nova DApp böngészőn keresztül + További stake-elési lehetőségek + Stake és jutalom szerzés + Becsült jutalmak + korszak #%s + Becsült bevétel + Becsült %s bevétel + Validátor saját stake-je + Validátor saját stake-je (%s) + Az unstake-elési időszak alatt a tokenek nem generálnak jutalmat + Az unstake-elési időszak alatt a tokenek nem hoznak jutalmakat + A stake-elési időszak után ki kell váltanod a tokenjeid. + Az unstake-elési időszak után ne felejtsd el kiváltani tokenjeidet + A jutalmaid meg fognak emelkedni a következő korszaktól + A jutalmaid meg fognak emelkedni a következő korszaktól + A stake-elt tokenjeid minden korszakban jutalmakat generálnak (%s). + Stake-elt tokenek minden korszakban jutalmat generálnak (%s) + A fennmaradó stake elkerülése érdekében, a Nova Wallet\nmeg fogja változtatni a jutalmak érkezési helyét a címedre. + Amennyiben megszüntetnéd stake-elésed, meg kell várnod a megszüntetési időszakot (%s) + Tokenek unstake-elésénél, meg kell várnod az unstake-elési időszakot (%s) + Stake-elési információ + Aktív nominátorok + + nap + nap + + Minimális stake + %s hálózat + Stake-elt + Proxy beállításához válts át a(z) %s tárcára + Stash fiók kiválasztása proxy beállításához + Kezelés + %s (max %s ) + A nominátorok száma elérte a maximumot. Próbáld újra később + A stake-et nem lehet elkezdeni + Min. stake + Fel kell vegyél egy %s fiókot a tárcádba, ahhoz hogy elkezdhess stake-elni + Havi + Vezérlő fiók hozzáadása az eszközön. + Nincs hozzáférés a vezérlő fiókhoz + Nominált: + %s jutalmazott + A hálózat nem választotta ki az egyik validátorodat. + Aktív állapot + Inaktív állapot + A stake-elt összeg kevesebb, mint a jutalom megszerzéséhez szükséges minimum. + A hálózat egyik validátorodat sem választotta ki. + A stake-elés a következő korszakban for kezdődni. + Inaktív + Várakozás a következő korszakra + Várakozás a következő korszakra (%s) + Az egyenleged nem elegendő a(z) %s proxy letéthez. Elérhető egyenleg: %s + Stake-elési Értesítési Csatorna + Kollátor + A kollátor minimális stake-je nagyobb, mint amennyit delegálsz. Nem fogsz jutalmakat kapni ettől a kollátortól. + Kollátor információ + Kollátor saját stake-je + Kollátorok: %s + Egy vagy több kollátorod ki lett választva a hálózat által. + Delegálók + Elérted a maximum delegálást a(z) %s kollátornál + Nem választhatsz új kollátort + Új kollátor + várakozás a következő körre (%s) + Függőben lévő unstake kérelmeid vannak az összes kollátorodnál. + Egy kollátor sem elérhető unstake-hez + A visszaküldött tokenek a következő körtől lesznek számolva + Stake-elt tokenek minden körben jutalmat generálnak (%s) + Kollátor kiválasztása + Kollátor kiválasztása... + A jutalmaid meg fognak emelkedni a következő körtől + Nem fogsz jutalmakat kapni ebben a körben, mivel a delegálásod nem aktív. + Már unstake-elsz ennél a kollátortól. Csak egy függőben lévő unstake lehet kollátoronként + Nem tudsz unstake-elni ettől a kollátortól + Stake-ednek nagyobbnak kell lennie, mint a minimális stake (%s) ennél a kollátornál. + Nem fogsz jutalmat kapni + Néhány kollátorod vagy nem lett kiválasztva vagy a stake-elt összegnél magasabb a minimum stake limitje. Nem fogsz jutalmat kapni a jelenlegi körben ettől a kollátortól. + Kollátoraid + Stake-ed a következő kollátorokhoz lett rendelve + Aktív kollátorok, melyek nem termelnek jutalmakat + Kollátorok, melyek nem rendelkeznek elegendő stake-el a megválasztáshoz + Kollátorok, melyek a következő körben lesznek beiktatva + Függőben (%s) + Kifizetés + A kifizetés lejárt + + %d nap van vissza + %d nap van vissza + + A kifizetést saját kezűleg is igényelheted a hálózati díj megfizetése mellett, amikor a jutalmak közel vannak a lejárathoz. + A validátorok a jutalmakat 2-3 naponta fizetik ki. + Mind + Mind + A befejezés dátuma mindig a mai nap + Egyéni időszak + %dN + Befejező dátum kiválasztása + Véget ér + Az elmúlt 6 hónap (6H) + 6H + Az elmúlt 30 nap (30N) + 30N + Az elmúlt 3 hónap (3H) + 3H + Dátum kiválasztása + Kezdő dátum kiválasztása + Kezdődik + Stake-elési jutalmak mutatása + Az elmúlt 7 nap (7N) + 7N + Az elmúlt év (1É) + + Az elérhető egyenleged %s, Nem stake-elhetsz többet, mint %s + Delegált hatáskörök (proxy) + Jelenlegi pozíció a sorban + Új pozíció a sorban + Visza a stake-be + Minden unstake-elés alatti + A visszaküldött tokenek a következő korszaktól lesznek számolva + Egyéni összeg + A vissza stake-elni kívánt összeg nagyobb, mint az unstake-elési egyenleg + Utoljára unstake-elt + Legjövedelmezőbb + Nincs túljelentkezés + Rendelkezik láncon tárolt azonosítóval + Nem büntetett + Identitásonként legfeljebb 2 validátor + legalább egy kapcsolattartóval + Ajánlott validátorok + Validátorok + Becsült jutalom (APY) + Kiváltás + Kiváltható: %s + Jutalom + Jutalom érkezési helye + Átutalható jutalmak + Korszak + Jutalom részletek + Validátor + Bevétel újra stake-el + Jutalmak újra stake-elés nélkül + Tökéletes! Minden jutalom kifizetésre került. + Fantasztikus! Nincsenek kifizetetlen jutalmaid + Az összes kifizetése (%s) + Függőben lévő jutalmak + Kifizetetlen jutalmak + Jutalmak + A jutalmakról + Jutalmak (APY) + Jutalmak érkezési helye + Válassz saját kezűleg + Válaszd ki a kifizetési fiókot + Ajánlottak választása + %d kiválasztva (max. %d) + Validátorok (%d) + Vezérlő frissítése Stash-re + Proxy-k használata javasolt ahhoz, hogy stake-elési műveletek delegálhass egy másik fiókhoz + A vezérlő fiókok hamarosan elavulnak + Válassz egy másik fiókot, mint vezérlő, hogy a stake-eléssel kapcsolatos műveleteket átruházd + A stake-elés biztonságának javítása + Validátorok beállítása + Nincsenek kiválasztva validátorok + Válassz validátorokat a stake-elés megkezdéséhez + A jutalmak következetes megszerzéséhez ajánlott minimális stake %s. + A hálózati minimális értéknél (%s) nem lehet kevesebb a stake. + A minimális stake-nek nagyobbnak kell lennie mint %s + Újra stake-elés + Újra stake-elési jutalmak + Hogyan használd fel a jutalmaidat? + Válaszd ki a jutalom típusát + Kifizetési fiók + Büntetés + Stake %s + Stake max. + Stake-elési időszak + Stake-elési típus + Bíznod kell a nomináltjaidban, hogy azok szakszerűen és őszintén járnak el. Kizárólag az aktuális jövedelmezőségre alapozni döntésed, a jutalmak csökkenésével vagy akár elvesztésével végződhet. + Gondosan válaszd ki validátoraidat, mert azoknak szakszerűen és őszintén kell eljárniuk. Kizárólag a jövedelmezőségre alapozni döntésed, a jutalmak csökkenésével vagy akár elvesztésével végződhet. + Stake-elés validátorokkal + A Nova Wallet biztonsági és jövedelmezőségi kritériumok alapján választja ki a legjobb validátorokat + Stake-elés ajánlott validátorokkal + Stake-elés indítása + Stash + A stash képes többet lekötni és vezérlőt beállítani + A stash fiók képes növelni a stake-et és beállítani a vezérlőt + A(z) %s stash fiók nem elérhető, a stake beállításainak frissítéséhez + A nomináló passzív jövedelemre tesz szert, ha zárolja tokenjeit a hálózat biztosítására. Ennek elérése érdekében a nominálónak ki kell választania a támogatandó validátorokat. A nominálónak óvatosnak kell lennie a validátorok kiválasztásakor. Amennyiben a kiválasztott validátor nem fog megfelelően viselkedik, az incidens súlyosságától függően mindkettőre súlyos büntetések vonatkozhatnak. + A Nova Wallet segít kiválasztani a validátorokat a nominálók számára. A mobilalkalmazás lekéri az adatokat a blokkláncról, majd összeállít egy listát a validátorokról, a következőket szempontok alapján: a legmagasabb nyereségel rendelkezők, rendelkezik identitással és elérhetőségi adatokkal, nem büntetett és van szabad kapacitása a nominálások fogadására. A Nova Wallet a decentralizációval is törődik, így ha egy személy vagy egy cég több validátor csomópontot futtat, akkor az ajánlott listában legfeljebb 2 ilyen csomópontot jelenit meg. + Ki az a nominátor? + A stake-elésért járó jutalmak minden korszak végén kifizethetők (6 óra Kusama-án és 24 óra Polkadot-on). A hálózat 84 korszakig tárolja a jutalmakat és legtöbb esetben a validátorok mindenkinek automatikusan kifizetik azokat. Az validátorok, bármilyen okból kifolyólag vagy akár csak feledékenyséből elmulaszthatják a kifzetést, emiatt a nominálók maguk is igényelhetik azt. + Habár a jutalmakat a validátorok általában kifizetik, Nova Wallet figyelmeztetésekkel segít, ha vannak olyan kifizetetlen jutalmak, amelyek a lejárathoz közelednek. A stake-elési képernyőn ilyen és egyéb figyelmeztetések is megjelennek. + Jutalmak átvétele + A stake-elés egy lehetőség passzív jövedelemszerzésre a tokenek hálózatban való zárolásával. A stake-elési jutalmak minden korszakban kiosztásra kerülnek (6 óra a Kusama-án és 24 óra a Polkadot-on). Bármennyi ideig lehet stake-elni, de a tokenek unstake-eléséhez meg kell várnod, amíg a stake-elési időszak véget ér, csak ez után lesznek kiválthatóak. + A stake-elés a hálózat biztonságának és megbízhatóságának fontos része. Bárki futtathat validátor csomópontokat, de a hálózat csak azokat választja meg és jutalmazza, melyeknek elegendő tokenje van ahhoz, hogy részt vegyenek az új blokkok összeállításában. A validátorok gyakran nem rendelkeznek elegendő saját tokennel, ezért a nominálók a szükséges stake mennyiség eléréséhez lekötik tokenjeiket a validároknál. + Mi az a stake-elés? + A validátor a hét minden napján, egész nap egy blokklánc-csomópontot futtat. Elegendő stake-elt tokennel kell rendelkeznie (mind saját, mind a nominálók által biztosított) ahhoz, hogy a hálózat megválaszthassa. Az validátoroknak meg kell őrizniük csomópontjaik teljesítményét és megbízhatóságát, hogy jutalmat kaphassanak. Validátornak lenni szinte egy teljes munkaidős folyamat, vannak olyan cégek, amelyek kizárólag blokklánc validátorok üzemeltetéssel foglalkoznak. + Bárki lehet validátor és futtathat egy blokklánc csomópontot, de ehhez bizonyos szintű technikai készségekre és felelősségre van szükség. A Polkadot és a Kusama hálózatoknak van egy Thousand Validators Program nevű programjuk, amely támogatást nyújt a kezdőknek. Sőt, maga a hálózat is mindig több és több validátort fog jutalmazni, akiknek alacsonyabb a stake-je (de elegendő a megválasztáshoz) a decentralizáció érdekében. + Ki az a validátor? + A vezérlő fiók beállításához válts át a stash fiókodra. + Stake-elés + %s stake-elés + Jutalmazott + Összes stake-elt + Boost küszöbérték + A kollátoromnál + Yield Boost nélkül + Yield Boost-val + automatikusan %s után az átutalható tokenjeimmel, ha több van, mint + automatikusan %s után (korábban mint: %s) az átutalható tokenjeimmel, ha több van, mint + Stake-elni akarok + Yield Boost + Stake-elési típus + Az összes tokened unstake-elés alatt áll, emiatt nem tudsz többet stake-elni. + Nem tudsz többet stake-elni + Amikor részletekben unstake-elsz, a stake-ben kell maradjon legalább %s. Szeretnéd az összesed unstake-elni, beleértve a(z) %s maradékot is? + Túl kis összeg marad a stake-ben + Az unstake-elni kívánt összeg nagyobb, mint a stake-elt egyenleg + Unstake + Itt fognak megjelenni az unstake-elési tranzakciók + Itt fognak megjelenni az unstake-elési tranzakciók + Unstake-elés: %s + A tokenek kiválthatóak az unstake-elési időszak után. + Elérted az unstake-elési kérelmek limitjét (%d aktív kérelem) + Az unstake-elési kérelmek limitje elérve + Unstake-elési időszak + Unstake mind + Unstake mindent? + Becsült jutalom (% APY) + Becsült jutalom + Validator információ + Túljelentkezett. Nem fogsz jutalmat kapni ebben a korszakban ettől a validátortól. + Nominátorok + Túljelentkezett. Csak a legnagyobb stake-el rendelkező nominálók kapnak jutalmat. + Saját + Nincs találat.\nGyőződj meg róla, hogy a teljes fiókcímet írtad be + A validátor helytelen viselkedés miatt büntetve lett a hálózaton (pl. offline állapotba ment, megtámadta a hálozatot vagy módosított szoftvert futtatott). + Összes stake + Teljes stake (%s) + A jutalom kevesebb, mint a hálózati díj. + Éves + A stake-ed a következő validátorohoz van rendelve. + Stake-ed a következő validátorhoz lett rendelve + Megválasztott ( %s ) + Validátorok, akiket ebben a korszakban nem választottak meg. + Validátorok, melyek nem rendelkeznek elegendő stake-el a megválasztáshoz + Mások, akik a stake-ed nélkül aktívak. + Aktív validátorok, melyeknél nem stake-elsz + Nem választották meg ( %s ) + Tokenjeid túljelentkezett validátorhoz lettek szétosztva. Ebben a korszakban nem fogsz jutalmat kapni. + Jutalmak + Stake-ed + Validátoraid + A validátoraid a következő korszakban fognak megváltozni. + Most készítsünk biztonsági mentést a pénztárcádról. Ez biztosítja, hogy a forrásaid biztonságban legyenek. A biztonsági mentések lehetővé teszik a pénztárcád bármikori visszaállítását. + Folytatás a Google-lal + Írja be a pénztárca nevét + Az új pénztárcám + Folytatás kézi biztonsági mentéssel + Adjon egy nevet a pénztárcájának + Ez csak ön számára lesz látható, és később módosíthatja. + A pénztárca készen áll + Zárolt tokenek vannak egyenlegeden a(z) %s miatt. A folytatáshoz adj meg kevesebbet, mint %s vagy többet, mint %s. Egyéb összeg stake-eléséhez a(z) %s törlése szükséges. + Nem tudod stake-elni a megadott összeget + Kiválasztott: %d (max. %d) + Elérhető egyenleg: %1$s (%2$s) + %s a stake-elt tokenjeiddel + Vegyél részt a kormányzásban + Stake-elj többet, mint %1$s és %2$s a stake-elt tokenjeiddel + vegyél részt a kormányzásban + Stake-elj bármikor, mindössze %1$s-ért. Jutalmakat %2$s múlva kaphatsz. + %s múlva + Stake-elj bármikor. Jutalmakat %s múlva kaphatsz. + Tudj meg többet a\n%1$s stake-elésről a %2$s oldalon + Nova Használati útmutató + A jutalmak %1$s érkeznek. Automatikus kifizetéshez stake-elj többet, mint %2$s, ellenkező esetben manuálsan kell igényeled + minden %s + A jutalmak %s érkeznek + A jutalmak %s összegződnek. Manuálsan kell igényelned. + A jutalmak %s érkeznek és hozzáadódnak az utalható egyenleghez + A jutalmak %s érkeznek és visszakerülnek a stake-be + A jutalmak és stake-elés állapota időről időre változhat. %s rendszeresen + Ellenőrizd a stake-ed állapotát + Stake-elés indítása + Lásd %s + Felhasználási feltételek + %1$s egy %2$s ahol %3$s + a tokennek nincs értéke + teszt hálózat + %1$s\n%2$s tokenjeiden évente + Keress akár %s-ot + Unstake-elj bármikor és pénzedet kiválthatod %s. Unstake-elés alatt nem kapsz jutalmat + %s múlva + A pool amit választottál nem aktív, mert nincsenek kiválaszva a validátorai vagy a stake-je kevesebb, mint a minimum. Biztos vagy benne, hogy ezt a pool-t választod? + A nominátorok száma elérte a maximumot. Próbáld újra később + %s jelenleg nem elérhető + Validátorok: %d (max. %d) + Módosítva + Új + Eltávolítva + Token a hálózati díj fizetéséhez + A hálózati díj hozzáadódik a megadott összeghez + Ez a pár nem támogatott + A(z) #%s (%s) művelet nem sikerült + %s csere %s-ra %s-on + %s átutalás %s-ból %s-ba + + %s művelet + %s műveletek + + %s a %s műveletekből + Cseréljük %s-t %s-ra %s-on + Átutalása %s-ra %s + Végrehajtási idő + Nincs elegendő tokent a számládon. Legalább %s kell, hogy maradjon a(z) %s hálozati díj megfizetése után + Legalább %s meg kell maradjon, ahhoz hogy %s tokent fogadhass + Legfeljebb %1$s értékben válthatsz, mivel fizetned kell %2$s hálózati díjat is. + Legfeljebb %1$s értékben válthatsz, mivel fizetned kell %2$s hálózati díjat és konverziót is végre kell hajts %3$s és %4$s között, hogy megmaradjon a(z) %5$s minimális egyenleged. + Max. váltás + Min. váltás + Legalább %1$s kell, hogy legyen az egyenlegeden. Szeretnél egy teljes cserét végezni a maradék %2$s hozzáadásával? + Túl kevés összeg marad az egyenlegen + Legalább %1$s kell, hogy maradjon a %2$s hálózati díj megfizetése után továbbá a(z) %3$s és %4$s konverzió után, hogy megmaradjon a minimum egyenleg, ami %5$s. Szeretnél egy teljes cserét végezni a maradék %6$s hozzáadásával? + Fizetés + Fogadás + Token kiválasztása + Nincs elegendő token a váltáshoz + Nem elegendő likviditás + Nem fogadhatsz kevesebbet, mint %s + %s azonnali vásárlása hitelkártyával + %s átutalása egy másik hálózatról + %s fogadása QR kóddal vagy a címeddel + %s beszerzése a következővel + A csere végrehajtása közben a közbenső beérkező összeg %s, ami kevesebb, mint a minimális egyenleg %s. Próbáljon meg nagyobb csereösszeget megadni. + A csúszást %s és %s között kell megadni + Érvénytelen csúszás + Token kiválasztása fizetéshez + Token kiválasztása fogadáshoz + Összeg megadása + Egyéb összeg megadása + A %s tokennel történő hálózati díj fizetéséhez, Nova automatikus %s és %s konverziót fog végrehajtani, ahhoz, hogy fenntartsa a számla minimális egyenlegét, ami %s. + A hálózati díjakat a blokklánc számítja fel a tranzakciók feldolgozásáért és hitelesítéséért. A díj összege változhat a hálózattól és a tranzakció sebességétől. + Válassza ki a hálózatot %s cseréjekor + Az pool-nak nincs elegendő likviditása a cseréhez + Az árkülönbözet két eltérő eszköz közötti árkülönbségre utal. Egy kriptovaluta váltás során az árkülönbözet általában a váltani kívánt eszköz ára és a váltásra váró eszköz ára közötti különbség. + Árkülönbség + %s ≈ %s + Az árfolyam két különböző kriptovaluta között azt jelenti, hogy egy kriptovalutából mennyit kaphatsz cserébe egy bizonyos mennyiségű másik kriptovalutáért. + Mérték + Régi árfolyam: %1$s ≈ %2$s.\nÚj árfolyam: %1$s ≈ %3$s + Frissült a csere árfolyam + Művelet megismétlése + Útvonal + Az út, amelyen a tokenje különböző hálózatokon megy át, hogy megkapja a kívánt tokent. + Csere + Átutalás + Csere beállítások + Csúszás + A decentralizált cserekereskedésben gyakori jelenség a csúsztatás. A változó piaci feltételek miatt a végső tranzakció ára eltérhet a várt piaci ártól. + Másik érték megadása + Az érték %s és %s között kell legyen + Csúszás + A tranzakció a magas csúszás miatt lehet, hogy hamarabb for lefutni. + A tranzakció az alacsony csúszás miatt lehet, hogy vissza lesz fordítva. + A(z) %s mennyisége kevesebb, mint a(z) %s minimális egyenlege + Túl alacsony összeget próbálsz átváltani + Tartózkodik: %s + Igen: %s + Mindig szavazhat erre a referendumra később + Eltávolítás a szavazási listáról a referendum %s? + Néhány referendum már nem elérhető szavazásra, vagy lehet, hogy nincs elegendő token szavazáshoz. Elérhető szavazásra: %s. + Néhány referendum kizárva a szavazási listáról + A referendum adatai nem tölthetők be + Nincs adat lekérve + Ön már az összes elérhető referendumra szavazott, vagy jelenleg nincs szavazásra rendelkezésre álló referendum. Térjen vissza később. + Ön már az összes elérhető referendumra szavazott + Kért: + Szavazási lista + %d hátra + Szavazatok megerősítése + Nincs szavazásra váró referendum + Erősítse meg szavazatait + Nincsenek szavazatok + Sikeresen szavazott %d referendumra + Nincs elegendő egyenlege ahhoz, hogy szavazzon a jelenlegi szavazati erővel %s (%sx). Kérjük, változtassa meg a szavazati erőt vagy adjon hozzá több pénzt a pénztárcájához. + Nincs elegendő egyenleg a szavazáshoz + Ellene: %s + SwipeGov + Szavazzon %d népszavazásra + A szavazás a jövőbeni szavazásokra lesz beállítva a SwipeGovban + Szavazati erő + Stake-elés + Tárca + Ma + Coingecko hivatkozás az árakhoz (opcionális) + Hash másolása + Díj + Innen + Extrinsic Hash + Átutalás részletei + Megtekintés itt: %s + Megtekintés Polkascan-en + Megtekintés Subscan-en + %s - %s-kor + Befejezett + Sikertelen + Függőben + Tranzakciók Értesítési Csatornája + Feladó: %s + Címzett: %s + Átutalás + Itt fognak megjelenni a\nbejövő és kimenő utalások + Itt fognak megjelenni a műveleteid + Szavazatok eltávolítása delegáláshoz a következő kategóriákban + Kategóriák melyekben már delegáltál szavazatokat + Nem elérhető kategóriák + Kategóriák melyekben már szavaztál + Telepítés + Verzió %s + Frissítés érhető el + A problémák elkerülése és a felhasználói élmény javítása érdekében erősen ajánljuk, hogy a legújabb frissítéseket mielőbb telepítsd + Kritikus frissítés + Legújabb + A Nova Wallet számos izgalmas új funkciót kínál! Ne felejtsd el frissíteni az alkalmazását, hogy hozzáférj ezekhez + Jelentős frissítés + Kritikus + Jelentős + Az összes elérhető frissítés megtekintése + Név + Tárca neve + A név kizárólag csak számodra lesz látható és csakis ezen eszközön lesz tárolva. + Ezt a fiókot a hálózat nem választotta meg a jelenlegi korszakban való részvételre. + Újraszavaz + Szavazás + Szavazás állapota + Vétel + Vásárlás a következővel + Fogadás + %s fogadása + Csak %1$s tokent és a %2$s hálózatban lévő tokeneket küldjön erre a címre, különben elveszítheti a pénzét + Küldés + Váltás + Eszközök + Itt fognak megjelenni eszközeid.\nGyőződj meg róla, hogy a \"Nulla egyenlegűek elrejtése\"\n szűrő ki van kapcsolva. + Eszközök értéke + Elérhető + Stake-elt + Egyenleg részletei + Teljes egyenleg + Összesen az átutalás után + Lefagyasztott + Zárolt + Kiváltható + Fenntartott + Átutalható + Unstake-elés + Tárca + Új kapcsolat + + %s fiók hiányzik. Add hozzá a tárca beállításaiban + %s fiókok hiányoznak. Add hozzá őket a tárca beállításaiban + + A \\" %s \\" által kért egyes hálózatok nem támogatottak a Nova Walletben + Itt fognak megjelenni a WalletConnect munkamenetek + WalletConnect + Ismeretlen DApp + + %s nem támogatott hálózat el van rejtve + %s nem támogatott hálózat el van rejtve + + WalletConnect v2 + Láncok közötti átutalás + Kriptovaluták + Fiat valuták + Népszerű fiat valuták + Valuta + Extrinsic részletei + Nulla egyenlegűek elrejtése + Egyéb tranzakciók + Mutat + Jutalmak és Büntetések + Átváltások + Szűrők + Átutalások + Eszközök kezelése + Hogyan lehet pénztárcát hozzáadni? + Hogyan lehet pénztárcát hozzáadni? + Hogyan lehet pénztárcát hozzáadni? + Példák névre: Fő számla, Saját validátor, Dotsama közösségi hitelek stb. + QR-kód megosztása a küldővel + A QR-kódot a feladó beolvashatja + A(z) %s címem %s fogadásához + QR-kód megosztása + Címzett + Győződj meg róla, hogy a cím\na megfelelő hálózatból származik + A cín formátuma érvénytelen.\nGyőzödj meg róla, hogy a cím\na megfelelő hálózatból származik + Minimális egyenleg + A láncok közötti korlátozások miatt nem lehet többet átutalni, mint %s + Az egyenleged nem elegendő a(z) %s láncok közötti díj kifizetéséhez. \nFennmaradó egyenleged a díj megfizetése után: %s + A beírt összegen felül a láncok közötti díj is felszámolásra kerül. A címzett megkaphatja a láncok közötti díj egy részét + Átutalás megerősítése + Láncok között + Láncok közötti díj + Utalásod sikertelen lesz, mivel a célfiók nem rendelkezik elegendő %s tokennel, ahhoz hogy egyéb tokeneket fogadhasson + A címzett nem tudja elfogadni az átutalást + Az átutalás sikertelen lesz, mivel a célszámlán lévő végösszeg kisebb lesz, mint a minimális egyenleg. Kérlek, próbáld meg növelni az összeget. + Az utalás eltávolítja a fiókot a blokkláncról, mivel a teljes egyenleg kevesebb lesz a minimálisan szükségenél. + A fiókod el lesz távolítva a blokkláncról az utalás után, mivel a fennmaradó egyenleg kevesebb lesz, mint a minimum + Az utalás el fogja távolítani a fiókot + A fiókod el lesz távolítva a blokkláncról az utalás után, mivel a fennmaradó egyenleg kevesebb lesz, mint a minimum. A fennmaradó egyenleget szintén a címzett fogja megkapni. + Hálózatról + Legalább %s kell ahhoz, hogy kifizesd a tranzakciós díjat és, hogy a hálozat által megszabott minimális egyenleg felett maradj. A jelenlegi egyenleged: %s. A művelet végrehajtásához kell még %s. + Magamnak + Láncon + A következő cím: %s köztudottan adathalász tevékenységekhez használatos, ezért nem javasoljuk tokenek küldését erre a címre. Mindenképpen folytatni szeretnéd? + Átverési figyelmeztetés + A címzettet a token tulajdonosa letiltotta, jelenleg nem tud bejövő utalásokat fogadni + A címzett nem tud átutalást fogadni + Célhálózat + Hálózatra + %s küldése innen: + %s küldése ezen: + ide: + Feladó + Tokenek + Küldés ennek a kapcsolatnak + Átutalás részletei + Egyenleged + %s (%s) + %s címek a következőhöz: %s + A Nova problémákat észlet bizonyos %1$s címek integritásával kapcsolatban. Kérlek, vedd fel a kapcsolat a(z) %1$s tulajdonosával az integritási problémák kiküszöbölése érdekében. + Az integritás ellenőrzése sikertelen + Érvénytelen címzett + Nem található érvényes cím a(z) %s névhez a(z) %s hálózaton + A(z) %s nem található + A %1$s w3n szolgáltatások nem elérhetőek. Próbáld meg később vagy add meg a(z) %1$s címet manuálisan. + Sikertelen w3n keresés + A %s token kód nem értelmezhető Nova számára + %s token jelenleg nem támogatott + Tegnap + A Yield Boost ki lesz kapcsolva a jelenlegi kollátornál. Új kollátor: %s + Lecseréljük a Yield Boost kollátort? + Az egyenleged nem elegendő a(z) %s hálózati díj kifizetéséhez és a(z) %s Yield Boost indítási díjához. \nElérhető egyenleged a díj megfizetéséhez: %s + Nincs elegendő token az első végrehajtási díj megfizetéséhez + Az egyenleged nem elegendő a(z) %s hálózati díj kifizetéséhez és hogy ne essen a(z) %s küszöb alá. \nElérhető egyenleged a díj megfizetéséhez: %s + Nincs elegendő token a küszöb felett maradáshoz + Stake növekedési idő + A Yield Boost %s automatikusan stake-elni fogja a %s feletti utalható tokeneket + Yield Boost-olt + diff --git a/common/src/main/res/values-in/strings.xml b/common/src/main/res/values-in/strings.xml index debfdd123c..392bc36cf5 100644 --- a/common/src/main/res/values-in/strings.xml +++ b/common/src/main/res/values-in/strings.xml @@ -241,6 +241,7 @@ Cadangan Cadangan Otentikasi Biometrik + Tutup Semua Pembelian dimulai! Harap tunggu hingga 60 menit. Anda dapat melacak statusnya melalui email. Pilih jaringan untuk membeli %s Untuk melanjutkan pembelian, Anda akan diarahkan dari aplikasi Nova Wallet ke %s @@ -257,6 +258,8 @@ Biaya transfer Jaringan tidak merespons Ke + Semua tab yang terbuka di browser DApp akan ditutup. + Tutup Semua DApps? Lakukan %s dan selalu simpan offline untuk memulihkannya kapan saja. Anda dapat melakukannya di Pengaturan Cadangan. Harap catat semua Frasa Sandi dompet Anda sebelum melanjutkan Cadangan akan dihapus dari Google Drive @@ -475,6 +478,7 @@ %d detik Jalur derivasi rahasia + Lihat Semua Pengaturan Bagikan Masuk @@ -636,6 +640,8 @@ Setujui permintaan ini jika Anda mempercayai aplikasi ini.\nPeriksa detail transaksi. DApp DApps + %d DApps + Favorit Favorit Tambahkan ke favorit DApp \"%s\" akan dihapus dari Favorit diff --git a/common/src/main/res/values-it/strings.xml b/common/src/main/res/values-it/strings.xml index 2af74e3cb8..53fb257c19 100644 --- a/common/src/main/res/values-it/strings.xml +++ b/common/src/main/res/values-it/strings.xml @@ -241,6 +241,7 @@ Backup Backup Autenticazione biometrica + Chiudi tutto Acquisto iniziato! Attendi fino a 60 minuti. Puoi controllare lo stato sulla email. Seleziona la rete per l\'acquisto di %s Per continuare l\'acquisto sarai reindirizzato dall\'app Nova Wallet a %s @@ -257,6 +258,8 @@ Commissione di trasferimento Rete non risponde A + Tutte le schede aperte nel browser DApp saranno chiuse. + Chiudere tutte le DApp? %s e ricorda di tenerle sempre offline per poterle ripristinare in qualsiasi momento. Puoi fare questo nelle Impostazioni di Backup. Per favore scrivi tutte le Passphrase del tuo portafoglio prima di procedere. Il backup sarà eliminato da Google Drive @@ -480,6 +483,7 @@ %d secondi Percorso di derivazione segreto + Vedi tutto Impostazioni Condividi Accedi @@ -642,6 +646,8 @@ Approva questa richiesta se ti fidi dell\'applicazione.\nControlla i dettagli della transazione. DApp DApp + %d DApp + Preferiti Preferiti Aggiungi ai preferiti \"%s\" DApp verrà rimosso dai Preferiti diff --git a/common/src/main/res/values-ja/strings.xml b/common/src/main/res/values-ja/strings.xml index 70b079d07b..b5e7ea2177 100644 --- a/common/src/main/res/values-ja/strings.xml +++ b/common/src/main/res/values-ja/strings.xml @@ -241,6 +241,7 @@ バックアップ バックアップ 生体認証 + すべて閉じる 購入が開始されました! 最大60分お待ちください。メールでステータスを追跡できます。 購入するネットワークを選択 %s 購入を続行するために、Nova Walletアプリから%sにリダイレクトされます。 @@ -257,6 +258,8 @@ 転送手数料 ネットワークが応答していません 宛先 + DAppブラウザーで開いているすべてのタブが閉じられます。 + すべてのDAppを閉じますか? %s と、いつでも復元できるように必ずオフラインで保持してください。バックアップ設定でこれを行うことができます。 進む前にすべてのウォレットのパスフレーズを書き留めてください Googleドライブからバックアップが削除されます @@ -475,6 +478,7 @@ %d秒 シークレット導出パス + すべて見る 設定 共有 サインイン @@ -636,6 +640,8 @@ アプリケーションを信頼する場合はこのリクエストを承認してください。\nトランザクションの詳細を確認してください。 DApp DApps + %d DApps + お気に入り お気に入り お気に入りに追加 「%s」DAppはお気に入りから削除されます diff --git a/common/src/main/res/values-ko/strings.xml b/common/src/main/res/values-ko/strings.xml index f53c55c062..c4c98516cb 100644 --- a/common/src/main/res/values-ko/strings.xml +++ b/common/src/main/res/values-ko/strings.xml @@ -241,6 +241,7 @@ 백업 백업 생체 인증 + 모두 닫기 구매가 시작되었습니다! 최대 60분까지 기다려주세요. 이메일에서 상태를 추적할 수 있습니다. %s 구매를 위한 네트워크 선택 구매를 계속하려면 Nova Wallet 앱에서 %s로 리디렉션됩니다. @@ -257,6 +258,8 @@ 전송 수수료 네트워크가 응답하지 않습니다 받는 사람 + DApp 브라우저의 모든 열린 탭이 닫힙니다. + 모든 DApp 닫기? %s 및 언제든지 복원할 수 있도록 항상 오프라인으로 보관하십시오. 백업 설정에서 이 작업을 수행할 수 있습니다. 진행하기 전에 모든 지갑의 암호 구문을 적어 두십시오 Google 드라이브에서 백업이 삭제됩니다 @@ -475,6 +478,7 @@ %d초 비밀 경로 도출 + 모두 보기 설정 공유 로그인 @@ -636,6 +640,8 @@ 애플리케이션을 신뢰하는 경우 이 요청을 승인하십시오.\n거래 세부 정보를 확인하십시오. DApp DApps + %d DApps + 즐겨찾기 즐겨찾기 즐겨찾기에 추가 “%s” DApp이(가) 즐겨찾기에서 제거됩니다. diff --git a/common/src/main/res/values-pl/strings.xml b/common/src/main/res/values-pl/strings.xml index 5ae357680d..d5746a33a1 100644 --- a/common/src/main/res/values-pl/strings.xml +++ b/common/src/main/res/values-pl/strings.xml @@ -241,6 +241,7 @@ Kopia zapasowa Kopia zapasowa Autoryzacja biometryczna + Zamknij wszystkie Zakup zainicjowany! Proszę czekać do 60 minut. Możesz śledzić status na e-mail. Wybierz sieć do kupna %s Aby kontynuować zakup, zostaniesz przekierowany z aplikacji Nova Wallet na %s @@ -257,6 +258,8 @@ Opłata transferowa Sieć nie odpowiada Do + Wszystkie otwarte karty w przeglądarce DApp zostaną zamknięte. + Zamknąć wszystkie DAppy? %s i pamiętaj, aby zawsze trzymać je w formie pisemnej, aby móc je odzyskać w każdej chwili. Możesz to zrobić w ustawieniach kopii zapasowej. Proszę zapisz wszystkie Passphrases do swoich portfeli przed kontynuacją Kopia zapasowa zostanie usunięta z Google Drive @@ -485,11 +488,12 @@ sek %d sekunda - - + %d sekunda + %d sekunda %d sekund Ścieżka pochodzenia sekretu + Zobacz wszystkie Ustawienia Udostępnij Zaloguj się @@ -654,6 +658,8 @@ Zatwierdź to żądanie, jeśli ufasz aplikacji. \nSprawdź szczegóły transakcji. DApp DApps + %d DAppy + Ulubione Ulubione Dodaj do ulubionych „%s” DApp zostanie usunięty z Ulubionych diff --git a/common/src/main/res/values-pt/strings.xml b/common/src/main/res/values-pt/strings.xml index 709d602585..c5baf86593 100644 --- a/common/src/main/res/values-pt/strings.xml +++ b/common/src/main/res/values-pt/strings.xml @@ -241,6 +241,7 @@ Backup Backup Autenticação biométrica + Fechar Tudo Compra iniciada! Por favor, aguarde até 60 minutos. Você pode rastrear o status pelo email. Selecione a rede para comprar %s Para continuar a compra você será redirecionado do aplicativo Nova Wallet para %s @@ -257,6 +258,8 @@ Taxa de transferência Rede não está respondendo Para + Todas as abas abertas no navegador DApp serão fechadas. + Fechar Todos os DApps? %s e lembre-se de sempre mantê-los offline para restaurá-los a qualquer momento. Você pode fazer isso nas Configurações de Backup. Por favor, anote todas as Frases de Segurança da sua carteira antes de prosseguir O backup será excluído do Google Drive @@ -480,6 +483,7 @@ %d segundos Sequência de derivação secreta + Ver Tudo Configurações Compartilhar Entrar @@ -642,6 +646,8 @@ Aprove esta solicitação se você confia no aplicativo.\nVerifique os detalhes da transação. DApp DApps + %d DApps + Favoritos Favoritos Adicionar aos favoritos O DApp “%s” será removido dos Favoritos diff --git a/common/src/main/res/values-ru/strings.xml b/common/src/main/res/values-ru/strings.xml index 25da573bb4..a4a8fc1a14 100644 --- a/common/src/main/res/values-ru/strings.xml +++ b/common/src/main/res/values-ru/strings.xml @@ -241,6 +241,7 @@ Бэкап Бэкап Биометрия + Закрыть все Покупка совершена! Ожидайте до 60 минут. Вы можете отслеживать статус по электронной почте. Выберите сеть для покупки %s Для продолжения покупки вы будете перенаправлены из приложения Nova Wallet на сайт %s @@ -257,6 +258,8 @@ Комиссия Сеть недоступна Кому + Все открытые вкладки в DApp браузере будут закрыты. + Закрыть все DApps? %s и не забывайте всегда держать их в письменном виде, чтобы иметь возможность восстановить в любое время. Сделать это можно в Настройках Бэкапа. Пожалуйста, запишите секретные фразы от всех ваших кошельков перед тем как продолжить Бэкап будет удалён из Google Диска @@ -485,11 +488,12 @@ сек %d секунда - - + %d секунды + %d секунд %d секунд Последовательность для вывода + Посмотреть все Настройки Поделиться Войти @@ -654,6 +658,8 @@ Одобрите этот запрос, если вы доверяете приложению.\nПроверьте детали транзакции. DApp DApps + %d DApps + Избранное Избранное Добавить в избранное ”%s” DApp будет удален из Избранного diff --git a/common/src/main/res/values-tr/strings.xml b/common/src/main/res/values-tr/strings.xml index 6129687c0f..0c1ce16bd7 100644 --- a/common/src/main/res/values-tr/strings.xml +++ b/common/src/main/res/values-tr/strings.xml @@ -241,6 +241,7 @@ Yedekleme Yedekleme Biyometrik Doğrulama + Hepsini Kapat Satın alma başlatıldı! Lütfen 60 dakika kadar bekleyin. Durumu e-postadan takip edebilirsiniz. Satın alma için ağ seçin %s Satın alma işlemine devam etmek için Nova Wallet uygulamasından %s\'a yönlendirileceksiniz @@ -257,6 +258,8 @@ Transfer ücreti Ağ yanıt vermiyor Kime + DApp tarayıcısındaki tüm açık sekmeler kapatılacak. + Tüm DApp\'leri Kapat? %s ve her zaman çevrimdışı tutup istediğiniz zaman geri yükleyebilmek için hatırlayın. Bunu Yedekleme Ayarlarında yapabilirsiniz. Lütfen devam etmeden önce tüm cüzdan şifre kelimelerinizi not edin. Yedek Google Drive\'dan silinecek @@ -480,6 +483,7 @@ %d saniye Gizli türetme yolu + Hepsini Gör Ayarlar Paylaş Giriş Yap @@ -642,6 +646,8 @@ Uygulamaya güveniyorsanız bu isteği onaylayın.\\nİşlem detaylarını kontrol edin. DApp DApp\'ler + %d DApp + Favoriler Favoriler Favorilere Ekle ‘%s’ DApp Favorilerden kaldırılacak diff --git a/common/src/main/res/values-vi/strings.xml b/common/src/main/res/values-vi/strings.xml index f98dab1519..8027bca163 100644 --- a/common/src/main/res/values-vi/strings.xml +++ b/common/src/main/res/values-vi/strings.xml @@ -241,6 +241,7 @@ Sao lưu Sao lưu Xác thực sinh trắc học + Đóng tất cả Mua hàng đã được khởi tạo! Vui lòng đợi tối đa 60 phút. Bạn có thể theo dõi trạng thái trong email. Chọn mạng để mua %s Để tiếp tục mua hàng, bạn sẽ được chuyển hướng từ ứng dụng Nova Wallet tới %s @@ -257,6 +258,8 @@ Phí chuyển khoản Mạng không phản hồi Đến + Tất cả các tab mở trong trình duyệt DApp sẽ bị đóng. + Đóng tất cả DApps? %s và nhớ luôn giữ chúng ngoại tuyến để khôi phục bất cứ lúc nào. Bạn có thể thực hiện việc này trong Cài đặt Sao lưu. Vui lòng viết ra tất cả Passphrases của ví trước khi tiếp tục Sao lưu sẽ bị xóa khỏi Google Drive @@ -475,6 +478,7 @@ %d giây Đường dẫn dẫn xuất bí mật + Xem tất cả Cài đặt Chia sẻ Đăng nhập @@ -636,6 +640,8 @@ Phê duyệt yêu cầu này nếu bạn tin tưởng ứng dụng.\nKiểm tra chi tiết giao dịch. DApp DApps + %d DApps + Yêu thích Yêu thích Thêm vào yêu thích DApp “%s” sẽ bị xóa khỏi Yêu thích diff --git a/common/src/main/res/values-zh-rCN/strings.xml b/common/src/main/res/values-zh-rCN/strings.xml index 4460344fb4..cf2dfbb850 100644 --- a/common/src/main/res/values-zh-rCN/strings.xml +++ b/common/src/main/res/values-zh-rCN/strings.xml @@ -241,6 +241,7 @@ 备份 备份 生物认证 + 关闭所有 购买已启动!请等待最多 60 分钟。你可以在电子邮件上追踪状态。 选择购买%s的网络 为了继续购买,你将从 Nova Wallet 应用被重定向到 %s @@ -257,6 +258,8 @@ 转账费 网络无响应 + DApp浏览器中所有打开的标签页将被关闭。 + 关闭所有DApps? %s 并且记住始终将它们离线保存,以便随时恢复它们。您可以在备份设置中执行此操作。 请在继续之前记下您钱包的所有助记词 备份将从Google Drive中删除 @@ -475,6 +478,7 @@ %d 秒 密钥派生路径 + 查看全部 设置 分享 登录 @@ -636,6 +640,8 @@ 如果您信任此应用,请批准此请求。\n检查交易详情。 DApp DApps + %d DApps + 收藏夹 收藏夹 添加到收藏夹 “%s”DApp将从收藏夹中移除 diff --git a/common/src/main/res/values/colors.xml b/common/src/main/res/values/colors.xml index 450bf10f81..f0b75cd3e4 100644 --- a/common/src/main/res/values/colors.xml +++ b/common/src/main/res/values/colors.xml @@ -52,6 +52,7 @@ #E0FFFFFF + #05081C #52FFFFFF #29FFFFFF #7AFFFFFF @@ -62,6 +63,7 @@ #E53450 #E53450 #2FC864 + #8E8F9A #08090E @@ -79,6 +81,7 @@ #5208090E #1A999EC7 #FFFFFF + #E0FFFFFF #3D999EC7 #3D999EC7 @@ -89,6 +92,7 @@ #1FEDCE36 #1F2AB0F2 #66151D27 + #CC151D27 #0F111A #7A08090E #3D999EC7 diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index 26d5a6471e..193dea3ed4 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -40,6 +40,15 @@ The way that your token will take through different networks to get the desired token. Route + See All + Favorites + %d DApps + + Close All DApps? + All opened tabs in DApp browser will be closed. + + Close All + Select network Search by token diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/AppDatabase.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/AppDatabase.kt index 5a315b581b..6df66245d4 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/AppDatabase.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/AppDatabase.kt @@ -21,6 +21,7 @@ import io.novafoundation.nova.core_db.dao.AccountDao import io.novafoundation.nova.core_db.dao.AccountStakingDao import io.novafoundation.nova.core_db.dao.AssetDao import io.novafoundation.nova.core_db.dao.BrowserHostSettingsDao +import io.novafoundation.nova.core_db.dao.BrowserTabsDao import io.novafoundation.nova.core_db.dao.ChainAssetDao import io.novafoundation.nova.core_db.dao.ChainDao import io.novafoundation.nova.core_db.dao.CoinPriceDao @@ -49,6 +50,8 @@ import io.novafoundation.nova.core_db.migrations.AddAdditionalFieldToChains_12_1 import io.novafoundation.nova.core_db.migrations.AddBalanceHolds_60_61 import io.novafoundation.nova.core_db.migrations.AddBalanceModesToAssets_51_52 import io.novafoundation.nova.core_db.migrations.AddBrowserHostSettings_34_35 +import io.novafoundation.nova.core_db.migrations.AddBrowserTabs_64_65 +import io.novafoundation.nova.core_db.migrations.AddFavoriteDAppsOrdering_65_66 import io.novafoundation.nova.core_db.migrations.AddBuyProviders_7_8 import io.novafoundation.nova.core_db.migrations.AddChainColor_4_5 import io.novafoundation.nova.core_db.migrations.AddChainForeignKeyForProxy_63_64 @@ -114,6 +117,7 @@ import io.novafoundation.nova.core_db.model.AssetLocal import io.novafoundation.nova.core_db.model.BalanceHoldLocal import io.novafoundation.nova.core_db.model.BalanceLockLocal import io.novafoundation.nova.core_db.model.BrowserHostSettingsLocal +import io.novafoundation.nova.core_db.model.BrowserTabLocal import io.novafoundation.nova.core_db.model.CoinPriceLocal import io.novafoundation.nova.core_db.model.ContributionLocal import io.novafoundation.nova.core_db.model.CurrencyLocal @@ -151,7 +155,7 @@ import io.novafoundation.nova.core_db.model.operation.SwapTypeLocal import io.novafoundation.nova.core_db.model.operation.TransferTypeLocal @Database( - version = 64, + version = 66, entities = [ AccountLocal::class, NodeLocal::class, @@ -193,7 +197,8 @@ import io.novafoundation.nova.core_db.model.operation.TransferTypeLocal BalanceHoldLocal::class, NodeSelectionPreferencesLocal::class, TinderGovBasketItemLocal::class, - TinderGovVotingPowerLocal::class + TinderGovVotingPowerLocal::class, + BrowserTabLocal::class ], ) @TypeConverters( @@ -249,7 +254,8 @@ abstract class AppDatabase : RoomDatabase() { .addMigrations(AddFungibleNfts_55_56, ChainPushSupport_56_57) .addMigrations(AddLocalMigratorVersionToChainRuntimes_57_58, AddGloballyUniqueIdToMetaAccounts_58_59) .addMigrations(ChainNetworkManagement_59_60, AddBalanceHolds_60_61, ChainNetworkManagement_61_62) - .addMigrations(TinderGovBasket_62_63, AddChainForeignKeyForProxy_63_64) + .addMigrations(TinderGovBasket_62_63, AddChainForeignKeyForProxy_63_64, AddBrowserTabs_64_65) + .addMigrations(AddFavoriteDAppsOrdering_65_66) .build() } return instance!! @@ -311,4 +317,6 @@ abstract class AppDatabase : RoomDatabase() { abstract fun holdsDao(): HoldsDao abstract fun tinderGovDao(): TinderGovDao + + abstract fun browserTabsDao(): BrowserTabsDao } diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/dao/BrowserTabsDao.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/dao/BrowserTabsDao.kt new file mode 100644 index 0000000000..42b1c07c52 --- /dev/null +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/dao/BrowserTabsDao.kt @@ -0,0 +1,44 @@ +package io.novafoundation.nova.core_db.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import androidx.room.Transaction +import io.novafoundation.nova.core_db.model.BrowserTabLocal +import kotlinx.coroutines.flow.Flow + +@Dao +abstract class BrowserTabsDao { + + @Query("SELECT id FROM browser_tabs WHERE metaId = :metaId") + abstract fun getTabIdsFor(metaId: Long): List + + @Query("SELECT * FROM browser_tabs WHERE metaId = :metaId ORDER BY creationTime DESC") + abstract fun observeTabsByMetaId(metaId: Long): Flow> + + @Insert(onConflict = OnConflictStrategy.REPLACE) + abstract suspend fun insertTab(tab: BrowserTabLocal) + + @Transaction + open suspend fun removeTabsByMetaId(metaId: Long): List { + val tabIds = getTabIdsFor(metaId) + removeTabsByIds(tabIds) + return tabIds + } + + @Query("DELETE FROM browser_tabs WHERE id = :tabId") + abstract suspend fun removeTab(tabId: String) + + @Query("DELETE FROM browser_tabs WHERE id IN (:tabIds)") + abstract suspend fun removeTabsByIds(tabIds: List) + + @Query("UPDATE browser_tabs SET pageName = :pageName, pageIconPath = :pageIconPath, pagePicturePath = :pagePicturePath WHERE id = :tabId") + abstract suspend fun updatePageSnapshot(tabId: String, pageName: String?, pageIconPath: String?, pagePicturePath: String?) + + @Query("UPDATE browser_tabs SET currentUrl = :url WHERE id = :tabId") + abstract fun updateCurrentUrl(tabId: String, url: String) + + @Query("UPDATE browser_tabs SET dappMetadata_iconLink = :dappIconUrl WHERE id = :tabId") + abstract fun updateKnownDAppMetadata(tabId: String, dappIconUrl: String?) +} diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/dao/FavouriteDAppsDao.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/dao/FavouriteDAppsDao.kt index 824d9314bf..e72c02b16f 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/dao/FavouriteDAppsDao.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/dao/FavouriteDAppsDao.kt @@ -4,6 +4,7 @@ import androidx.room.Dao import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query +import androidx.room.Update import io.novafoundation.nova.core_db.model.FavouriteDAppLocal import kotlinx.coroutines.flow.Flow @@ -24,4 +25,10 @@ interface FavouriteDAppsDao { @Query("DELETE FROM favourite_dapps WHERE url = :dAppUrl") suspend fun deleteFavouriteDApp(dAppUrl: String) + + @Update(onConflict = OnConflictStrategy.REPLACE) + suspend fun updateFavourites(dapps: List) + + @Query("SELECT MAX(orderingIndex) FROM favourite_dapps") + suspend fun getMaxOrderingIndex(): Int } diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/di/DbApi.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/di/DbApi.kt index 43c6f9ed94..0d46e10a2f 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/di/DbApi.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/di/DbApi.kt @@ -5,6 +5,7 @@ import io.novafoundation.nova.core_db.dao.AccountDao import io.novafoundation.nova.core_db.dao.AccountStakingDao import io.novafoundation.nova.core_db.dao.AssetDao import io.novafoundation.nova.core_db.dao.BrowserHostSettingsDao +import io.novafoundation.nova.core_db.dao.BrowserTabsDao import io.novafoundation.nova.core_db.dao.ChainAssetDao import io.novafoundation.nova.core_db.dao.ChainDao import io.novafoundation.nova.core_db.dao.CoinPriceDao @@ -32,6 +33,20 @@ import io.novafoundation.nova.core_db.dao.WalletConnectSessionsDao interface DbApi { + val phishingSitesDao: PhishingSitesDao + + val favouritesDAppsDao: FavouriteDAppsDao + + val currencyDao: CurrencyDao + + val walletConnectSessionsDao: WalletConnectSessionsDao + + val stakingDashboardDao: StakingDashboardDao + + val externalBalanceDao: ExternalBalanceDao + + val holdsDao: HoldsDao + fun provideDatabase(): AppDatabase fun provideLockDao(): LockDao @@ -76,17 +91,5 @@ interface DbApi { fun tinderGovDao(): TinderGovDao - val phishingSitesDao: PhishingSitesDao - - val favouritesDAppsDao: FavouriteDAppsDao - - val currencyDao: CurrencyDao - - val walletConnectSessionsDao: WalletConnectSessionsDao - - val stakingDashboardDao: StakingDashboardDao - - val externalBalanceDao: ExternalBalanceDao - - val holdsDao: HoldsDao + fun browserTabsDao(): BrowserTabsDao } diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/di/DbModule.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/di/DbModule.kt index e497cbbb8b..cbd560896b 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/di/DbModule.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/di/DbModule.kt @@ -9,6 +9,7 @@ import io.novafoundation.nova.core_db.dao.AccountDao import io.novafoundation.nova.core_db.dao.AccountStakingDao import io.novafoundation.nova.core_db.dao.AssetDao import io.novafoundation.nova.core_db.dao.BrowserHostSettingsDao +import io.novafoundation.nova.core_db.dao.BrowserTabsDao import io.novafoundation.nova.core_db.dao.ChainAssetDao import io.novafoundation.nova.core_db.dao.ChainDao import io.novafoundation.nova.core_db.dao.CoinPriceDao @@ -212,4 +213,10 @@ class DbModule { fun provideTinderGovDao(appDatabase: AppDatabase): TinderGovDao { return appDatabase.tinderGovDao() } + + @Provides + @ApplicationScope + fun provideBrowserTabsDao(appDatabase: AppDatabase): BrowserTabsDao { + return appDatabase.browserTabsDao() + } } diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/migrations/64_65_AddBrowserTabs.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/migrations/64_65_AddBrowserTabs.kt new file mode 100644 index 0000000000..3a94d171f5 --- /dev/null +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/migrations/64_65_AddBrowserTabs.kt @@ -0,0 +1,25 @@ +package io.novafoundation.nova.core_db.migrations + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +val AddBrowserTabs_64_65 = object : Migration(64, 65) { + + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL( + """ + CREATE TABLE IF NOT EXISTS `browser_tabs` ( + `id` TEXT NOT NULL, + `metaId` INTEGER NOT NULL, + `currentUrl` TEXT NOT NULL, + `creationTime` INTEGER NOT NULL, + `pageName` TEXT, `pageIconPath` TEXT, + `pagePicturePath` TEXT, + `dappMetadata_iconLink` TEXT, + PRIMARY KEY(`id`), + FOREIGN KEY(`metaId`) REFERENCES `meta_accounts`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE + ) + """ + ) + } +} diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/migrations/65_66_AddFavoritesOrdering.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/migrations/65_66_AddFavoritesOrdering.kt new file mode 100644 index 0000000000..c74f41f53a --- /dev/null +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/migrations/65_66_AddFavoritesOrdering.kt @@ -0,0 +1,11 @@ +package io.novafoundation.nova.core_db.migrations + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +val AddFavoriteDAppsOrdering_65_66 = object : Migration(65, 66) { + + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("ALTER TABLE favourite_dapps ADD COLUMN orderingIndex INTEGER NOT NULL DEFAULT 0") + } +} diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/model/BrowserTabLocal.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/model/BrowserTabLocal.kt new file mode 100644 index 0000000000..e6fd61bebd --- /dev/null +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/model/BrowserTabLocal.kt @@ -0,0 +1,33 @@ +package io.novafoundation.nova.core_db.model + +import androidx.room.Embedded +import androidx.room.Entity +import androidx.room.ForeignKey +import androidx.room.PrimaryKey +import io.novafoundation.nova.core_db.model.chain.account.MetaAccountLocal + +@Entity( + tableName = "browser_tabs", + foreignKeys = [ + ForeignKey( + entity = MetaAccountLocal::class, + parentColumns = ["id"], + childColumns = ["metaId"], + onDelete = ForeignKey.CASCADE + ) + ] +) +data class BrowserTabLocal( + @PrimaryKey(autoGenerate = false) val id: String, + val metaId: Long, + val currentUrl: String, + val creationTime: Long, + val pageName: String?, + val pageIconPath: String?, + @Embedded(prefix = "dappMetadata_") + val knownDAppMetadata: KnownDAppMetadata?, + val pagePicturePath: String? +) { + + class KnownDAppMetadata(val iconLink: String) +} diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/model/FavouriteDAppLocal.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/model/FavouriteDAppLocal.kt index 18d04d6197..bbc5f55388 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/model/FavouriteDAppLocal.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/model/FavouriteDAppLocal.kt @@ -1,12 +1,21 @@ package io.novafoundation.nova.core_db.model +import androidx.room.ColumnInfo import androidx.room.Entity +import androidx.room.Ignore import androidx.room.PrimaryKey +import io.novafoundation.nova.common.utils.Identifiable @Entity(tableName = "favourite_dapps") class FavouriteDAppLocal( @PrimaryKey val url: String, val label: String, - val icon: String? -) + val icon: String?, + @ColumnInfo(defaultValue = "0") + val orderingIndex: Int +) : Identifiable { + + @Ignore + override val identifier: String = url +} diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/chain/ChainUi.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/chain/ChainUi.kt index dc883f4cc6..031a2b4443 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/chain/ChainUi.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/chain/ChainUi.kt @@ -12,6 +12,7 @@ import io.novafoundation.nova.common.presentation.fallbackIcon import io.novafoundation.nova.common.presentation.getAssetIconOrFallback import io.novafoundation.nova.common.utils.images.Icon import io.novafoundation.nova.common.utils.images.asIcon +import io.novafoundation.nova.common.utils.images.asUrlIcon import io.novafoundation.nova.common.utils.images.setIcon import io.novafoundation.nova.feature_account_api.R import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain @@ -51,11 +52,11 @@ fun ImageView.setTokenIcon(icon: Icon, imageLoader: ImageLoader) { } fun Chain.iconOrFallback(): Icon { - return icon?.asIcon() ?: chainIconFallback() + return icon?.asUrlIcon() ?: chainIconFallback() } fun String?.asIconOrFallback(): Icon { - return this?.asIcon() ?: chainIconFallback() + return this?.asUrlIcon() ?: chainIconFallback() } fun chainIconFallback(): Icon { diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSource.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSource.kt index 38d90549a0..39c95eb93a 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSource.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSource.kt @@ -39,6 +39,7 @@ interface AccountDataSource : SecretStoreV1 { suspend fun saveSelectedAccount(account: Account) suspend fun getSelectedMetaAccount(): MetaAccount + fun selectedMetaAccountFlow(): Flow suspend fun findMetaAccount(accountId: ByteArray, chainId: ChainId): MetaAccount? diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/AccountRouter.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/AccountRouter.kt index 5f96d6195b..949242a7f1 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/AccountRouter.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/AccountRouter.kt @@ -78,8 +78,6 @@ interface AccountRouter : SecureRouter, ReturnableRouter { fun openAddLedgerChainAccountFlow(payload: AddAccountPayload.ChainAccount) - fun finishApp() - fun openCreateCloudBackupPassword(walletName: String) fun restoreCloudBackup() @@ -99,4 +97,6 @@ interface AccountRouter : SecureRouter, ReturnableRouter { fun openManualBackupSecrets(payload: ManualBackupCommonPayload) fun openManualBackupAdvancedSecrets(payload: ManualBackupCommonPayload) + + fun finishApp() } diff --git a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/flow/asset/AssetFlowFragment.kt b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/flow/asset/AssetFlowFragment.kt index 42d1891ca4..a9161b94d2 100644 --- a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/flow/asset/AssetFlowFragment.kt +++ b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/flow/asset/AssetFlowFragment.kt @@ -12,13 +12,13 @@ import io.novafoundation.nova.common.base.BaseFragment import io.novafoundation.nova.common.utils.applyStatusBarInsets import io.novafoundation.nova.common.utils.bindTo import io.novafoundation.nova.common.utils.keyboard.hideSoftKeyboard -import io.novafoundation.nova.common.utils.setVisible import io.novafoundation.nova.common.utils.keyboard.showSoftKeyboard +import io.novafoundation.nova.common.utils.setVisible import io.novafoundation.nova.common.utils.submitListPreservingViewPoint import io.novafoundation.nova.common.view.setModelOrHide import io.novafoundation.nova.feature_assets.R -import io.novafoundation.nova.feature_assets.presentation.balance.common.baseDecoration.AssetBaseDecoration import io.novafoundation.nova.feature_assets.presentation.balance.common.BalanceListAdapter +import io.novafoundation.nova.feature_assets.presentation.balance.common.baseDecoration.AssetBaseDecoration import io.novafoundation.nova.feature_assets.presentation.balance.common.baseDecoration.CompoundAssetDecorationPreferences import io.novafoundation.nova.feature_assets.presentation.balance.common.baseDecoration.NetworkAssetDecorationPreferences import io.novafoundation.nova.feature_assets.presentation.balance.common.baseDecoration.TokenAssetGroupDecorationPreferences diff --git a/feature-dapp-api/src/main/java/io/novafoundation/nova/feature_dapp_api/data/model/DappMetadata.kt b/feature-dapp-api/src/main/java/io/novafoundation/nova/feature_dapp_api/data/model/DappMetadata.kt index a134a07704..49355b7343 100644 --- a/feature-dapp-api/src/main/java/io/novafoundation/nova/feature_dapp_api/data/model/DappMetadata.kt +++ b/feature-dapp-api/src/main/java/io/novafoundation/nova/feature_dapp_api/data/model/DappMetadata.kt @@ -14,6 +14,7 @@ class DappMetadata( ) data class DappCategory( + val iconUrl: String?, val name: String, val id: String ) diff --git a/feature-dapp-api/src/main/java/io/novafoundation/nova/feature_dapp_api/data/model/SimpleTabModel.kt b/feature-dapp-api/src/main/java/io/novafoundation/nova/feature_dapp_api/data/model/SimpleTabModel.kt new file mode 100644 index 0000000000..7f9c2a7bdd --- /dev/null +++ b/feature-dapp-api/src/main/java/io/novafoundation/nova/feature_dapp_api/data/model/SimpleTabModel.kt @@ -0,0 +1,8 @@ +package io.novafoundation.nova.feature_dapp_api.data.model + +class SimpleTabModel( + val tabId: String, + val title: String?, + val knownDAppIconUrl: String?, + val faviconPath: String? +) diff --git a/feature-dapp-api/src/main/java/io/novafoundation/nova/feature_dapp_api/data/repository/BrowserTabExternalRepository.kt b/feature-dapp-api/src/main/java/io/novafoundation/nova/feature_dapp_api/data/repository/BrowserTabExternalRepository.kt new file mode 100644 index 0000000000..2321355b4e --- /dev/null +++ b/feature-dapp-api/src/main/java/io/novafoundation/nova/feature_dapp_api/data/repository/BrowserTabExternalRepository.kt @@ -0,0 +1,11 @@ +package io.novafoundation.nova.feature_dapp_api.data.repository + +import io.novafoundation.nova.feature_dapp_api.data.model.SimpleTabModel +import kotlinx.coroutines.flow.Flow + +interface BrowserTabExternalRepository { + + fun observeTabsWithNames(metaId: Long): Flow> + + suspend fun removeTabsForMetaAccount(metaId: Long): List +} diff --git a/feature-dapp-api/src/main/java/io/novafoundation/nova/feature_dapp_api/di/DAppFeatureApi.kt b/feature-dapp-api/src/main/java/io/novafoundation/nova/feature_dapp_api/di/DAppFeatureApi.kt index ff933f0858..7303c613da 100644 --- a/feature-dapp-api/src/main/java/io/novafoundation/nova/feature_dapp_api/di/DAppFeatureApi.kt +++ b/feature-dapp-api/src/main/java/io/novafoundation/nova/feature_dapp_api/di/DAppFeatureApi.kt @@ -1,8 +1,11 @@ package io.novafoundation.nova.feature_dapp_api.di +import io.novafoundation.nova.feature_dapp_api.data.repository.BrowserTabExternalRepository import io.novafoundation.nova.feature_dapp_api.data.repository.DAppMetadataRepository interface DAppFeatureApi { val dappMetadataRepository: DAppMetadataRepository + + val browserTabsRepository: BrowserTabExternalRepository } diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/addToFavourites/AddToFavouritesPayload.kt b/feature-dapp-api/src/main/java/io/novafoundation/nova/feature_dapp_api/presentation/addToFavorites/AddToFavouritesPayload.kt similarity index 71% rename from feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/addToFavourites/AddToFavouritesPayload.kt rename to feature-dapp-api/src/main/java/io/novafoundation/nova/feature_dapp_api/presentation/addToFavorites/AddToFavouritesPayload.kt index d4daf15ee6..357f24e3fc 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/addToFavourites/AddToFavouritesPayload.kt +++ b/feature-dapp-api/src/main/java/io/novafoundation/nova/feature_dapp_api/presentation/addToFavorites/AddToFavouritesPayload.kt @@ -1,4 +1,4 @@ -package io.novafoundation.nova.feature_dapp_impl.presentation.addToFavourites +package io.novafoundation.nova.feature_dapp_api.presentation.addToFavorites import android.os.Parcelable import kotlinx.android.parcel.Parcelize diff --git a/feature-dapp-api/src/main/java/io/novafoundation/nova/feature_dapp_api/presentation/browser/main/DAppBrowserPayload.kt b/feature-dapp-api/src/main/java/io/novafoundation/nova/feature_dapp_api/presentation/browser/main/DAppBrowserPayload.kt new file mode 100644 index 0000000000..49bb9dcd5f --- /dev/null +++ b/feature-dapp-api/src/main/java/io/novafoundation/nova/feature_dapp_api/presentation/browser/main/DAppBrowserPayload.kt @@ -0,0 +1,13 @@ +package io.novafoundation.nova.feature_dapp_api.presentation.browser.main + +import android.os.Parcelable +import kotlinx.android.parcel.Parcelize + +interface DAppBrowserPayload : Parcelable { + + @Parcelize + class Tab(val id: String) : DAppBrowserPayload + + @Parcelize + class Address(val address: String) : DAppBrowserPayload +} diff --git a/feature-dapp-api/src/main/java/io/novafoundation/nova/feature_dapp_api/presentation/view/DAppView.kt b/feature-dapp-api/src/main/java/io/novafoundation/nova/feature_dapp_api/presentation/view/DAppView.kt index 2b13758b32..6807a0d777 100644 --- a/feature-dapp-api/src/main/java/io/novafoundation/nova/feature_dapp_api/presentation/view/DAppView.kt +++ b/feature-dapp-api/src/main/java/io/novafoundation/nova/feature_dapp_api/presentation/view/DAppView.kt @@ -22,6 +22,7 @@ import kotlinx.android.synthetic.main.view_dapp.view.itemDAppSubtitle import kotlinx.android.synthetic.main.view_dapp.view.itemDAppSubtitleIcon import kotlinx.android.synthetic.main.view_dapp.view.itemDAppTitle import kotlinx.android.synthetic.main.view_dapp.view.itemDappAction +import kotlinx.android.synthetic.main.view_dapp.view.itemDappFavorite class DAppView @JvmOverloads constructor( context: Context, @@ -66,8 +67,8 @@ class DAppView @JvmOverloads constructor( itemDAppIcon.showDAppIcon(iconUrl, imageLoader) } - fun activateActionIcon(activate: Boolean) { - itemDappAction.isActivated = activate + fun setFavoriteIconVisible(visible: Boolean) { + itemDappFavorite.setVisible(visible) } fun enableSubtitleIcon(): ImageView { diff --git a/feature-dapp-api/src/main/res/color/dapp_category_text_tint.xml b/feature-dapp-api/src/main/res/color/dapp_category_text_tint.xml new file mode 100644 index 0000000000..219acecf9c --- /dev/null +++ b/feature-dapp-api/src/main/res/color/dapp_category_text_tint.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/feature-dapp-api/src/main/res/drawable/dapp_tab_background_default.xml b/feature-dapp-api/src/main/res/drawable/dapp_tab_background_default.xml new file mode 100644 index 0000000000..b1bf228835 --- /dev/null +++ b/feature-dapp-api/src/main/res/drawable/dapp_tab_background_default.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/feature-dapp-api/src/main/res/drawable/dapp_tab_background_selected.xml b/feature-dapp-api/src/main/res/drawable/dapp_tab_background_selected.xml index 5b9e5ef3ff..d87e2db38e 100644 --- a/feature-dapp-api/src/main/res/drawable/dapp_tab_background_selected.xml +++ b/feature-dapp-api/src/main/res/drawable/dapp_tab_background_selected.xml @@ -1,7 +1,7 @@ - + - + \ No newline at end of file diff --git a/feature-dapp-api/src/main/res/layout/item_dapp_shimmering.xml b/feature-dapp-api/src/main/res/layout/item_dapp_shimmering.xml index 104bf184e7..759c10b319 100644 --- a/feature-dapp-api/src/main/res/layout/item_dapp_shimmering.xml +++ b/feature-dapp-api/src/main/res/layout/item_dapp_shimmering.xml @@ -18,7 +18,7 @@ - + android:orientation="vertical"> + android:layout_marginTop="34dp"> + android:orientation="vertical"> - + android:background="@drawable/bg_shimmering" /> - + - + - + + android:layout_marginTop="34dp"> + + + android:layout_height="wrap_content" + android:layout_marginTop="16dp" /> - \ No newline at end of file + \ No newline at end of file diff --git a/feature-dapp-api/src/main/res/layout/view_dapp.xml b/feature-dapp-api/src/main/res/layout/view_dapp.xml index 8294c210a8..ef5ed5385a 100644 --- a/feature-dapp-api/src/main/res/layout/view_dapp.xml +++ b/feature-dapp-api/src/main/res/layout/view_dapp.xml @@ -59,10 +59,10 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="8dp" - android:includeFontPadding="false" android:layout_marginTop="2dp" android:layout_marginEnd="24dp" android:ellipsize="end" + android:includeFontPadding="false" android:maxLines="1" android:textColor="@color/text_secondary" app:layout_constraintBottom_toBottomOf="@+id/itemDAppIcon" @@ -73,6 +73,19 @@ app:layout_goneMarginStart="0dp" tools:text="Staking" /> + + - + tools:src="@drawable/ic_close" /> \ No newline at end of file diff --git a/feature-dapp-impl/build.gradle b/feature-dapp-impl/build.gradle index 2cbb7c5846..7d1a5dd1d7 100644 --- a/feature-dapp-impl/build.gradle +++ b/feature-dapp-impl/build.gradle @@ -85,6 +85,8 @@ dependencies { implementation liveDataKtxDep implementation lifeCycleKtxDep + implementation navigationFragmentDep + implementation retrofitDep implementation web3jDep diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/DAppRouter.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/DAppRouter.kt deleted file mode 100644 index 016c08bfd9..0000000000 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/DAppRouter.kt +++ /dev/null @@ -1,17 +0,0 @@ -package io.novafoundation.nova.feature_dapp_impl - -import io.novafoundation.nova.common.navigation.ReturnableRouter -import io.novafoundation.nova.feature_dapp_impl.presentation.addToFavourites.AddToFavouritesPayload - -interface DAppRouter : ReturnableRouter { - - fun openChangeAccount() - - fun openDAppBrowser(initialUrl: String) - - fun openDappSearch() - - fun openAddToFavourites(payload: AddToFavouritesPayload) - - fun openAuthorizedDApps() -} diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/data/mappers/DappMetadata.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/data/mappers/DappMetadata.kt index ddd7ef0044..e55fdd5494 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/data/mappers/DappMetadata.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/data/mappers/DappMetadata.kt @@ -1,18 +1,17 @@ package io.novafoundation.nova.feature_dapp_impl.data.mappers import io.novafoundation.nova.common.utils.Urls -import io.novafoundation.nova.feature_dapp_api.data.model.DApp import io.novafoundation.nova.feature_dapp_api.data.model.DappCatalog import io.novafoundation.nova.feature_dapp_api.data.model.DappCategory import io.novafoundation.nova.feature_dapp_api.data.model.DappMetadata import io.novafoundation.nova.feature_dapp_impl.data.network.metadata.DappMetadataResponse -import io.novafoundation.nova.feature_dapp_impl.presentation.common.DappModel fun mapDAppMetadataResponseToDAppMetadatas( response: DappMetadataResponse ): DappCatalog { val categories = response.categories.map { DappCategory( + iconUrl = it.icon, name = it.name, id = it.id ) @@ -32,25 +31,3 @@ fun mapDAppMetadataResponseToDAppMetadatas( return DappCatalog(categories, metadata) } - -fun mapDappCategoriesToDescription(categories: Collection) = categories.joinToString { it.name } - -fun mapDappToDappModel(dApp: DApp) = with(dApp) { - DappModel( - name = name, - description = description, - iconUrl = iconLink, - url = url, - isFavourite = isFavourite - ) -} - -fun mapDappModelToDApp(dApp: DappModel) = with(dApp) { - DApp( - name = name, - description = description, - iconLink = iconUrl, - url = url, - isFavourite = isFavourite - ) -} diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/data/mappers/FavouriteDapps.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/data/mappers/FavouriteDapps.kt index a118fd3331..098cb14da5 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/data/mappers/FavouriteDapps.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/data/mappers/FavouriteDapps.kt @@ -8,7 +8,8 @@ fun mapFavouriteDAppLocalToFavouriteDApp(favouriteDAppLocal: FavouriteDAppLocal) FavouriteDApp( url = url, label = label, - icon = icon + icon = icon, + orderingIndex = orderingIndex ) } } @@ -18,7 +19,8 @@ fun mapFavouriteDAppToFavouriteDAppLocal(favouriteDApp: FavouriteDApp): Favourit FavouriteDAppLocal( url = url, label = label, - icon = icon + icon = icon, + orderingIndex = orderingIndex ) } } diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/data/model/FavouriteDApp.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/data/model/FavouriteDApp.kt index 4f24f41bfb..fed9491915 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/data/model/FavouriteDApp.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/data/model/FavouriteDApp.kt @@ -1,7 +1,8 @@ package io.novafoundation.nova.feature_dapp_impl.data.model -class FavouriteDApp( +data class FavouriteDApp( val url: String, val label: String, - val icon: String? + val icon: String?, + val orderingIndex: Int ) diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/data/network/metadata/DappMetadataRemote.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/data/network/metadata/DappMetadataRemote.kt index c026beeb52..cc69fdee5f 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/data/network/metadata/DappMetadataRemote.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/data/network/metadata/DappMetadataRemote.kt @@ -14,6 +14,7 @@ class DappMetadataRemote( ) class DappCategoryRemote( + val icon: String?, val name: String, val id: String ) diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/data/repository/FavouritesDAppRepository.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/data/repository/FavouritesDAppRepository.kt index 69e1c24343..c0fb1ea3f1 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/data/repository/FavouritesDAppRepository.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/data/repository/FavouritesDAppRepository.kt @@ -1,5 +1,6 @@ package io.novafoundation.nova.feature_dapp_impl.data.repository +import io.novafoundation.nova.common.utils.CollectionDiffer import io.novafoundation.nova.common.utils.mapList import io.novafoundation.nova.core_db.dao.FavouriteDAppsDao import io.novafoundation.nova.feature_dapp_impl.data.mappers.mapFavouriteDAppLocalToFavouriteDApp @@ -18,6 +19,10 @@ interface FavouritesDAppRepository { fun observeIsFavourite(url: String): Flow suspend fun removeFavourite(dAppUrl: String) + + suspend fun updateFavoriteDapps(favoriteDapps: List) + + suspend fun getNextOrderingIndex(): Int } class DbFavouritesDAppRepository( @@ -47,4 +52,19 @@ class DbFavouritesDAppRepository( override suspend fun removeFavourite(dAppUrl: String) { favouriteDAppsDao.deleteFavouriteDApp(dAppUrl) } + + override suspend fun updateFavoriteDapps(favoriteDapps: List) { + val newDapps = favoriteDapps.map { mapFavouriteDAppToFavouriteDAppLocal(it) } + val currentDapps = favouriteDAppsDao.getFavouriteDApps() + val diff = CollectionDiffer.findDiff(newDapps, currentDapps, false) + favouriteDAppsDao.updateFavourites(diff.updated) + } + + override suspend fun getNextOrderingIndex(): Int { + return try { + favouriteDAppsDao.getMaxOrderingIndex() + 1 + } catch (e: NullPointerException) { // For case we don't have added favorite dapps + 0 + } + } } diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/data/repository/tabs/BrowserTabInternalRepository.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/data/repository/tabs/BrowserTabInternalRepository.kt new file mode 100644 index 0000000000..e2871a4471 --- /dev/null +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/data/repository/tabs/BrowserTabInternalRepository.kt @@ -0,0 +1,21 @@ +package io.novafoundation.nova.feature_dapp_impl.data.repository.tabs + +import io.novafoundation.nova.feature_dapp_api.data.repository.BrowserTabExternalRepository +import io.novafoundation.nova.feature_dapp_impl.utils.tabs.models.BrowserTab +import io.novafoundation.nova.feature_dapp_impl.utils.tabs.models.PageSnapshot +import kotlinx.coroutines.flow.Flow + +interface BrowserTabInternalRepository : BrowserTabExternalRepository { + + suspend fun saveTab(tab: BrowserTab) + + suspend fun removeTab(tabId: String) + + suspend fun savePageSnapshot(tabId: String, snapshot: PageSnapshot) + + fun observeTabs(metaId: Long): Flow> + + suspend fun changeCurrentUrl(tabId: String, url: String) + + suspend fun changeKnownDAppMetadata(tabId: String, dappIconUrl: String?) +} diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/data/repository/tabs/RealBrowserTabRepository.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/data/repository/tabs/RealBrowserTabRepository.kt new file mode 100644 index 0000000000..372109b757 --- /dev/null +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/data/repository/tabs/RealBrowserTabRepository.kt @@ -0,0 +1,89 @@ +package io.novafoundation.nova.feature_dapp_impl.data.repository.tabs + +import io.novafoundation.nova.common.utils.mapList +import io.novafoundation.nova.core_db.dao.BrowserTabsDao +import io.novafoundation.nova.core_db.model.BrowserTabLocal +import io.novafoundation.nova.feature_dapp_api.data.model.SimpleTabModel +import io.novafoundation.nova.feature_dapp_impl.utils.tabs.models.BrowserTab +import io.novafoundation.nova.feature_dapp_impl.utils.tabs.models.PageSnapshot +import java.util.Date +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.withContext + +class RealBrowserTabRepository( + private val browserTabsDao: BrowserTabsDao +) : BrowserTabInternalRepository { + + override suspend fun saveTab(tab: BrowserTab) { + browserTabsDao.insertTab(tab.toLocal()) + } + + override suspend fun removeTab(tabId: String) { + browserTabsDao.removeTab(tabId) + } + + override fun observeTabsWithNames(metaId: Long): Flow> { + return browserTabsDao.observeTabsByMetaId(metaId) + .mapList { + SimpleTabModel(it.id, it.pageName, it.knownDAppMetadata?.iconLink, it.pageIconPath) + } + } + + override suspend fun removeTabsForMetaAccount(metaId: Long): List { + return withContext(Dispatchers.Default) { + browserTabsDao.removeTabsByMetaId(metaId) + } + } + + override suspend fun savePageSnapshot(tabId: String, snapshot: PageSnapshot) { + browserTabsDao.updatePageSnapshot( + tabId = tabId, + pageName = snapshot.pageName, + pageIconPath = snapshot.pageIconPath, + pagePicturePath = snapshot.pagePicturePath + ) + } + + override fun observeTabs(metaId: Long): Flow> { + return browserTabsDao.observeTabsByMetaId(metaId).mapList { tab -> + tab.fromLocal() + } + } + + override suspend fun changeCurrentUrl(tabId: String, url: String) { + browserTabsDao.updateCurrentUrl(tabId, url) + } + + override suspend fun changeKnownDAppMetadata(tabId: String, dappIconUrl: String?) { + browserTabsDao.updateKnownDAppMetadata(tabId, dappIconUrl) + } +} + +private fun BrowserTabLocal.fromLocal(): BrowserTab { + return BrowserTab( + id = id, + metaId = metaId, + currentUrl = currentUrl, + pageSnapshot = PageSnapshot( + pageName = pageName, + pageIconPath = pageIconPath, + pagePicturePath = pagePicturePath + ), + knownDAppMetadata = knownDAppMetadata?.let { BrowserTab.KnownDAppMetadata(it.iconLink) }, + creationTime = Date(creationTime), + ) +} + +private fun BrowserTab.toLocal(): BrowserTabLocal { + return BrowserTabLocal( + id = id, + metaId = metaId, + currentUrl = currentUrl, + creationTime = creationTime.time, + pageName = pageSnapshot.pageName, + pageIconPath = pageSnapshot.pageIconPath, + knownDAppMetadata = knownDAppMetadata?.let { BrowserTabLocal.KnownDAppMetadata(it.iconLink) }, + pagePicturePath = pageSnapshot.pagePicturePath + ) +} diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/di/DAppFeatureComponent.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/di/DAppFeatureComponent.kt index 3b4285e4a7..0ab88b7121 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/di/DAppFeatureComponent.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/di/DAppFeatureComponent.kt @@ -8,13 +8,15 @@ import io.novafoundation.nova.core_db.di.DbApi import io.novafoundation.nova.feature_account_api.di.AccountFeatureApi 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_dapp_impl.presentation.DAppRouter import io.novafoundation.nova.feature_dapp_impl.presentation.addToFavourites.di.AddToFavouritesComponent import io.novafoundation.nova.feature_dapp_impl.presentation.authorizedDApps.di.AuthorizedDAppsComponent import io.novafoundation.nova.feature_dapp_impl.presentation.browser.main.di.DAppBrowserComponent +import io.novafoundation.nova.feature_dapp_impl.presentation.favorites.di.DAppFavoritesComponent import io.novafoundation.nova.feature_dapp_impl.presentation.main.di.MainDAppComponent import io.novafoundation.nova.feature_dapp_impl.presentation.search.DAppSearchCommunicator import io.novafoundation.nova.feature_dapp_impl.presentation.search.di.DAppSearchComponent +import io.novafoundation.nova.feature_dapp_impl.presentation.tab.di.BrowserTabsComponent import io.novafoundation.nova.feature_external_sign_api.model.ExternalSignCommunicator import io.novafoundation.nova.feature_wallet_api.di.WalletFeatureApi import io.novafoundation.nova.runtime.di.RuntimeApi @@ -30,14 +32,16 @@ import io.novafoundation.nova.runtime.di.RuntimeApi @FeatureScope interface DAppFeatureComponent : DAppFeatureApi { - // Screens - fun mainComponentFactory(): MainDAppComponent.Factory fun browserComponentFactory(): DAppBrowserComponent.Factory + fun browserTabsComponentFactory(): BrowserTabsComponent.Factory + fun dAppSearchComponentFactory(): DAppSearchComponent.Factory + fun dAppFavoritesComponentFactory(): DAppFavoritesComponent.Factory + fun addToFavouritesComponentFactory(): AddToFavouritesComponent.Factory fun authorizedDAppsComponentFactory(): AuthorizedDAppsComponent.Factory diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/di/DAppFeatureDependencies.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/di/DAppFeatureDependencies.kt index 15210b8d41..402f6acf66 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/di/DAppFeatureDependencies.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/di/DAppFeatureDependencies.kt @@ -1,14 +1,20 @@ package io.novafoundation.nova.feature_dapp_impl.di +import android.content.Context import coil.ImageLoader import com.google.gson.Gson import io.novafoundation.nova.common.address.AddressIconGenerator import io.novafoundation.nova.common.data.network.AppLinksProvider import io.novafoundation.nova.common.data.network.NetworkApiCreator import io.novafoundation.nova.common.data.secrets.v2.SecretStoreV2 +import io.novafoundation.nova.common.interfaces.FileProvider import io.novafoundation.nova.common.mixin.actionAwaitable.ActionAwaitableMixin +import io.novafoundation.nova.common.resources.ContextManager import io.novafoundation.nova.common.resources.ResourceManager +import io.novafoundation.nova.common.utils.coroutines.RootScope +import io.novafoundation.nova.common.utils.permissions.PermissionsAskerFactory import io.novafoundation.nova.core_db.dao.BrowserHostSettingsDao +import io.novafoundation.nova.core_db.dao.BrowserTabsDao import io.novafoundation.nova.core_db.dao.DappAuthorizationDao import io.novafoundation.nova.core_db.dao.FavouriteDAppsDao import io.novafoundation.nova.core_db.dao.PhishingSitesDao @@ -26,6 +32,28 @@ import io.novafoundation.nova.runtime.multiNetwork.runtime.repository.RuntimeVer interface DAppFeatureDependencies { + val context: Context + + val browserTabsDao: BrowserTabsDao + + val phishingSitesDao: PhishingSitesDao + + val favouriteDAppsDao: FavouriteDAppsDao + + val actionAwaitableMixinFactory: ActionAwaitableMixin.Factory + + val walletUiUseCase: WalletUiUseCase + + val walletRepository: WalletRepository + + val fileProvider: FileProvider + + val contextManager: ContextManager + + val rootScope: RootScope + + val permissionsAskerFactory: PermissionsAskerFactory + fun currencyRepository(): CurrencyRepository fun accountRepository(): AccountRepository @@ -62,14 +90,4 @@ interface DAppFeatureDependencies { fun dappAuthorizationDao(): DappAuthorizationDao fun browserHostSettingsDao(): BrowserHostSettingsDao - - val phishingSitesDao: PhishingSitesDao - - val favouriteDAppsDao: FavouriteDAppsDao - - val actionAwaitableMixinFactory: ActionAwaitableMixin.Factory - - val walletUiUseCase: WalletUiUseCase - - val walletRepository: WalletRepository } diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/di/DAppFeatureHolder.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/di/DAppFeatureHolder.kt index ed21680394..ed54bbc4c9 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/di/DAppFeatureHolder.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/di/DAppFeatureHolder.kt @@ -6,7 +6,7 @@ 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_currency_api.di.CurrencyFeatureApi -import io.novafoundation.nova.feature_dapp_impl.DAppRouter +import io.novafoundation.nova.feature_dapp_impl.presentation.DAppRouter import io.novafoundation.nova.feature_dapp_impl.presentation.search.DAppSearchCommunicator import io.novafoundation.nova.feature_external_sign_api.model.ExternalSignCommunicator import io.novafoundation.nova.feature_wallet_api.di.WalletFeatureApi diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/di/DappFeatureModule.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/di/DappFeatureModule.kt index 64e7a46292..2ee07434e1 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/di/DappFeatureModule.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/di/DappFeatureModule.kt @@ -3,17 +3,17 @@ package io.novafoundation.nova.feature_dapp_impl.di import dagger.Module import dagger.Provides import io.novafoundation.nova.common.di.scope.FeatureScope -import io.novafoundation.nova.common.resources.ResourceManager import io.novafoundation.nova.feature_dapp_api.data.repository.DAppMetadataRepository import io.novafoundation.nova.feature_dapp_impl.data.repository.FavouritesDAppRepository import io.novafoundation.nova.feature_dapp_impl.data.repository.PhishingSitesRepository +import io.novafoundation.nova.feature_dapp_impl.di.modules.BrowserTabsModule import io.novafoundation.nova.feature_dapp_impl.di.modules.DappMetadataModule import io.novafoundation.nova.feature_dapp_impl.di.modules.FavouritesDAppModule import io.novafoundation.nova.feature_dapp_impl.di.modules.PhishingSitesModule import io.novafoundation.nova.feature_dapp_impl.di.modules.Web3Module import io.novafoundation.nova.feature_dapp_impl.domain.DappInteractor -@Module(includes = [Web3Module::class, DappMetadataModule::class, PhishingSitesModule::class, FavouritesDAppModule::class]) +@Module(includes = [Web3Module::class, DappMetadataModule::class, PhishingSitesModule::class, FavouritesDAppModule::class, BrowserTabsModule::class]) class DappFeatureModule { @Provides @@ -21,12 +21,10 @@ class DappFeatureModule { fun provideCommonInteractor( dAppMetadataRepository: DAppMetadataRepository, favouritesDAppRepository: FavouritesDAppRepository, - phishingSitesRepository: PhishingSitesRepository, - resourceManager: ResourceManager + phishingSitesRepository: PhishingSitesRepository ) = DappInteractor( dAppMetadataRepository = dAppMetadataRepository, favouritesDAppRepository = favouritesDAppRepository, - phishingSitesRepository = phishingSitesRepository, - resourceManager = resourceManager + phishingSitesRepository = phishingSitesRepository ) } diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/di/modules/BrowserTabsModule.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/di/modules/BrowserTabsModule.kt new file mode 100644 index 0000000000..91cdfcefc2 --- /dev/null +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/di/modules/BrowserTabsModule.kt @@ -0,0 +1,86 @@ +package io.novafoundation.nova.feature_dapp_impl.di.modules + +import android.content.Context +import dagger.Module +import dagger.Provides +import io.novafoundation.nova.common.di.scope.FeatureScope +import io.novafoundation.nova.common.interfaces.FileProvider +import io.novafoundation.nova.common.resources.ContextManager +import io.novafoundation.nova.common.utils.coroutines.RootScope +import io.novafoundation.nova.core_db.dao.BrowserTabsDao +import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository +import io.novafoundation.nova.feature_dapp_api.data.repository.BrowserTabExternalRepository +import io.novafoundation.nova.feature_dapp_api.data.repository.DAppMetadataRepository +import io.novafoundation.nova.feature_dapp_impl.utils.tabs.BrowserTabService +import io.novafoundation.nova.feature_dapp_impl.data.repository.tabs.BrowserTabInternalRepository +import io.novafoundation.nova.feature_dapp_impl.utils.tabs.RealPageSnapshotBuilder +import io.novafoundation.nova.feature_dapp_impl.utils.tabs.RealBrowserTabService +import io.novafoundation.nova.feature_dapp_impl.data.repository.tabs.RealBrowserTabRepository +import io.novafoundation.nova.feature_dapp_impl.utils.tabs.PageSnapshotBuilder +import io.novafoundation.nova.feature_dapp_impl.utils.tabs.RealTabMemoryRestrictionService +import io.novafoundation.nova.feature_dapp_impl.utils.tabs.TabMemoryRestrictionService +import io.novafoundation.nova.feature_dapp_impl.utils.tabs.models.BrowserTabSessionFactory +import io.novafoundation.nova.feature_dapp_impl.web3.webview.CompoundWeb3Injector + +@Module +class BrowserTabsModule { + + @FeatureScope + @Provides + fun provideBrowserTabStorage( + browserTabsDao: BrowserTabsDao, + ): BrowserTabInternalRepository { + return RealBrowserTabRepository(browserTabsDao = browserTabsDao) + } + + @FeatureScope + @Provides + fun provideBrowserTabRepository( + repository: BrowserTabInternalRepository, + ): BrowserTabExternalRepository { + return repository + } + + @FeatureScope + @Provides + fun providePageSnapshotBuilder(fileProvider: FileProvider, rootScope: RootScope): PageSnapshotBuilder { + return RealPageSnapshotBuilder(fileProvider, rootScope) + } + + @FeatureScope + @Provides + fun provideTabMemoryRestrictionService(context: Context): TabMemoryRestrictionService { + return RealTabMemoryRestrictionService(context) + } + + @FeatureScope + @Provides + fun providePageSessionFactory( + compoundWeb3Injector: CompoundWeb3Injector, + contextManager: ContextManager + ): BrowserTabSessionFactory { + return BrowserTabSessionFactory(compoundWeb3Injector, contextManager) + } + + @FeatureScope + @Provides + fun provideBrowserTabPoolService( + accountRepository: AccountRepository, + dAppMetadataRepository: DAppMetadataRepository, + browserTabInternalRepository: BrowserTabInternalRepository, + pageSnapshotBuilder: PageSnapshotBuilder, + tabMemoryRestrictionService: TabMemoryRestrictionService, + browserTabSessionFactory: BrowserTabSessionFactory, + rootScope: RootScope + ): BrowserTabService { + return RealBrowserTabService( + browserTabInternalRepository = browserTabInternalRepository, + pageSnapshotBuilder = pageSnapshotBuilder, + tabMemoryRestrictionService = tabMemoryRestrictionService, + browserTabSessionFactory = browserTabSessionFactory, + accountRepository = accountRepository, + dAppMetadataRepository = dAppMetadataRepository, + rootScope = rootScope + ) + } +} diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/di/modules/Web3Module.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/di/modules/Web3Module.kt index bed72a5fdd..936c635704 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/di/modules/Web3Module.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/di/modules/Web3Module.kt @@ -16,7 +16,7 @@ import io.novafoundation.nova.feature_dapp_impl.web3.polkadotJs.states.PolkadotJ import io.novafoundation.nova.feature_dapp_impl.web3.session.DbWeb3Session import io.novafoundation.nova.feature_dapp_impl.web3.session.Web3Session import io.novafoundation.nova.feature_dapp_impl.web3.states.ExtensionStoreFactory -import io.novafoundation.nova.feature_dapp_impl.web3.webview.Web3WebViewClientFactory +import io.novafoundation.nova.feature_dapp_impl.web3.webview.CompoundWeb3Injector import io.novafoundation.nova.feature_dapp_impl.web3.webview.WebViewHolder import io.novafoundation.nova.feature_dapp_impl.web3.webview.WebViewScriptInjector @@ -35,10 +35,10 @@ class Web3Module { @Provides @FeatureScope - fun provideWeb3ClientFactory( + fun provideWeb3InjectorPool( polkadotJsInjector: PolkadotJsInjector, metamaskInjector: MetamaskInjector, - ) = Web3WebViewClientFactory( + ) = CompoundWeb3Injector( injectors = listOf( polkadotJsInjector, metamaskInjector diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/domain/DappInteractor.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/domain/DappInteractor.kt index 6d3af553df..2449fd467a 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/domain/DappInteractor.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/domain/DappInteractor.kt @@ -1,12 +1,10 @@ package io.novafoundation.nova.feature_dapp_impl.domain import io.novafoundation.nova.common.list.GroupedList -import io.novafoundation.nova.common.resources.ResourceManager import io.novafoundation.nova.common.utils.Urls import io.novafoundation.nova.feature_dapp_api.data.model.DApp import io.novafoundation.nova.feature_dapp_api.data.model.DappCategory import io.novafoundation.nova.feature_dapp_api.data.repository.DAppMetadataRepository -import io.novafoundation.nova.feature_dapp_impl.R import io.novafoundation.nova.feature_dapp_impl.data.model.FavouriteDApp import io.novafoundation.nova.feature_dapp_impl.data.repository.FavouritesDAppRepository import io.novafoundation.nova.feature_dapp_impl.data.repository.PhishingSitesRepository @@ -19,6 +17,7 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.async import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.map import kotlinx.coroutines.joinAll import kotlinx.coroutines.withContext @@ -26,7 +25,6 @@ class DappInteractor( private val dAppMetadataRepository: DAppMetadataRepository, private val favouritesDAppRepository: FavouritesDAppRepository, private val phishingSitesRepository: PhishingSitesRepository, - private val resourceManager: ResourceManager, ) { private val dAppComparator by lazy { @@ -44,12 +42,13 @@ class DappInteractor( favouritesDAppRepository.removeFavourite(dAppUrl) } - suspend fun toggleDAppFavouritesState(dApp: DApp) = withContext(Dispatchers.Default) { - if (dApp.isFavourite) { - favouritesDAppRepository.removeFavourite(dApp.url) - } else { - favouritesDAppRepository.addFavourite(dAppToFavourite(dApp)) - } + suspend fun getFavoriteDApps(): List { + return favouritesDAppRepository.getFavourites().sortDApps() + } + + fun observeFavoriteDApps(): Flow> { + return favouritesDAppRepository.observeFavourites() + .map { it.sortDApps() } } fun observeDAppsByCategory(): Flow> { @@ -62,35 +61,12 @@ class DappInteractor( val urlToDAppMapping = buildUrlToDappMapping(dapps, favourites) - val favouritesCategory = DappCategory( - id = "favourites", - name = resourceManager.getString(R.string.dapp_favourites) - ) - val favouritesCategoryItems = favourites.map { urlToDAppMapping.getValue(it.url) } - // Regrouping in O(Categories * Dapps) // Complexity should be fine for expected amount of dApps - val derivedCategories = categories.associateWith { category -> + categories.associateWith { category -> dapps.filter { category in it.categories } .map { urlToDAppMapping.getValue(it.url) } } - - val categoryAll = DappCategory( - id = "all", - name = resourceManager.getString(R.string.common_all) - ) - - buildMap { - putCategory(categoryAll, urlToDAppMapping.values.toList()) - - if (favouritesCategoryItems.isNotEmpty()) { - putCategory(favouritesCategory, favouritesCategoryItems) - } - - derivedCategories.forEach { (category, items) -> - putCategory(category, items) - } - } } } @@ -105,19 +81,15 @@ class DappInteractor( } } - private fun MutableMap>.putCategory(category: DappCategory, items: Collection) { - put(category, items.sortedWith(dAppComparator)) + private inline fun CoroutineScope.runSync(crossinline sync: suspend () -> Unit): Job { + return async { runCatching { sync() } } } - private fun dAppToFavourite(dApp: DApp): FavouriteDApp { - return FavouriteDApp( - url = dApp.url, - label = dApp.name, - icon = dApp.iconLink - ) + suspend fun updateFavoriteDapps(favoriteDapps: List) { + favouritesDAppRepository.updateFavoriteDapps(favoriteDapps) } - private inline fun CoroutineScope.runSync(crossinline sync: suspend () -> Unit): Job { - return async { runCatching { sync() } } + private fun List.sortDApps(): List { + return sortedBy { it.orderingIndex } } } diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/domain/browser/addToFavourites/AddToFavouritesInteractor.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/domain/browser/addToFavourites/AddToFavouritesInteractor.kt index 57b9d62341..9b6e05adfc 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/domain/browser/addToFavourites/AddToFavouritesInteractor.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/domain/browser/addToFavourites/AddToFavouritesInteractor.kt @@ -12,6 +12,18 @@ class AddToFavouritesInteractor( private val dAppMetadataRepository: DAppMetadataRepository, ) { + suspend fun addToFavourites(url: String, label: String, icon: String?) = withContext(Dispatchers.Default) { + val nextOrderingIndex = favouritesDAppRepository.getNextOrderingIndex() + + val favorite = FavouriteDApp( + url = url, + label = label, + icon = icon, + orderingIndex = nextOrderingIndex + ) + favouritesDAppRepository.addFavourite(favorite) + } + suspend fun addToFavourites(favouriteDApp: FavouriteDApp) = withContext(Dispatchers.Default) { favouritesDAppRepository.addFavourite(favouriteDApp) } @@ -26,7 +38,8 @@ class AddToFavouritesInteractor( FavouriteDApp( url = url, label = dAppMetadataExactMatch?.name ?: suppliedLabel ?: Urls.hostOf(url), - icon = dAppMetadataExactMatch?.iconLink ?: dAppMetadataBaseUrlSingleMatch?.iconLink + icon = dAppMetadataExactMatch?.iconLink ?: dAppMetadataBaseUrlSingleMatch?.iconLink, + orderingIndex = 0 ) } } diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/domain/common/DAppLists.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/domain/common/DAppLists.kt index 2bfff51eff..2814cee17d 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/domain/common/DAppLists.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/domain/common/DAppLists.kt @@ -3,16 +3,16 @@ package io.novafoundation.nova.feature_dapp_impl.domain.common import io.novafoundation.nova.common.utils.mapToSet import io.novafoundation.nova.feature_dapp_api.data.model.DApp import io.novafoundation.nova.feature_dapp_api.data.model.DappMetadata -import io.novafoundation.nova.feature_dapp_impl.data.mappers.mapDappCategoriesToDescription import io.novafoundation.nova.feature_dapp_impl.data.model.FavouriteDApp +import io.novafoundation.nova.feature_dapp_impl.presentation.common.mapDappCategoriesToDescription fun createDAppComparator() = compareByDescending { it.isFavourite } .thenBy { it.name } // Build mapping in O(Metadatas + Favourites) in case of HashMap. It allows constant time access later internal fun buildUrlToDappMapping( - dAppMetadatas: List, - favourites: List + dAppMetadatas: Collection, + favourites: Collection ): Map { val favouritesUrls: Set = favourites.mapToSet { it.url } @@ -32,7 +32,16 @@ internal fun buildUrlToDappMapping( } } -private fun favouriteToDApp(favouriteDApp: FavouriteDApp): DApp { +fun dappToFavorite(dapp: DApp, orderingIndex: Int): FavouriteDApp { + return FavouriteDApp( + label = dapp.name, + icon = dapp.iconLink, + url = dapp.url, + orderingIndex = orderingIndex + ) +} + +fun favouriteToDApp(favouriteDApp: FavouriteDApp): DApp { return DApp( name = favouriteDApp.label, description = favouriteDApp.url, diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/domain/search/DappSearchResult.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/domain/search/DappSearchResult.kt index b965ba6cce..ea634ff073 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/domain/search/DappSearchResult.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/domain/search/DappSearchResult.kt @@ -2,11 +2,17 @@ package io.novafoundation.nova.feature_dapp_impl.domain.search import io.novafoundation.nova.feature_dapp_api.data.model.DApp -sealed class DappSearchResult { +sealed interface DappSearchResult { - class Url(val url: String) : DappSearchResult() + val isTrustedByNova: Boolean - class Search(val query: String, val searchUrl: String) : DappSearchResult() + class Url(val url: String, override val isTrustedByNova: Boolean) : DappSearchResult - class Dapp(val dapp: DApp) : DappSearchResult() + class Search(val query: String, val searchUrl: String) : DappSearchResult { + override val isTrustedByNova: Boolean = false + } + + class Dapp(val dapp: DApp) : DappSearchResult { + override val isTrustedByNova: Boolean = true + } } diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/domain/search/SearchDappInteractor.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/domain/search/SearchDappInteractor.kt index bf2f98cdd3..4c96be0fa6 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/domain/search/SearchDappInteractor.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/domain/search/SearchDappInteractor.kt @@ -2,11 +2,15 @@ package io.novafoundation.nova.feature_dapp_impl.domain.search import io.novafoundation.nova.common.list.GroupedList import io.novafoundation.nova.common.utils.Urls +import io.novafoundation.nova.feature_dapp_api.data.model.DApp +import io.novafoundation.nova.feature_dapp_api.data.model.DappCategory import io.novafoundation.nova.feature_dapp_api.data.repository.DAppMetadataRepository import io.novafoundation.nova.feature_dapp_impl.data.repository.FavouritesDAppRepository import io.novafoundation.nova.feature_dapp_impl.domain.common.buildUrlToDappMapping import io.novafoundation.nova.feature_dapp_impl.domain.common.createDAppComparator import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map import kotlinx.coroutines.withContext class SearchDappInteractor( @@ -14,20 +18,28 @@ class SearchDappInteractor( private val favouritesDAppRepository: FavouritesDAppRepository, ) { - suspend fun searchDapps(query: String): GroupedList = withContext(Dispatchers.Default) { - val catalog = dAppMetadataRepository.getDAppCatalog() - val favouriteDApps = favouritesDAppRepository.getFavourites() + fun categories(): Flow> { + return dAppMetadataRepository.observeDAppCatalog() + .map { it.categories } + } - val dAppByUrlMapping = buildUrlToDappMapping(catalog.dApps, favouriteDApps) - val allDApps = dAppByUrlMapping.values + suspend fun searchDapps(query: String, categoryId: String?): GroupedList = withContext(Dispatchers.Default) { + val dapps = getDapps(categoryId) - val dappsGroupContent = allDApps.filter { query.isEmpty() || query.lowercase() in it.name.lowercase() } + val dappsGroupContent = dapps + .filter { query.isEmpty() || query.lowercase() in it.name.lowercase() } .sortedWith(createDAppComparator()) .map(DappSearchResult::Dapp) val searchGroupContent = when { query.isEmpty() -> null - Urls.isValidWebUrl(query) -> DappSearchResult.Url(Urls.ensureHttpsProtocol(query)) + Urls.isValidWebUrl(query) -> { + val searchUrl = Urls.ensureHttpsProtocol(query) + val searchUrlDomain = Urls.domainOf(searchUrl) + val trusting = dapps.any { Urls.domainOf(it.url) == searchUrlDomain } + DappSearchResult.Url(searchUrl, trusting) + } + else -> DappSearchResult.Search(query, searchUrlFor(query)) } @@ -43,4 +55,16 @@ class SearchDappInteractor( } private fun searchUrlFor(query: String): String = "https://duckduckgo.com/?q=$query" + + private suspend fun getDapps(categoryId: String?): Collection { + val dApps = dAppMetadataRepository.getDAppCatalog() + .dApps + .filter { dapp -> categoryId == null || dapp.categories.any { it.id == categoryId } } + .associateBy { it.url } + val favouriteDApps = favouritesDAppRepository.getFavourites() + .filter { categoryId == null || it.url in dApps.keys } + + val dAppByUrlMapping = buildUrlToDappMapping(dApps.values, favouriteDApps) + return dAppByUrlMapping.values + } } diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/DAppRouter.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/DAppRouter.kt new file mode 100644 index 0000000000..936c1e7721 --- /dev/null +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/DAppRouter.kt @@ -0,0 +1,29 @@ +package io.novafoundation.nova.feature_dapp_impl.presentation + +import androidx.navigation.fragment.FragmentNavigator +import io.novafoundation.nova.common.navigation.ReturnableRouter +import io.novafoundation.nova.feature_dapp_api.presentation.addToFavorites.AddToFavouritesPayload +import io.novafoundation.nova.feature_dapp_api.presentation.browser.main.DAppBrowserPayload + +interface DAppRouter : ReturnableRouter { + + fun openChangeAccount() + + fun openDAppBrowser(payload: DAppBrowserPayload, extras: FragmentNavigator.Extras? = null) + + fun openDappSearch() + + fun openDappSearchWithCategory(categoryId: String?) + + fun finishDappSearch() + + fun openAddToFavourites(payload: AddToFavouritesPayload) + + fun openAuthorizedDApps() + + fun openTabs() + + fun closeTabsScreen() + + fun openDAppFavorites() +} diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/addToFavourites/AddToFavouritesFragment.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/addToFavourites/AddToFavouritesFragment.kt index a40de0c386..26da3e23f0 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/addToFavourites/AddToFavouritesFragment.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/addToFavourites/AddToFavouritesFragment.kt @@ -16,6 +16,7 @@ import io.novafoundation.nova.common.utils.keyboard.showSoftKeyboard import io.novafoundation.nova.common.utils.moveCursorToTheEnd import io.novafoundation.nova.common.utils.postToSelf import io.novafoundation.nova.feature_dapp_api.di.DAppFeatureApi +import io.novafoundation.nova.feature_dapp_api.presentation.addToFavorites.AddToFavouritesPayload import io.novafoundation.nova.feature_dapp_impl.R import io.novafoundation.nova.feature_dapp_impl.di.DAppFeatureComponent import io.novafoundation.nova.feature_external_sign_api.presentation.dapp.showDAppIcon diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/addToFavourites/AddToFavouritesViewModel.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/addToFavourites/AddToFavouritesViewModel.kt index a19d8ebdbf..6ac4ea866f 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/addToFavourites/AddToFavouritesViewModel.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/addToFavourites/AddToFavouritesViewModel.kt @@ -6,8 +6,8 @@ import io.novafoundation.nova.common.base.BaseViewModel import io.novafoundation.nova.common.utils.Event import io.novafoundation.nova.common.utils.sendEvent import io.novafoundation.nova.common.utils.singleReplaySharedFlow -import io.novafoundation.nova.feature_dapp_impl.DAppRouter -import io.novafoundation.nova.feature_dapp_impl.data.model.FavouriteDApp +import io.novafoundation.nova.feature_dapp_impl.presentation.DAppRouter +import io.novafoundation.nova.feature_dapp_api.presentation.addToFavorites.AddToFavouritesPayload import io.novafoundation.nova.feature_dapp_impl.domain.browser.addToFavourites.AddToFavouritesInteractor import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.first @@ -32,13 +32,7 @@ class AddToFavouritesViewModel( } fun saveClicked() = launch { - val favouriteDApp = FavouriteDApp( - url = urlFlow.value, - label = labelFlow.first(), - icon = iconLink.first() - ) - - interactor.addToFavourites(favouriteDApp) + interactor.addToFavourites(urlFlow.value, labelFlow.first(), iconLink.first()) router.back() } diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/addToFavourites/di/AddToFavouritesComponent.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/addToFavourites/di/AddToFavouritesComponent.kt index dad834c9d9..2c567a6e0d 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/addToFavourites/di/AddToFavouritesComponent.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/addToFavourites/di/AddToFavouritesComponent.kt @@ -5,7 +5,7 @@ import dagger.BindsInstance import dagger.Subcomponent import io.novafoundation.nova.common.di.scope.ScreenScope import io.novafoundation.nova.feature_dapp_impl.presentation.addToFavourites.AddToFavouritesFragment -import io.novafoundation.nova.feature_dapp_impl.presentation.addToFavourites.AddToFavouritesPayload +import io.novafoundation.nova.feature_dapp_api.presentation.addToFavorites.AddToFavouritesPayload @Subcomponent( modules = [ diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/addToFavourites/di/AddToFavouritesModule.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/addToFavourites/di/AddToFavouritesModule.kt index 1d501980d7..126b4fb7b2 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/addToFavourites/di/AddToFavouritesModule.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/addToFavourites/di/AddToFavouritesModule.kt @@ -10,10 +10,10 @@ 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.feature_dapp_api.data.repository.DAppMetadataRepository -import io.novafoundation.nova.feature_dapp_impl.DAppRouter +import io.novafoundation.nova.feature_dapp_impl.presentation.DAppRouter import io.novafoundation.nova.feature_dapp_impl.data.repository.FavouritesDAppRepository import io.novafoundation.nova.feature_dapp_impl.domain.browser.addToFavourites.AddToFavouritesInteractor -import io.novafoundation.nova.feature_dapp_impl.presentation.addToFavourites.AddToFavouritesPayload +import io.novafoundation.nova.feature_dapp_api.presentation.addToFavorites.AddToFavouritesPayload import io.novafoundation.nova.feature_dapp_impl.presentation.addToFavourites.AddToFavouritesViewModel @Module(includes = [ViewModelModule::class]) diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/authorizedDApps/AuthorizedDAppsViewModel.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/authorizedDApps/AuthorizedDAppsViewModel.kt index 05d3997748..bafc6b184a 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/authorizedDApps/AuthorizedDAppsViewModel.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/authorizedDApps/AuthorizedDAppsViewModel.kt @@ -5,7 +5,7 @@ import io.novafoundation.nova.common.mixin.actionAwaitable.ActionAwaitableMixin import io.novafoundation.nova.common.mixin.actionAwaitable.confirmingAction import io.novafoundation.nova.common.utils.mapList import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.WalletUiUseCase -import io.novafoundation.nova.feature_dapp_impl.DAppRouter +import io.novafoundation.nova.feature_dapp_impl.presentation.DAppRouter import io.novafoundation.nova.feature_dapp_impl.domain.authorizedDApps.AuthorizedDApp import io.novafoundation.nova.feature_dapp_impl.domain.authorizedDApps.AuthorizedDAppsInteractor import io.novafoundation.nova.feature_dapp_impl.presentation.authorizedDApps.model.AuthorizedDAppModel diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/authorizedDApps/di/AuthorizedDAppsModule.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/authorizedDApps/di/AuthorizedDAppsModule.kt index cf17369a8a..008ceea5cd 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/authorizedDApps/di/AuthorizedDAppsModule.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/authorizedDApps/di/AuthorizedDAppsModule.kt @@ -13,7 +13,7 @@ import io.novafoundation.nova.common.mixin.actionAwaitable.ActionAwaitableMixin import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.WalletUiUseCase import io.novafoundation.nova.feature_dapp_api.data.repository.DAppMetadataRepository -import io.novafoundation.nova.feature_dapp_impl.DAppRouter +import io.novafoundation.nova.feature_dapp_impl.presentation.DAppRouter import io.novafoundation.nova.feature_dapp_impl.domain.authorizedDApps.AuthorizedDAppsInteractor import io.novafoundation.nova.feature_dapp_impl.presentation.authorizedDApps.AuthorizedDAppsViewModel import io.novafoundation.nova.feature_dapp_impl.web3.session.Web3Session diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/browser/main/DAppBrowserFragment.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/browser/main/DAppBrowserFragment.kt index 46742a906b..84f74e6b37 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/browser/main/DAppBrowserFragment.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/browser/main/DAppBrowserFragment.kt @@ -2,45 +2,63 @@ package io.novafoundation.nova.feature_dapp_impl.presentation.browser.main import android.content.Intent import android.content.pm.ActivityInfo +import android.graphics.Bitmap import android.os.Bundle +import android.transition.TransitionInflater import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.webkit.WebView +import android.widget.ImageView import android.widget.Toast import androidx.activity.OnBackPressedCallback +import androidx.core.app.SharedElementCallback import androidx.core.os.bundleOf +import androidx.core.transition.addListener +import androidx.lifecycle.viewModelScope +import coil.ImageLoader import io.novafoundation.nova.common.base.BaseFragment import io.novafoundation.nova.common.di.FeatureUtils import io.novafoundation.nova.common.utils.applyStatusBarInsets -import io.novafoundation.nova.common.utils.themed -import io.novafoundation.nova.common.view.dialog.dialog +import io.novafoundation.nova.common.utils.makeGone +import io.novafoundation.nova.common.utils.makeVisible import io.novafoundation.nova.feature_dapp_api.di.DAppFeatureApi +import io.novafoundation.nova.feature_dapp_api.presentation.browser.main.DAppBrowserPayload import io.novafoundation.nova.feature_dapp_impl.R import io.novafoundation.nova.feature_dapp_impl.di.DAppFeatureComponent import io.novafoundation.nova.feature_dapp_impl.domain.browser.isSecure import io.novafoundation.nova.feature_dapp_impl.presentation.browser.main.DappPendingConfirmation.Action import io.novafoundation.nova.feature_dapp_impl.presentation.browser.main.sheets.AcknowledgePhishingBottomSheet -import io.novafoundation.nova.feature_dapp_impl.presentation.browser.options.DAppOptionsPayload import io.novafoundation.nova.feature_dapp_impl.presentation.browser.options.OptionsBottomSheetDialog import io.novafoundation.nova.feature_dapp_impl.presentation.common.favourites.setupRemoveFavouritesConfirmation +import io.novafoundation.nova.feature_dapp_impl.utils.tabs.models.BrowserTabSession import io.novafoundation.nova.feature_dapp_impl.web3.webview.PageCallback +import io.novafoundation.nova.feature_dapp_impl.web3.webview.Web3ChromeClient +import io.novafoundation.nova.feature_dapp_impl.web3.webview.CompoundWeb3Injector import io.novafoundation.nova.feature_dapp_impl.web3.webview.Web3WebViewClient -import io.novafoundation.nova.feature_dapp_impl.web3.webview.Web3WebViewClientFactory import io.novafoundation.nova.feature_dapp_impl.web3.webview.WebViewFileChooser import io.novafoundation.nova.feature_dapp_impl.web3.webview.WebViewHolder -import io.novafoundation.nova.feature_dapp_impl.web3.webview.injectWeb3 -import io.novafoundation.nova.feature_dapp_impl.web3.webview.uninjectWeb3 +import io.novafoundation.nova.feature_dapp_impl.web3.webview.WebViewPermissionAsker import io.novafoundation.nova.feature_external_sign_api.presentation.externalSign.AuthorizeDappBottomSheet import kotlinx.android.synthetic.main.fragment_dapp_browser.dappBrowserAddressBar import kotlinx.android.synthetic.main.fragment_dapp_browser.dappBrowserAddressBarGroup import kotlinx.android.synthetic.main.fragment_dapp_browser.dappBrowserBack -import kotlinx.android.synthetic.main.fragment_dapp_browser.dappBrowserClose +import kotlinx.android.synthetic.main.fragment_dapp_browser.dappBrowserHide import kotlinx.android.synthetic.main.fragment_dapp_browser.dappBrowserForward import kotlinx.android.synthetic.main.fragment_dapp_browser.dappBrowserMore import kotlinx.android.synthetic.main.fragment_dapp_browser.dappBrowserProgress import kotlinx.android.synthetic.main.fragment_dapp_browser.dappBrowserRefresh -import kotlinx.android.synthetic.main.fragment_dapp_browser.dappBrowserWebView import javax.inject.Inject +import kotlinx.android.synthetic.main.fragment_dapp_browser.dappBrowserFavorite +import kotlinx.android.synthetic.main.fragment_dapp_browser.dappBrowserTabs +import kotlinx.android.synthetic.main.fragment_dapp_browser.dappBrowserTabsContent +import kotlinx.android.synthetic.main.fragment_dapp_browser.dappBrowserTabsIcon +import kotlinx.android.synthetic.main.fragment_dapp_browser.dappBrowserTransitionImage +import kotlinx.android.synthetic.main.fragment_dapp_browser.dappBrowserWebViewContainer + +private const val OVERFLOW_TABS_COUNT = 100 + +const val DAPP_SHARED_ELEMENT_ID_IMAGE_TAB = "DAPP_SHARED_ELEMENT_ID_IMAGE_TAB" class DAppBrowserFragment : BaseFragment(), OptionsBottomSheetDialog.Callback, PageCallback { @@ -48,11 +66,11 @@ class DAppBrowserFragment : BaseFragment(), OptionsBottomS private const val PAYLOAD = "DAppBrowserFragment.Payload" - fun getBundle(initialUrl: String) = bundleOf(PAYLOAD to initialUrl) + fun getBundle(payload: DAppBrowserPayload) = bundleOf(PAYLOAD to payload) } @Inject - lateinit var web3WebViewClientFactory: Web3WebViewClientFactory + lateinit var compoundWeb3Injector: CompoundWeb3Injector @Inject lateinit var webViewHolder: WebViewHolder @@ -60,10 +78,41 @@ class DAppBrowserFragment : BaseFragment(), OptionsBottomS @Inject lateinit var fileChooser: WebViewFileChooser + @Inject + lateinit var permissionAsker: WebViewPermissionAsker + + @Inject + lateinit var imageLoader: ImageLoader + private var webViewClient: Web3WebViewClient? = null var backCallback: OnBackPressedCallback? = null + private val dappBrowserWebView: WebView? + get() { + return dappBrowserWebViewContainer.getChildAt(0) as? WebView + } + + override fun onCreate(savedInstanceState: Bundle?) { + WebView.enableSlowWholeDocumentDraw() + super.onCreate(savedInstanceState) + + sharedElementEnterTransition = TransitionInflater.from(requireContext()) + .inflateTransition(android.R.transition.move).apply { + addListener( + onStart = { dappBrowserWebViewContainer.makeGone() }, // Hide WebView during transition animation + onEnd = { + dappBrowserWebViewContainer.makeVisible() + dappBrowserTransitionImage.animate() + .setDuration(300) + .alpha(0f) + .withEndAction { dappBrowserTransitionImage.makeGone() } + .start() + } + ) + } + } + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -77,11 +126,9 @@ class DAppBrowserFragment : BaseFragment(), OptionsBottomS } override fun initViews() { - webViewHolder.set(dappBrowserWebView) - dappBrowserAddressBarGroup.applyStatusBarInsets() - dappBrowserClose.setOnClickListener { viewModel.closeClicked() } + dappBrowserHide.setOnClickListener { viewModel.closeClicked() } dappBrowserBack.setOnClickListener { backClicked() } @@ -90,25 +137,40 @@ class DAppBrowserFragment : BaseFragment(), OptionsBottomS } dappBrowserForward.setOnClickListener { forwardClicked() } + dappBrowserTabs.setOnClickListener { viewModel.openTabs() } dappBrowserRefresh.setOnClickListener { refreshClicked() } - + dappBrowserFavorite.setOnClickListener { viewModel.onFavoriteClick() } dappBrowserMore.setOnClickListener { moreClicked() } requireActivity().requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER + + dappBrowserTransitionImage.transitionName = DAPP_SHARED_ELEMENT_ID_IMAGE_TAB + + setEnterSharedElementCallback(object : SharedElementCallback() { + override fun onSharedElementStart( + sharedElementNames: MutableList?, + sharedElements: MutableList?, + sharedElementSnapshots: MutableList? + ) { + val sharedView = sharedElements?.firstOrNull { it.transitionName == DAPP_SHARED_ELEMENT_ID_IMAGE_TAB } + val sharedImageView = sharedView as? ImageView + dappBrowserTransitionImage.setImageDrawable(sharedImageView?.drawable) // Set image from shared element + } + }) } override fun onDestroyView() { + dappBrowserWebViewContainer.removeAllViews() + viewModel.detachCurrentSession() super.onDestroyView() - dappBrowserWebView.uninjectWeb3() - requireActivity().requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT - - webViewHolder.release() } override fun onPause() { super.onPause() + viewModel.makePageSnapshot() + detachBackCallback() } @@ -136,12 +198,9 @@ class DAppBrowserFragment : BaseFragment(), OptionsBottomS override fun subscribe(viewModel: DAppBrowserViewModel) { setupRemoveFavouritesConfirmation(viewModel.removeFromFavouritesConfirmation) - webViewClient = web3WebViewClientFactory.create(dappBrowserWebView, viewModel.extensionsStore, viewModel::onPageChanged, this) - dappBrowserWebView.injectWeb3( - progressBar = dappBrowserProgress, - fileChooser = fileChooser, - web3Client = webViewClient!! - ) + viewModel.currentTabFlow.observe { currentTab -> + attachSession(currentTab.browserTabSession) + } viewModel.desktopModeChangedModel.observe { webViewClient?.desktopMode = it.desktopModeEnabled @@ -152,7 +211,7 @@ class DAppBrowserFragment : BaseFragment(), OptionsBottomS is Action.Authorize -> { showConfirmAuthorizeSheet(it as DappPendingConfirmation) } - Action.CloseScreen -> showCloseConfirmation(it) + Action.AcknowledgePhishingAlert -> { AcknowledgePhishingBottomSheet(requireContext(), it) .show() @@ -162,12 +221,12 @@ class DAppBrowserFragment : BaseFragment(), OptionsBottomS viewModel.browserCommandEvent.observeEvent { when (it) { - BrowserCommand.Reload -> dappBrowserWebView.reload() + BrowserCommand.Reload -> dappBrowserWebView?.reload() BrowserCommand.GoBack -> backClicked() - is BrowserCommand.OpenUrl -> dappBrowserWebView.loadUrl(it.url) + is BrowserCommand.OpenUrl -> dappBrowserWebView?.loadUrl(it.url) is BrowserCommand.ChangeDesktopMode -> { webViewClient?.desktopMode = it.enabled - dappBrowserWebView.reload() + dappBrowserWebView?.reload() } } } @@ -179,25 +238,43 @@ class DAppBrowserFragment : BaseFragment(), OptionsBottomS viewModel.currentPageAnalyzed.observe { dappBrowserAddressBar.setAddress(it.display) - dappBrowserAddressBar.showSecureIcon(it.isSecure) + dappBrowserAddressBar.showSecure(it.isSecure) + dappBrowserFavorite.setImageResource(favoriteIcon(it.isFavourite)) updateButtonsState() } + + viewModel.tabsCountFlow.observe { + if (it >= OVERFLOW_TABS_COUNT) { + dappBrowserTabsIcon.makeVisible() + dappBrowserTabsContent.text = null + } else { + dappBrowserTabsIcon.makeGone() + dappBrowserTabsContent.text = it.toString() + } + } } - private fun showCloseConfirmation(pendingConfirmation: DappPendingConfirmation<*>) { - dialog(requireContext().themed(R.style.AccentNegativeAlertDialogTheme_Reversed)) { - setPositiveButton(R.string.common_close) { _, _ -> pendingConfirmation.onConfirm() } - setNegativeButton(R.string.common_cancel) { _, _ -> pendingConfirmation.onCancel() } + private fun attachSession(session: BrowserTabSession) { + clearProgress() + session.attachToHost(createChromeClient(), this) + webViewHolder.set(session.webView) + webViewClient = session.webViewClient - setTitle(R.string.common_confirmation_title) - setMessage(R.string.common_close_confirmation_message) - } + dappBrowserWebViewContainer.removeAllViews() + dappBrowserWebViewContainer.addView(session.webView) } + private fun clearProgress() { + dappBrowserProgress.makeGone() + dappBrowserProgress.progress = 0 + } + + private fun createChromeClient() = Web3ChromeClient(permissionAsker, fileChooser, dappBrowserProgress, viewModel.viewModelScope) + private fun updateButtonsState() { - dappBrowserForward.isEnabled = dappBrowserWebView.canGoForward() - dappBrowserBack.isEnabled = dappBrowserWebView.canGoBack() + dappBrowserForward.isEnabled = dappBrowserWebView?.canGoForward() ?: false + dappBrowserBack.isEnabled = dappBrowserWebView?.canGoBack() ?: false } private fun showConfirmAuthorizeSheet(pendingConfirmation: DappPendingConfirmation) { @@ -210,19 +287,19 @@ class DAppBrowserFragment : BaseFragment(), OptionsBottomS } private fun backClicked() { - if (dappBrowserWebView.canGoBack()) { - dappBrowserWebView.goBack() + if (dappBrowserWebView?.canGoBack() == true) { + dappBrowserWebView?.goBack() } else { viewModel.closeClicked() } } private fun forwardClicked() { - dappBrowserWebView.goForward() + dappBrowserWebView?.goForward() } private fun refreshClicked() { - dappBrowserWebView.reload() + dappBrowserWebView?.reload() } private fun attachBackCallback() { @@ -245,14 +322,14 @@ class DAppBrowserFragment : BaseFragment(), OptionsBottomS backCallback = null } - override fun onFavoriteClick(payload: DAppOptionsPayload) { - viewModel.onFavoriteClick(payload) - } - override fun onDesktopModeClick() { viewModel.onDesktopClick() } + override fun onPageStarted(webView: WebView, url: String, favicon: Bitmap?) { + compoundWeb3Injector.injectForPage(webView, viewModel.extensionsStore) + } + override fun handleBrowserIntent(intent: Intent) { try { startActivity(intent) @@ -261,4 +338,16 @@ class DAppBrowserFragment : BaseFragment(), OptionsBottomS .show() } } + + override fun onPageChanged(webView: WebView, url: String?, title: String?) { + viewModel.onPageChanged(url, title) + } + + private fun favoriteIcon(isFavorite: Boolean): Int { + return if (isFavorite) { + R.drawable.ic_favorite_heart_filled + } else { + R.drawable.ic_favorite_heart_outline + } + } } diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/browser/main/DAppBrowserViewModel.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/browser/main/DAppBrowserViewModel.kt index 6dbf8a672d..381b44ec9d 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/browser/main/DAppBrowserViewModel.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/browser/main/DAppBrowserViewModel.kt @@ -12,16 +12,22 @@ import io.novafoundation.nova.common.utils.removeHexPrefix import io.novafoundation.nova.common.utils.singleReplaySharedFlow import io.novafoundation.nova.feature_account_api.domain.interfaces.SelectedAccountUseCase import io.novafoundation.nova.feature_dapp_api.data.model.BrowserHostSettings -import io.novafoundation.nova.feature_dapp_impl.DAppRouter +import io.novafoundation.nova.feature_dapp_impl.presentation.DAppRouter import io.novafoundation.nova.feature_dapp_impl.domain.DappInteractor import io.novafoundation.nova.feature_dapp_impl.domain.browser.BrowserPage import io.novafoundation.nova.feature_dapp_impl.domain.browser.BrowserPageAnalyzed import io.novafoundation.nova.feature_dapp_impl.domain.browser.DappBrowserInteractor -import io.novafoundation.nova.feature_dapp_impl.presentation.addToFavourites.AddToFavouritesPayload +import io.novafoundation.nova.feature_dapp_api.presentation.addToFavorites.AddToFavouritesPayload +import io.novafoundation.nova.feature_dapp_api.presentation.browser.main.DAppBrowserPayload import io.novafoundation.nova.feature_dapp_impl.presentation.browser.options.DAppOptionsPayload import io.novafoundation.nova.feature_dapp_impl.presentation.common.favourites.RemoveFavouritesPayload +import io.novafoundation.nova.feature_dapp_impl.presentation.search.DAppSearchCommunicator import io.novafoundation.nova.feature_dapp_impl.presentation.search.DAppSearchRequester import io.novafoundation.nova.feature_dapp_impl.presentation.search.SearchPayload +import io.novafoundation.nova.feature_dapp_impl.utils.tabs.BrowserTabService +import io.novafoundation.nova.feature_dapp_impl.utils.tabs.createAndSelectTab +import io.novafoundation.nova.feature_dapp_impl.utils.tabs.models.CurrentTabState +import io.novafoundation.nova.feature_dapp_impl.utils.tabs.models.stateId import io.novafoundation.nova.feature_dapp_impl.web3.session.Web3Session.Authorization.State import io.novafoundation.nova.feature_dapp_impl.web3.states.ExtensionStoreFactory import io.novafoundation.nova.feature_dapp_impl.web3.states.Web3ExtensionStateMachine.ExternalEvent @@ -42,7 +48,9 @@ import io.novafoundation.nova.runtime.multiNetwork.chainsById import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.distinctUntilChangedBy import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.launchIn @@ -66,10 +74,11 @@ class DAppBrowserViewModel( private val dAppInteractor: DappInteractor, private val interactor: DappBrowserInteractor, private val dAppSearchRequester: DAppSearchRequester, - private val initialUrl: String, + private val payload: DAppBrowserPayload, private val selectedAccountUseCase: SelectedAccountUseCase, private val actionAwaitableMixinFactory: ActionAwaitableMixin.Factory, - private val chainRegistry: ChainRegistry + private val chainRegistry: ChainRegistry, + private val browserTabService: BrowserTabService ) : BaseViewModel(), Web3StateMachineHost { val removeFromFavouritesConfirmation = actionAwaitableMixinFactory.confirmingAction() @@ -107,14 +116,33 @@ class DAppBrowserViewModel( .distinctUntilChanged() .shareInBackground() + private val tabsState = browserTabService.tabStateFlow + .distinctUntilChangedBy { it.stateId() } + .shareInBackground() + + val currentTabFlow = tabsState.map { it.selectedTab } + .distinctUntilChangedBy { it.stateId() } + .filterIsInstance() + .shareInBackground() + + val tabsCountFlow = tabsState.map { it.tabs.size } + .shareInBackground() + init { dAppSearchRequester.responseFlow - .onEach { it.newUrl?.let(::forceLoad) } + .filterIsInstance() + .onEach { forceLoad(it.url) } .launchIn(this) watchDangerousWebsites() - forceLoad(initialUrl) + launch { + when (payload) { + is DAppBrowserPayload.Tab -> browserTabService.selectTab(payload.id) + + is DAppBrowserPayload.Address -> browserTabService.createAndSelectTab(payload.address) + } + } } override suspend fun authorizeDApp(payload: AuthorizeDappBottomSheet.Payload): State { @@ -147,22 +175,22 @@ class DAppBrowserViewModel( _browserCommandEvent.postValue(BrowserCommand.Reload.event()) } - fun onPageChanged(url: String, title: String?) { - updateCurrentPage(url, title, synchronizedWithBrowser = true) + fun detachCurrentSession() { + browserTabService.detachCurrentSession() } - fun closeClicked() = launch { - val confirmationState = awaitConfirmation(DappPendingConfirmation.Action.CloseScreen) + fun onPageChanged(url: String?, title: String?) { + updateCurrentPage(url ?: "", title, synchronizedWithBrowser = true) + } - if (confirmationState == ConfirmationState.ALLOWED) { - exitBrowser() - } + fun closeClicked() = launch { + exitBrowser() } fun openSearch() = launch { val currentPage = currentPage.first() - dAppSearchRequester.openRequest(SearchPayload(initialUrl = currentPage.url)) + dAppSearchRequester.openRequest(SearchPayload(initialUrl = currentPage.url, SearchPayload.Request.GO_TO_URL)) } fun onMoreClicked() { @@ -172,16 +200,20 @@ class DAppBrowserViewModel( } } - fun onFavoriteClick(optionsPayload: DAppOptionsPayload) { + fun onFavoriteClick() { launch { - if (optionsPayload.isFavorite) { - removeFromFavouritesConfirmation.awaitAction(optionsPayload.currentPageTitle) + val page = currentPageAnalyzed.first() + val currentPageTitle = page.title ?: page.display + val isCurrentPageFavorite = page.isFavourite + + if (isCurrentPageFavorite) { + removeFromFavouritesConfirmation.awaitAction(currentPageTitle) - dAppInteractor.removeDAppFromFavourites(optionsPayload.url) + dAppInteractor.removeDAppFromFavourites(page.url) } else { val payload = AddToFavouritesPayload( - url = optionsPayload.url, - label = optionsPayload.currentPageTitle, + url = page.url, + label = currentPageTitle, iconLink = null ) @@ -201,6 +233,14 @@ class DAppBrowserViewModel( } } + fun openTabs() { + router.openTabs() + } + + fun makePageSnapshot() { + browserTabService.makeCurrentTabSnapshot() + } + private fun watchDangerousWebsites() { currentPageAnalyzed .filter { it.synchronizedWithBrowser && it.security == BrowserPageAnalyzed.Security.DANGEROUS } @@ -222,14 +262,8 @@ class DAppBrowserViewModel( } private suspend fun getCurrentPageOptionsPayload(): DAppOptionsPayload { - val page = currentPageAnalyzed.first() - val currentPageTitle = page.title ?: page.display - val isCurrentPageFavorite = page.isFavourite return DAppOptionsPayload( - currentPageTitle, - isCurrentPageFavorite, - isDesktopModeEnabled = desktopModeChangedModel.first().desktopModeEnabled, - url = page.url + isDesktopModeEnabled = desktopModeChangedModel.first().desktopModeEnabled ) } diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/browser/main/DappPendingConfirmation.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/browser/main/DappPendingConfirmation.kt index e38fa78e5b..60040fdd92 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/browser/main/DappPendingConfirmation.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/browser/main/DappPendingConfirmation.kt @@ -13,7 +13,5 @@ class DappPendingConfirmation( class Authorize(val content: AuthorizeDappBottomSheet.Payload) : Action() object AcknowledgePhishingAlert : Action() - - object CloseScreen : Action() } } diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/browser/main/di/DAppBrowserComponent.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/browser/main/di/DAppBrowserComponent.kt index a0b10d780e..a53cfdc724 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/browser/main/di/DAppBrowserComponent.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/browser/main/di/DAppBrowserComponent.kt @@ -5,6 +5,7 @@ import dagger.BindsInstance import dagger.Subcomponent import io.novafoundation.nova.common.di.scope.ScreenScope import io.novafoundation.nova.feature_dapp_impl.presentation.browser.main.DAppBrowserFragment +import io.novafoundation.nova.feature_dapp_api.presentation.browser.main.DAppBrowserPayload @Subcomponent( modules = [ @@ -19,7 +20,7 @@ interface DAppBrowserComponent { fun create( @BindsInstance fragment: Fragment, - @BindsInstance initialUrl: String + @BindsInstance payload: DAppBrowserPayload ): DAppBrowserComponent } diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/browser/main/di/DAppBrowserModule.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/browser/main/di/DAppBrowserModule.kt index 896af4f8d9..9613c29daa 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/browser/main/di/DAppBrowserModule.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/browser/main/di/DAppBrowserModule.kt @@ -10,10 +10,13 @@ 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.utils.permissions.PermissionsAsker +import io.novafoundation.nova.common.utils.permissions.PermissionsAskerFactory import io.novafoundation.nova.core_db.dao.BrowserHostSettingsDao import io.novafoundation.nova.feature_account_api.domain.interfaces.SelectedAccountUseCase import io.novafoundation.nova.feature_dapp_api.data.repository.BrowserHostSettingsRepository -import io.novafoundation.nova.feature_dapp_impl.DAppRouter +import io.novafoundation.nova.feature_dapp_impl.presentation.DAppRouter +import io.novafoundation.nova.feature_dapp_api.presentation.browser.main.DAppBrowserPayload import io.novafoundation.nova.feature_dapp_impl.data.repository.FavouritesDAppRepository import io.novafoundation.nova.feature_dapp_impl.data.repository.PhishingSitesRepository import io.novafoundation.nova.feature_dapp_impl.data.repository.RealBrowserHostSettingsRepository @@ -21,8 +24,10 @@ import io.novafoundation.nova.feature_dapp_impl.domain.DappInteractor import io.novafoundation.nova.feature_dapp_impl.domain.browser.DappBrowserInteractor import io.novafoundation.nova.feature_dapp_impl.presentation.browser.main.DAppBrowserViewModel import io.novafoundation.nova.feature_dapp_impl.presentation.search.DAppSearchCommunicator +import io.novafoundation.nova.feature_dapp_impl.utils.tabs.BrowserTabService import io.novafoundation.nova.feature_dapp_impl.web3.states.ExtensionStoreFactory import io.novafoundation.nova.feature_dapp_impl.web3.webview.WebViewFileChooser +import io.novafoundation.nova.feature_dapp_impl.web3.webview.WebViewPermissionAsker import io.novafoundation.nova.feature_external_sign_api.model.ExternalSignCommunicator import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry @@ -53,6 +58,20 @@ class DAppBrowserModule { fragment: Fragment ) = WebViewFileChooser(fragment) + @Provides + @ScreenScope + fun providePermissionAsker( + permissionsAskerFactory: PermissionsAskerFactory, + fragment: Fragment, + router: DAppRouter + ) = permissionsAskerFactory.create(fragment, router) + + @Provides + @ScreenScope + fun provideWebViewPermissionAsker(permissionsAsker: PermissionsAsker.Presentation): WebViewPermissionAsker { + return WebViewPermissionAsker(permissionsAsker) + } + @Provides internal fun provideViewModel(fragment: Fragment, factory: ViewModelProvider.Factory): DAppBrowserViewModel { return ViewModelProvider(fragment, factory).get(DAppBrowserViewModel::class.java) @@ -67,11 +86,12 @@ class DAppBrowserModule { selectedAccountUseCase: SelectedAccountUseCase, signRequester: ExternalSignCommunicator, searchRequester: DAppSearchCommunicator, - initialUrl: String, + payload: DAppBrowserPayload, extensionStoreFactory: ExtensionStoreFactory, dAppInteractor: DappInteractor, actionAwaitableMixinFactory: ActionAwaitableMixin.Factory, - chainRegistry: ChainRegistry + chainRegistry: ChainRegistry, + browserTabService: BrowserTabService ): ViewModel { return DAppBrowserViewModel( router = router, @@ -80,10 +100,11 @@ class DAppBrowserModule { selectedAccountUseCase = selectedAccountUseCase, signRequester = signRequester, dAppSearchRequester = searchRequester, - initialUrl = initialUrl, + payload = payload, extensionStoreFactory = extensionStoreFactory, actionAwaitableMixinFactory = actionAwaitableMixinFactory, - chainRegistry = chainRegistry + chainRegistry = chainRegistry, + browserTabService = browserTabService ) } } diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/browser/main/view/AddressBarView.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/browser/main/view/AddressBarView.kt index 4cefb563d6..629eb0ec52 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/browser/main/view/AddressBarView.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/browser/main/view/AddressBarView.kt @@ -6,6 +6,8 @@ import android.view.Gravity import android.view.View import android.widget.LinearLayout import io.novafoundation.nova.common.utils.WithContextExtensions +import io.novafoundation.nova.common.utils.setImageTintRes +import io.novafoundation.nova.common.utils.setTextColorRes import io.novafoundation.nova.common.utils.setVisible import io.novafoundation.nova.feature_dapp_impl.R import kotlinx.android.synthetic.main.view_address_bar.view.addressBarIcon @@ -25,14 +27,22 @@ class AddressBarView @JvmOverloads constructor( orientation = HORIZONTAL gravity = Gravity.CENTER - background = addRipple(getRoundedCornerDrawable(R.color.input_background, cornerSizeDp = 10), mask = getRippleMask(cornerSizeDp = 10)) + background = addRipple(getRoundedCornerDrawable(R.color.dapp_blur_navigation_background, cornerSizeDp = 10), mask = getRippleMask(cornerSizeDp = 10)) } fun setAddress(address: String) { addressBarUrl.text = address } - fun showSecureIcon(shouldShow: Boolean) { + fun showSecure(shouldShow: Boolean) { addressBarIcon.setVisible(shouldShow) + + if (shouldShow) { + addressBarUrl.setTextColorRes(R.color.text_positive) + addressBarIcon.setImageTintRes(R.color.icon_positive) + } else { + addressBarUrl.setTextColorRes(R.color.text_primary) + addressBarIcon.setImageTintRes(R.color.icon_primary) + } } } diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/browser/options/DAppOptionsPayload.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/browser/options/DAppOptionsPayload.kt index c132323ebd..c49bbe03c4 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/browser/options/DAppOptionsPayload.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/browser/options/DAppOptionsPayload.kt @@ -5,8 +5,5 @@ import kotlinx.android.parcel.Parcelize @Parcelize class DAppOptionsPayload( - val currentPageTitle: String, - val isFavorite: Boolean, - val isDesktopModeEnabled: Boolean, - val url: String + val isDesktopModeEnabled: Boolean ) : Parcelable diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/browser/options/OptionsBottomSheetDialog.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/browser/options/OptionsBottomSheetDialog.kt index a18f38e9fb..ffc9d24b9d 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/browser/options/OptionsBottomSheetDialog.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/browser/options/OptionsBottomSheetDialog.kt @@ -4,7 +4,6 @@ import android.content.Context import android.view.View import io.novafoundation.nova.common.view.bottomSheet.list.fixed.FixedListBottomSheet import io.novafoundation.nova.common.view.bottomSheet.list.fixed.switcherItem -import io.novafoundation.nova.common.view.bottomSheet.list.fixed.textItem import io.novafoundation.nova.feature_dapp_impl.R class OptionsBottomSheetDialog( @@ -16,14 +15,6 @@ class OptionsBottomSheetDialog( init { setTitle(R.string.dapp_options_title) - textItem( - if (payload.isFavorite) R.drawable.ic_unfavorite_heart_outline else R.drawable.ic_favorite_heart_outline, - if (payload.isFavorite) R.string.dapp_options_remove_from_favorite else R.string.dapp_options_add_to_favorite, - showArrow = false, - applyIconTint = true, - ::toggleFavorite - ) - switcherItem( R.drawable.ic_desktop, R.string.dapp_options_desktop_mode, @@ -32,16 +23,11 @@ class OptionsBottomSheetDialog( ) } - private fun toggleFavorite(view: View) { - callback.onFavoriteClick(payload) - } - private fun toggleDesktopMode(view: View) { callback.onDesktopModeClick() } interface Callback { - fun onFavoriteClick(payload: DAppOptionsPayload) fun onDesktopModeClick() } } diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/common/DAppClickHandler.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/common/DAppClickHandler.kt new file mode 100644 index 0000000000..bed44021ed --- /dev/null +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/common/DAppClickHandler.kt @@ -0,0 +1,5 @@ +package io.novafoundation.nova.feature_dapp_impl.presentation.common + +interface DAppClickHandler { + fun onDAppClicked(item: DappModel) +} diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/common/DappCategoryListAdapter.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/common/DappCategoryListAdapter.kt new file mode 100644 index 0000000000..dd6d71c89e --- /dev/null +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/common/DappCategoryListAdapter.kt @@ -0,0 +1,60 @@ +package io.novafoundation.nova.feature_dapp_impl.presentation.common + +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.LinearSnapHelper +import io.novafoundation.nova.common.list.BaseListAdapter +import io.novafoundation.nova.common.list.BaseViewHolder +import io.novafoundation.nova.common.utils.inflateChild +import io.novafoundation.nova.feature_dapp_impl.R +import kotlinx.android.synthetic.main.item_dapp_group.view.dappRecyclerView +import kotlinx.android.synthetic.main.item_dapp_group.view.itemDAppCategoryTitle + +class DappCategoryListAdapter( + private val handler: DAppClickHandler +) : BaseListAdapter(DappCategoryDiffCallback) { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DappCategoryViewHolder { + return DappCategoryViewHolder(parent.inflateChild(R.layout.item_dapp_group), handler) + } + + override fun onBindViewHolder(holder: DappCategoryViewHolder, position: Int) { + holder.bind(getItem(position)) + } +} + +private object DappCategoryDiffCallback : DiffUtil.ItemCallback() { + + override fun areItemsTheSame(oldItem: DappCategoryModel, newItem: DappCategoryModel): Boolean { + return oldItem.categoryName == newItem.categoryName + } + + override fun areContentsTheSame(oldItem: DappCategoryModel, newItem: DappCategoryModel): Boolean { + return oldItem == newItem + } +} + +class DappCategoryViewHolder( + view: View, + itemHandler: DAppClickHandler, +) : BaseViewHolder(view) { + + private val adapter = DappListAdapter(itemHandler) + + init { + itemView.dappRecyclerView.layoutManager = GridLayoutManager(itemView.context, 3, GridLayoutManager.HORIZONTAL, false) + itemView.dappRecyclerView.adapter = adapter + itemView.dappRecyclerView.itemAnimator = null + val snapHelper = LinearSnapHelper() + snapHelper.attachToRecyclerView(itemView.dappRecyclerView) + } + + fun bind(item: DappCategoryModel) = with(itemView) { + itemDAppCategoryTitle.text = item.categoryName + adapter.submitList(item.items) + } + + override fun unbind() {} +} diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/common/DappCategoryModel.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/common/DappCategoryModel.kt new file mode 100644 index 0000000000..cdf6f0a60b --- /dev/null +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/common/DappCategoryModel.kt @@ -0,0 +1,6 @@ +package io.novafoundation.nova.feature_dapp_impl.presentation.common + +data class DappCategoryModel( + val categoryName: String, + val items: List +) diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/common/DappListAdapter.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/common/DappListAdapter.kt index cde0bd6ce1..3934dfb78c 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/common/DappListAdapter.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/common/DappListAdapter.kt @@ -1,22 +1,13 @@ package io.novafoundation.nova.feature_dapp_impl.presentation.common import android.view.ViewGroup -import androidx.recyclerview.widget.DiffUtil import io.novafoundation.nova.common.list.BaseListAdapter import io.novafoundation.nova.common.list.BaseViewHolder -import io.novafoundation.nova.feature_dapp_impl.R import io.novafoundation.nova.feature_dapp_api.presentation.view.DAppView class DappListAdapter( - private val handler: Handler -) : BaseListAdapter(DappDiffCallback) { - - interface Handler { - - fun onDAppClicked(item: DappModel) - - fun onItemFavouriteClicked(item: DappModel) - } + private val handler: DAppClickHandler +) : BaseListAdapter(DappModelDiffCallback) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DappViewHolder { return DappViewHolder(DAppView.createUsingMathParentWidth(parent.context), handler) @@ -27,36 +18,16 @@ class DappListAdapter( } } -private object DappDiffCallback : DiffUtil.ItemCallback() { - - override fun areItemsTheSame(oldItem: DappModel, newItem: DappModel): Boolean { - return oldItem.url == newItem.url - } - - override fun areContentsTheSame(oldItem: DappModel, newItem: DappModel): Boolean { - return oldItem == newItem - } -} - class DappViewHolder( private val dAppView: DAppView, - private val itemHandler: DappListAdapter.Handler, + private val itemHandler: DAppClickHandler, ) : BaseViewHolder(dAppView) { fun bind(item: DappModel) = with(dAppView) { setTitle(item.name) setSubtitle(item.description) setIconUrl(item.iconUrl) - activateActionIcon(item.isFavourite) - setOnActionClickListener { itemHandler.onItemFavouriteClicked(item) } - - if (item.isFavourite) { - setActionResource(R.drawable.ic_favorite_heart_filled) - setActionTintRes(null) - } else { - setActionResource(R.drawable.ic_favorite_heart_outline) - setActionTintRes(R.color.icon_secondary) - } + setFavoriteIconVisible(item.isFavourite) setOnClickListener { itemHandler.onDAppClicked(item) } } diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/common/DappModel.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/common/DappModel.kt index 9e479ba40f..8b93a641dd 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/common/DappModel.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/common/DappModel.kt @@ -1,5 +1,11 @@ package io.novafoundation.nova.feature_dapp_impl.presentation.common +import io.novafoundation.nova.feature_dapp_api.data.model.DApp +import io.novafoundation.nova.feature_dapp_api.data.model.DappCategory +import io.novafoundation.nova.feature_dapp_impl.data.model.FavouriteDApp +import io.novafoundation.nova.feature_dapp_impl.domain.common.dappToFavorite +import io.novafoundation.nova.feature_dapp_impl.domain.common.favouriteToDApp + data class DappModel( val name: String, val description: String, @@ -7,3 +13,40 @@ data class DappModel( val isFavourite: Boolean, val url: String ) + +fun mapDappCategoriesToDescription(categories: Collection) = categories.joinToString { it.name } + +fun mapDappCategoryToDappCategoryModel(category: DappCategory, dApps: List) = DappCategoryModel( + categoryName = category.name, + items = dApps.map { mapDappToDappModel(it) } +) + +fun mapDappToDappModel(dApp: DApp) = with(dApp) { + DappModel( + name = name, + description = description, + iconUrl = iconLink, + url = url, + isFavourite = isFavourite + ) +} + +fun mapDappModelToDApp(dApp: DappModel) = with(dApp) { + DApp( + name = name, + description = description, + iconLink = iconUrl, + url = url, + isFavourite = isFavourite + ) +} + +fun mapFavoriteDappToDappModel(favoriteDapp: FavouriteDApp): DappModel { + val dapp = favouriteToDApp(favoriteDapp) + return mapDappToDappModel(dapp) +} + +fun mapDAppModelToFavorite(model: DappModel, orderingIndex: Int): FavouriteDApp { + val dapp = mapDappModelToDApp(model) + return dappToFavorite(dapp, orderingIndex) +} diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/common/DappModelDiffCallback.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/common/DappModelDiffCallback.kt new file mode 100644 index 0000000000..64babac19c --- /dev/null +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/common/DappModelDiffCallback.kt @@ -0,0 +1,14 @@ +package io.novafoundation.nova.feature_dapp_impl.presentation.common + +import androidx.recyclerview.widget.DiffUtil + +object DappModelDiffCallback : DiffUtil.ItemCallback() { + + override fun areItemsTheSame(oldItem: DappModel, newItem: DappModel): Boolean { + return oldItem.url == newItem.url + } + + override fun areContentsTheSame(oldItem: DappModel, newItem: DappModel): Boolean { + return oldItem == newItem + } +} diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/common/DappModelMapper.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/common/DappModelMapper.kt new file mode 100644 index 0000000000..91cca77f0a --- /dev/null +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/common/DappModelMapper.kt @@ -0,0 +1,13 @@ +package io.novafoundation.nova.feature_dapp_impl.presentation.common + +import io.novafoundation.nova.feature_dapp_api.data.model.DappCategory +import io.novafoundation.nova.feature_dapp_impl.presentation.main.model.DAppCategoryModel + +fun dappCategoryToUi(dappCategory: DappCategory, isSelected: Boolean): DAppCategoryModel { + return DAppCategoryModel( + id = dappCategory.id, + name = dappCategory.name, + selected = isSelected, + iconUrl = dappCategory.iconUrl + ) +} diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/favorites/DAppFavoritesViewModel.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/favorites/DAppFavoritesViewModel.kt new file mode 100644 index 0000000000..8ee0a5604c --- /dev/null +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/favorites/DAppFavoritesViewModel.kt @@ -0,0 +1,67 @@ +package io.novafoundation.nova.feature_dapp_impl.presentation.favorites + +import io.novafoundation.nova.common.base.BaseViewModel +import io.novafoundation.nova.common.mixin.actionAwaitable.ActionAwaitableMixin +import io.novafoundation.nova.common.mixin.actionAwaitable.confirmingAction +import io.novafoundation.nova.feature_dapp_api.presentation.browser.main.DAppBrowserPayload +import io.novafoundation.nova.feature_dapp_impl.data.model.FavouriteDApp +import io.novafoundation.nova.feature_dapp_impl.domain.DappInteractor +import io.novafoundation.nova.feature_dapp_impl.presentation.DAppRouter +import io.novafoundation.nova.feature_dapp_impl.presentation.common.DappModel +import io.novafoundation.nova.feature_dapp_impl.presentation.common.favourites.RemoveFavouritesPayload +import io.novafoundation.nova.feature_dapp_impl.presentation.common.mapDAppModelToFavorite +import io.novafoundation.nova.feature_dapp_impl.presentation.common.mapFavoriteDappToDappModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch + +class DAppFavoritesViewModel( + private val router: DAppRouter, + private val interactor: DappInteractor, + private val actionAwaitableMixinFactory: ActionAwaitableMixin.Factory, +) : BaseViewModel() { + + val removeFavouriteConfirmationAwaitable = actionAwaitableMixinFactory.confirmingAction() + + private val favoriteDAppsFlow = MutableStateFlow>(emptyList()) + + val favoriteDAppsUIFlow = favoriteDAppsFlow + .map { dapps -> dapps.map { mapFavoriteDappToDappModel(it) } } + .shareInBackground() + + init { + launch { + updateDApps() + } + } + + fun backClicked() { + router.back() + } + + fun openDApp(dapp: DappModel) { + router.openDAppBrowser(DAppBrowserPayload.Address(dapp.url)) + } + + fun onFavoriteClicked(dapp: DappModel) = launch { + removeFavouriteConfirmationAwaitable.awaitAction(dapp.name) + + interactor.removeDAppFromFavourites(dapp.url) + + // Update list, since item was removed + updateDApps() + } + + fun changeDAppOrdering(newOrdering: List) = launch { + val favoriteItems = newOrdering.mapIndexed { index, dappModel -> + mapDAppModelToFavorite(dappModel, index) + } + + interactor.updateFavoriteDapps(favoriteItems) + } + + private suspend fun updateDApps() { + val dapps = interactor.getFavoriteDApps() + favoriteDAppsFlow.value = dapps + } +} diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/favorites/DappDraggableFavoritesAdapter.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/favorites/DappDraggableFavoritesAdapter.kt new file mode 100644 index 0000000000..98060f4eb4 --- /dev/null +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/favorites/DappDraggableFavoritesAdapter.kt @@ -0,0 +1,83 @@ +package io.novafoundation.nova.feature_dapp_impl.presentation.favorites + +import android.annotation.SuppressLint +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView.ViewHolder +import coil.ImageLoader +import io.novafoundation.nova.common.utils.inflateChild +import io.novafoundation.nova.common.utils.recyclerView.dragging.OnItemDragCallback +import io.novafoundation.nova.common.utils.recyclerView.dragging.StartDragListener +import io.novafoundation.nova.common.utils.recyclerView.dragging.prepareForDragging +import io.novafoundation.nova.feature_dapp_impl.R +import io.novafoundation.nova.feature_dapp_impl.presentation.common.DappModel +import io.novafoundation.nova.feature_external_sign_api.presentation.dapp.showDAppIcon +import java.util.Collections +import kotlinx.android.synthetic.main.item_dapp_favorite_dragable.view.itemDraggableFavoriteDAppIcon +import kotlinx.android.synthetic.main.item_dapp_favorite_dragable.view.itemDraggableFavoriteDAppSubtitle +import kotlinx.android.synthetic.main.item_dapp_favorite_dragable.view.itemDraggableFavoriteDAppTitle +import kotlinx.android.synthetic.main.item_dapp_favorite_dragable.view.itemDraggableFavoriteDappDragHandle +import kotlinx.android.synthetic.main.item_dapp_favorite_dragable.view.itemDraggableFavoriteDappFavoriteIcon + +class DappDraggableFavoritesAdapter( + private val imageLoader: ImageLoader, + private val handler: Handler, + private val startDragListener: StartDragListener +) : RecyclerView.Adapter(), OnItemDragCallback { + + interface Handler { + fun onDAppClicked(dapp: DappModel) + + fun onDAppFavoriteClicked(dapp: DappModel) + + fun onItemOrderingChanged(dapps: List) + } + + private val dapps = mutableListOf() + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DappDraggableFavoritesViewHolder { + return DappDraggableFavoritesViewHolder(parent.inflateChild(R.layout.item_dapp_favorite_dragable), imageLoader, handler, startDragListener) + } + + override fun getItemCount(): Int { + return dapps.size + } + + override fun onBindViewHolder(holder: DappDraggableFavoritesViewHolder, position: Int) { + holder.bind(dapps[position]) + } + + override fun onItemMove(fromPosition: Int, toPosition: Int) { + Collections.swap(dapps, fromPosition, toPosition) + notifyItemMoved(toPosition, fromPosition) + handler.onItemOrderingChanged(dapps) + } + + @SuppressLint("NotifyDataSetChanged") + fun submitList(dapps: List) { + this.dapps.clear() + this.dapps.addAll(dapps) + notifyDataSetChanged() + } +} + +class DappDraggableFavoritesViewHolder( + view: View, + private val imageLoader: ImageLoader, + private val itemHandler: DappDraggableFavoritesAdapter.Handler, + private val startDragListener: StartDragListener +) : ViewHolder(view) { + + @SuppressLint("ClickableViewAccessibility") + fun bind(item: DappModel) = with(itemView) { + itemDraggableFavoriteDAppIcon.showDAppIcon(item.iconUrl, imageLoader) + itemDraggableFavoriteDAppTitle.text = item.name + itemDraggableFavoriteDAppSubtitle.text = item.description + + itemDraggableFavoriteDappDragHandle.prepareForDragging(this@DappDraggableFavoritesViewHolder, startDragListener) + + itemDraggableFavoriteDappFavoriteIcon.setOnClickListener { itemHandler.onDAppFavoriteClicked(item) } + setOnClickListener { itemHandler.onDAppClicked(item) } + } +} diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/favorites/DappFavoritesFragment.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/favorites/DappFavoritesFragment.kt new file mode 100644 index 0000000000..d4f6f1c95f --- /dev/null +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/favorites/DappFavoritesFragment.kt @@ -0,0 +1,78 @@ +package io.novafoundation.nova.feature_dapp_impl.presentation.favorites + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.RecyclerView +import coil.ImageLoader +import io.novafoundation.nova.common.base.BaseBottomSheetFragment +import io.novafoundation.nova.common.di.FeatureUtils +import io.novafoundation.nova.common.utils.applyStatusBarInsets +import io.novafoundation.nova.common.utils.recyclerView.dragging.SimpleItemDragHelperCallback +import io.novafoundation.nova.common.utils.recyclerView.dragging.StartDragListener +import io.novafoundation.nova.feature_dapp_api.di.DAppFeatureApi +import io.novafoundation.nova.feature_dapp_impl.R +import io.novafoundation.nova.feature_dapp_impl.di.DAppFeatureComponent +import io.novafoundation.nova.feature_dapp_impl.presentation.common.DappModel +import io.novafoundation.nova.feature_dapp_impl.presentation.common.favourites.setupRemoveFavouritesConfirmation +import javax.inject.Inject +import kotlinx.android.synthetic.main.fragment_favorites_dapp.favoritesDappList +import kotlinx.android.synthetic.main.fragment_favorites_dapp.favoritesDappToolbar + +class DappFavoritesFragment : BaseBottomSheetFragment(), DappDraggableFavoritesAdapter.Handler, StartDragListener { + + @Inject + protected lateinit var imageLoader: ImageLoader + + private val adapter by lazy(LazyThreadSafetyMode.NONE) { DappDraggableFavoritesAdapter(imageLoader, this, this) } + + private val itemDragHelper by lazy(LazyThreadSafetyMode.NONE) { ItemTouchHelper(SimpleItemDragHelperCallback(adapter)) } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return layoutInflater.inflate(R.layout.fragment_favorites_dapp, container, false) + } + + override fun initViews() { + favoritesDappToolbar.applyStatusBarInsets() + favoritesDappToolbar.setHomeButtonListener { viewModel.backClicked() } + favoritesDappList.adapter = adapter + itemDragHelper.attachToRecyclerView(favoritesDappList) + } + + override fun inject() { + FeatureUtils.getFeature(this, DAppFeatureApi::class.java) + .dAppFavoritesComponentFactory() + .create(this) + .inject(this) + } + + override fun subscribe(viewModel: DAppFavoritesViewModel) { + setupRemoveFavouritesConfirmation(viewModel.removeFavouriteConfirmationAwaitable) + + viewModel.favoriteDAppsUIFlow.observe { + adapter.submitList(it) + } + } + + override fun onDAppClicked(dapp: DappModel) { + viewModel.openDApp(dapp) + } + + override fun onDAppFavoriteClicked(dapp: DappModel) { + viewModel.onFavoriteClicked(dapp) + } + + override fun onItemOrderingChanged(dapps: List) { + viewModel.changeDAppOrdering(dapps) + } + + override fun requestDrag(viewHolder: RecyclerView.ViewHolder) { + itemDragHelper.startDrag(viewHolder) + } +} diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/favorites/di/DAppFavoritesComponent.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/favorites/di/DAppFavoritesComponent.kt new file mode 100644 index 0000000000..51564ec20b --- /dev/null +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/favorites/di/DAppFavoritesComponent.kt @@ -0,0 +1,26 @@ +package io.novafoundation.nova.feature_dapp_impl.presentation.favorites.di + +import androidx.fragment.app.Fragment +import dagger.BindsInstance +import dagger.Subcomponent +import io.novafoundation.nova.common.di.scope.ScreenScope +import io.novafoundation.nova.feature_dapp_impl.presentation.favorites.DappFavoritesFragment + +@Subcomponent( + modules = [ + DAppFavoritesModule::class + ] +) +@ScreenScope +interface DAppFavoritesComponent { + + @Subcomponent.Factory + interface Factory { + + fun create( + @BindsInstance fragment: Fragment + ): DAppFavoritesComponent + } + + fun inject(fragment: DappFavoritesFragment) +} diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/favorites/di/DAppFavoritesModule.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/favorites/di/DAppFavoritesModule.kt new file mode 100644 index 0000000000..bd9a5da602 --- /dev/null +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/favorites/di/DAppFavoritesModule.kt @@ -0,0 +1,38 @@ +package io.novafoundation.nova.feature_dapp_impl.presentation.favorites.di + +import androidx.fragment.app.Fragment +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import dagger.Module +import dagger.Provides +import dagger.multibindings.IntoMap +import io.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.feature_dapp_impl.presentation.DAppRouter +import io.novafoundation.nova.feature_dapp_impl.domain.DappInteractor +import io.novafoundation.nova.feature_dapp_impl.presentation.favorites.DAppFavoritesViewModel + +@Module(includes = [ViewModelModule::class]) +class DAppFavoritesModule { + + @Provides + internal fun provideViewModel(fragment: Fragment, factory: ViewModelProvider.Factory): DAppFavoritesViewModel { + return ViewModelProvider(fragment, factory).get(DAppFavoritesViewModel::class.java) + } + + @Provides + @IntoMap + @ViewModelKey(DAppFavoritesViewModel::class) + fun provideViewModel( + router: DAppRouter, + interactor: DappInteractor, + actionAwaitableMixinFactory: ActionAwaitableMixin.Factory, + ): ViewModel { + return DAppFavoritesViewModel( + router, + interactor, + actionAwaitableMixinFactory + ) + } +} diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/main/DAppHeaderAdapter.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/main/DAppHeaderAdapter.kt index 3049132566..f0c79485d0 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/main/DAppHeaderAdapter.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/main/DAppHeaderAdapter.kt @@ -2,18 +2,37 @@ package io.novafoundation.nova.feature_dapp_impl.presentation.main import android.view.View import android.view.ViewGroup +import androidx.core.view.isGone +import androidx.core.view.isInvisible import androidx.recyclerview.widget.RecyclerView import coil.ImageLoader import io.novafoundation.nova.common.utils.inflateChild +import io.novafoundation.nova.common.utils.setVisible import io.novafoundation.nova.feature_account_api.domain.interfaces.SelectedWalletModel import io.novafoundation.nova.feature_dapp_impl.R +import io.novafoundation.nova.feature_dapp_impl.presentation.common.DAppClickHandler +import io.novafoundation.nova.feature_dapp_impl.presentation.common.DappModel +import io.novafoundation.nova.feature_dapp_impl.presentation.main.model.DAppCategoryModel +import kotlinx.android.synthetic.main.item_dapp_header.view.mainDappCategories +import kotlinx.android.synthetic.main.item_dapp_header.view.categorizedDappsCategoriesShimmering +import kotlinx.android.synthetic.main.item_dapp_header.view.dAppMainFavoriteDAppList +import kotlinx.android.synthetic.main.item_dapp_header.view.dAppMainFavoriteDAppTitle +import kotlinx.android.synthetic.main.item_dapp_header.view.dAppMainFavoriteDAppsShow import kotlinx.android.synthetic.main.item_dapp_header.view.dappMainManage import kotlinx.android.synthetic.main.item_dapp_header.view.dappMainSearch import kotlinx.android.synthetic.main.item_dapp_header.view.dappMainSelectedWallet -class DAppHeaderAdapter(val imageLoader: ImageLoader, val handler: Handler) : RecyclerView.Adapter() { +class DAppHeaderAdapter( + val imageLoader: ImageLoader, + val headerHandler: Handler, + val categoriesHandler: DappCategoriesAdapter.Handler, + val dAppClickHandler: DAppClickHandler +) : RecyclerView.Adapter() { private var walletModel: SelectedWalletModel? = null + private var categories: List = emptyList() + private var favoritesDApps: List = emptyList() + private var showCategoriesShimmering: Boolean = false interface Handler { fun onWalletClick() @@ -21,14 +40,44 @@ class DAppHeaderAdapter(val imageLoader: ImageLoader, val handler: Handler) : Re fun onSearchClick() fun onManageClick() + + fun onManageFavoritesClick() + + fun onCategoryClicked(id: String) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HeaderHolder { - return HeaderHolder(imageLoader, parent.inflateChild(R.layout.item_dapp_header), handler) + return HeaderHolder( + imageLoader, + parent.inflateChild(R.layout.item_dapp_header), + headerHandler, + categoriesHandler, + dAppClickHandler + ) } override fun onBindViewHolder(holder: HeaderHolder, position: Int) { - holder.bind(walletModel) + holder.bind( + walletModel, + categories, + favoritesDApps, + showCategoriesShimmering + ) + } + + override fun onBindViewHolder(holder: HeaderHolder, position: Int, payloads: MutableList) { + if (payloads.isEmpty()) { + onBindViewHolder(holder, position) + } else { + payloads.filterIsInstance().forEach { + when (it) { + Payload.WALLET -> holder.bindWallet(walletModel) + Payload.FAVORITES -> holder.bindFavoritres(favoritesDApps) + Payload.CATEGORIES -> holder.bindCategories(categories) + Payload.CATEGORIES_SHIMMERING -> holder.bindCategoriesShimmering(showCategoriesShimmering) + } + } + } } override fun getItemCount(): Int { @@ -37,19 +86,78 @@ class DAppHeaderAdapter(val imageLoader: ImageLoader, val handler: Handler) : Re fun setWallet(walletModel: SelectedWalletModel) { this.walletModel = walletModel - notifyItemChanged(0, true) + notifyItemChanged(0, Payload.WALLET) + } + + fun setFavorites(favoritesDApps: List) { + this.favoritesDApps = favoritesDApps + notifyItemChanged(0, Payload.FAVORITES) + } + + fun setCategories(categories: List) { + this.categories = categories + notifyItemChanged(0, Payload.CATEGORIES) + } + + fun showCategoriesShimmering(show: Boolean) { + showCategoriesShimmering = show + notifyItemChanged(0, Payload.CATEGORIES_SHIMMERING) } } -class HeaderHolder(private val imageLoader: ImageLoader, view: View, handler: DAppHeaderAdapter.Handler) : RecyclerView.ViewHolder(view) { +class HeaderHolder( + imageLoader: ImageLoader, + view: View, + headerHandler: DAppHeaderAdapter.Handler, + categoriesHandler: DappCategoriesAdapter.Handler, + dAppClickHandler: DAppClickHandler +) : RecyclerView.ViewHolder(view) { + + private val categoriesAdapter = DappCategoriesAdapter(imageLoader, categoriesHandler) + private val favoritesAdapter = DappFavoritesAdapter(imageLoader, dAppClickHandler) init { - view.dappMainSelectedWallet.setOnClickListener { handler.onWalletClick() } - view.dappMainSearch.setOnClickListener { handler.onSearchClick() } - view.dappMainManage.setOnClickListener { handler.onManageClick() } + view.dappMainSelectedWallet.setOnClickListener { headerHandler.onWalletClick() } + view.dappMainSearch.setOnClickListener { headerHandler.onSearchClick() } + view.dappMainManage.setOnClickListener { headerHandler.onManageClick() } + view.mainDappCategories.adapter = categoriesAdapter + view.dAppMainFavoriteDAppList.adapter = favoritesAdapter + view.dAppMainFavoriteDAppsShow.setOnClickListener { headerHandler.onManageFavoritesClick() } } - fun bind(walletModel: SelectedWalletModel?) { - walletModel?.let { itemView.dappMainSelectedWallet.setModel(walletModel) } + fun bind( + walletModel: SelectedWalletModel?, + categoriesState: List, + favoritesDApps: List, + showCategoriesShimmering: Boolean + ) { + bindWallet(walletModel) + bindCategories(categoriesState) + bindFavoritres(favoritesDApps) + bindCategoriesShimmering(showCategoriesShimmering) } + + fun bindWallet(walletModel: SelectedWalletModel?) = with(itemView) { + walletModel?.let { dappMainSelectedWallet.setModel(walletModel) } + } + + fun bindCategories(categoriesState: List) = with(itemView) { + categoriesAdapter.submitList(categoriesState) + } + + fun bindFavoritres(favoritesDApps: List) = with(itemView) { + favoritesAdapter.submitList(favoritesDApps) + dAppMainFavoriteDAppList.isGone = favoritesDApps.isEmpty() + dAppMainFavoriteDAppTitle.isGone = favoritesDApps.isEmpty() + dAppMainFavoriteDAppsShow.isGone = favoritesDApps.isEmpty() + } + + fun bindCategoriesShimmering(showCategoriesShimmering: Boolean) = with(itemView) { + categorizedDappsCategoriesShimmering.setVisible(showCategoriesShimmering, falseState = View.INVISIBLE) + mainDappCategories.isInvisible = showCategoriesShimmering + } +} + +private enum class Payload { + WALLET, FAVORITES, CATEGORIES, CATEGORIES_SHIMMERING } diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/main/DAppItemDecoration.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/main/DAppItemDecoration.kt deleted file mode 100644 index f95bdbbebd..0000000000 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/main/DAppItemDecoration.kt +++ /dev/null @@ -1,16 +0,0 @@ -package io.novafoundation.nova.feature_dapp_impl.presentation.main - -import android.content.Context -import androidx.recyclerview.widget.RecyclerView -import io.novafoundation.nova.common.list.decoration.BackgroundItemDecoration - -class DAppItemDecoration(context: Context) : BackgroundItemDecoration( - context = context, - outerHorizontalMarginDp = 16, - innerVerticalPaddingDp = 12 -) { - - override fun shouldApplyDecoration(holder: RecyclerView.ViewHolder): Boolean { - return holder !is HeaderHolder - } -} diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/main/DappCategoriesAdapter.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/main/DappCategoriesAdapter.kt index 7d31e543e0..bd3e374c32 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/main/DappCategoriesAdapter.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/main/DappCategoriesAdapter.kt @@ -2,18 +2,23 @@ package io.novafoundation.nova.feature_dapp_impl.presentation.main import android.view.View import android.view.ViewGroup +import androidx.core.content.ContextCompat import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView +import coil.ImageLoader import io.novafoundation.nova.common.list.PayloadGenerator import io.novafoundation.nova.common.list.resolvePayload import io.novafoundation.nova.common.utils.inflateChild +import io.novafoundation.nova.common.utils.loadOrHide import io.novafoundation.nova.feature_dapp_impl.R import io.novafoundation.nova.feature_dapp_impl.presentation.main.model.DAppCategoryModel import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.item_dapp_category.view.itemDappCategory +import kotlinx.android.synthetic.main.item_dapp_category.view.itemDappCategoryIcon +import kotlinx.android.synthetic.main.item_dapp_category.view.itemDappCategoryText class DappCategoriesAdapter( + private val imageLoader: ImageLoader, private val handler: Handler, ) : ListAdapter(DappDiffCallback) { @@ -23,7 +28,7 @@ class DappCategoriesAdapter( } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DappCategoryViewHolder { - return DappCategoryViewHolder(parent.inflateChild(R.layout.item_dapp_category), handler) + return DappCategoryViewHolder(parent.inflateChild(R.layout.item_dapp_category), imageLoader, handler) } override fun onBindViewHolder(holder: DappCategoryViewHolder, position: Int, payloads: MutableList) { @@ -60,18 +65,27 @@ private object DappDiffCallback : DiffUtil.ItemCallback() { class DappCategoryViewHolder( override val containerView: View, + private val imageLoader: ImageLoader, private val itemHandler: DappCategoriesAdapter.Handler, ) : RecyclerView.ViewHolder(containerView), LayoutContainer { fun bind(item: DAppCategoryModel) = with(containerView) { - itemDappCategory.text = item.name + itemDappCategoryIcon.loadOrHide(item.iconUrl, imageLoader) + itemDappCategoryText.text = item.name bindSelected(item.selected) containerView.setOnClickListener { itemHandler.onCategoryClicked(item.id) } } - fun bindSelected(isSelected: Boolean) = with(containerView) { - itemDappCategory.isSelected = isSelected + fun bindSelected(isSelected: Boolean) { + itemView.isSelected = isSelected + + // We must set tint to image view programmatically since we can't specify the state for default color in state list + if (isSelected) { + itemView.itemDappCategoryIcon.setColorFilter(ContextCompat.getColor(itemView.context, R.color.icon_primary_on_content)) + } else { + itemView.itemDappCategoryIcon.clearColorFilter() + } } } diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/main/DappFavoritesAdapter.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/main/DappFavoritesAdapter.kt new file mode 100644 index 0000000000..72214a7016 --- /dev/null +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/main/DappFavoritesAdapter.kt @@ -0,0 +1,43 @@ +package io.novafoundation.nova.feature_dapp_impl.presentation.main + +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView.ViewHolder +import coil.ImageLoader +import io.novafoundation.nova.common.utils.inflateChild +import io.novafoundation.nova.feature_dapp_impl.R +import io.novafoundation.nova.feature_dapp_impl.presentation.common.DAppClickHandler +import io.novafoundation.nova.feature_dapp_impl.presentation.common.DappModel +import io.novafoundation.nova.feature_dapp_impl.presentation.common.DappModelDiffCallback +import io.novafoundation.nova.feature_external_sign_api.presentation.dapp.showDAppIcon +import kotlinx.android.synthetic.main.item_favorite_dapp.view.itemFavoriteDAppIcon +import kotlinx.android.synthetic.main.item_favorite_dapp.view.itemFavoriteDAppTitle + +class DappFavoritesAdapter( + private val imageLoader: ImageLoader, + private val handler: DAppClickHandler +) : ListAdapter(DappModelDiffCallback) { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FavoriteDappViewHolder { + return FavoriteDappViewHolder(parent.inflateChild(R.layout.item_favorite_dapp), imageLoader, handler) + } + + override fun onBindViewHolder(holder: FavoriteDappViewHolder, position: Int) { + holder.bind(getItem(position)) + } +} + +class FavoriteDappViewHolder( + private val view: View, + private val imageLoader: ImageLoader, + private val itemHandler: DAppClickHandler, +) : ViewHolder(view) { + + fun bind(item: DappModel) = with(view) { + itemFavoriteDAppIcon.showDAppIcon(item.iconUrl, imageLoader) + itemFavoriteDAppTitle.text = item.name + + setOnClickListener { itemHandler.onDAppClicked(item) } + } +} diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/main/MainDAppFragment.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/main/MainDAppFragment.kt index e1c9220177..d3acb05296 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/main/MainDAppFragment.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/main/MainDAppFragment.kt @@ -1,16 +1,13 @@ package io.novafoundation.nova.feature_dapp_impl.presentation.main -import android.graphics.Rect import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.ConcatAdapter -import androidx.recyclerview.widget.RecyclerView import coil.ImageLoader import io.novafoundation.nova.common.base.BaseFragment import io.novafoundation.nova.common.di.FeatureUtils -import io.novafoundation.nova.common.list.NestedAdapter import io.novafoundation.nova.common.list.CustomPlaceholderAdapter import io.novafoundation.nova.common.mixin.impl.observeBrowserEvents import io.novafoundation.nova.common.presentation.LoadingState @@ -18,34 +15,26 @@ import io.novafoundation.nova.common.utils.applyStatusBarInsets import io.novafoundation.nova.feature_dapp_api.di.DAppFeatureApi import io.novafoundation.nova.feature_dapp_impl.R import io.novafoundation.nova.feature_dapp_impl.di.DAppFeatureComponent -import io.novafoundation.nova.feature_dapp_impl.presentation.common.DappListAdapter +import io.novafoundation.nova.feature_dapp_impl.presentation.common.DAppClickHandler +import io.novafoundation.nova.feature_dapp_impl.presentation.common.DappCategoryListAdapter import io.novafoundation.nova.feature_dapp_impl.presentation.common.DappModel -import io.novafoundation.nova.feature_dapp_impl.presentation.common.favourites.setupRemoveFavouritesConfirmation import javax.inject.Inject -import kotlinx.android.synthetic.main.fragment_dapp_main.dappRecyclerView +import kotlinx.android.synthetic.main.fragment_dapp_main.dappRecyclerViewCatalog class MainDAppFragment : BaseFragment(), - DappListAdapter.Handler, + DAppClickHandler, DAppHeaderAdapter.Handler, DappCategoriesAdapter.Handler { @Inject protected lateinit var imageLoader: ImageLoader - private val headerAdapter by lazy(LazyThreadSafetyMode.NONE) { DAppHeaderAdapter(imageLoader, this) } + private val headerAdapter by lazy(LazyThreadSafetyMode.NONE) { DAppHeaderAdapter(imageLoader, this, this, this) } private val dappsShimmering by lazy(LazyThreadSafetyMode.NONE) { CustomPlaceholderAdapter(R.layout.layout_dapps_shimmering) } - private val categoriesAdapter by lazy(LazyThreadSafetyMode.NONE) { - NestedAdapter( - DappCategoriesAdapter(this), - RecyclerView.HORIZONTAL, - paddingInDp = Rect(16, 0, 16, 0) - ) - } - - private val dappListAdapter by lazy(LazyThreadSafetyMode.NONE) { DappListAdapter(this) } + private val dappCategoriesListAdapter by lazy(LazyThreadSafetyMode.NONE) { DappCategoryListAdapter(this) } override fun onCreateView( inflater: LayoutInflater, @@ -56,9 +45,9 @@ class MainDAppFragment : } override fun initViews() { - dappRecyclerView.applyStatusBarInsets() - dappRecyclerView.adapter = ConcatAdapter(headerAdapter, categoriesAdapter, dappsShimmering, dappListAdapter) - dappRecyclerView.addItemDecoration(DAppItemDecoration(requireContext())) + dappRecyclerViewCatalog.applyStatusBarInsets() + dappRecyclerViewCatalog.adapter = ConcatAdapter(headerAdapter, dappsShimmering, dappCategoriesListAdapter) + dappRecyclerViewCatalog.itemAnimator = null } override fun inject() { @@ -70,7 +59,6 @@ class MainDAppFragment : override fun subscribe(viewModel: MainDAppViewModel) { observeBrowserEvents(viewModel) - setupRemoveFavouritesConfirmation(viewModel.removeFavouriteConfirmationAwaitable) viewModel.selectedWalletFlow.observe(headerAdapter::setWallet) @@ -78,36 +66,38 @@ class MainDAppFragment : when (state) { is LoadingState.Loaded -> { dappsShimmering.show(false) - dappListAdapter.submitList(state.data) + dappCategoriesListAdapter.submitList(state.data) } + is LoadingState.Loading -> { dappsShimmering.show(true) - dappListAdapter.submitList(listOf()) + dappCategoriesListAdapter.submitList(listOf()) } + else -> {} } } viewModel.categoriesStateFlow.observe { state -> - categoriesAdapter.show(state is LoadingState.Loaded) + headerAdapter.showCategoriesShimmering(state is LoadingState.Loading) if (state is LoadingState.Loaded) { - categoriesAdapter.submitList(state.data.categories) + headerAdapter.setCategories(state.data.categories) } } + + viewModel.favoriteDAppsUIFlow.observe { + headerAdapter.setFavorites(it) + } } override fun onCategoryClicked(id: String) { - viewModel.categorySelected(id) + viewModel.openCategory(id) } override fun onDAppClicked(item: DappModel) { viewModel.dappClicked(item) } - override fun onItemFavouriteClicked(item: DappModel) { - viewModel.dappFavouriteClicked(item) - } - override fun onWalletClick() { viewModel.accountIconClicked() } @@ -119,4 +109,8 @@ class MainDAppFragment : override fun onManageClick() { viewModel.manageClicked() } + + override fun onManageFavoritesClick() { + viewModel.openFavorites() + } } diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/main/MainDAppViewModel.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/main/MainDAppViewModel.kt index b77eeba4f1..f68f07b373 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/main/MainDAppViewModel.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/main/MainDAppViewModel.kt @@ -1,39 +1,28 @@ package io.novafoundation.nova.feature_dapp_impl.presentation.main import androidx.lifecycle.MutableLiveData -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.confirmingAction import io.novafoundation.nova.common.mixin.api.Browserable import io.novafoundation.nova.common.utils.Event import io.novafoundation.nova.common.utils.inBackground import io.novafoundation.nova.common.utils.indexOfFirstOrNull import io.novafoundation.nova.common.utils.withLoading import io.novafoundation.nova.feature_account_api.domain.interfaces.SelectedAccountUseCase -import io.novafoundation.nova.feature_dapp_api.data.model.DappCategory -import io.novafoundation.nova.feature_dapp_impl.DAppRouter -import io.novafoundation.nova.feature_dapp_impl.data.mappers.mapDappModelToDApp -import io.novafoundation.nova.feature_dapp_impl.data.mappers.mapDappToDappModel +import io.novafoundation.nova.feature_dapp_impl.presentation.DAppRouter +import io.novafoundation.nova.feature_dapp_api.presentation.browser.main.DAppBrowserPayload import io.novafoundation.nova.feature_dapp_impl.domain.DappInteractor import io.novafoundation.nova.feature_dapp_impl.presentation.common.DappModel -import io.novafoundation.nova.feature_dapp_impl.presentation.common.favourites.RemoveFavouritesPayload -import io.novafoundation.nova.feature_dapp_impl.presentation.main.model.DAppCategoryModel +import io.novafoundation.nova.feature_dapp_impl.presentation.common.dappCategoryToUi +import io.novafoundation.nova.feature_dapp_impl.presentation.common.mapDappCategoryToDappCategoryModel +import io.novafoundation.nova.feature_dapp_impl.presentation.common.mapFavoriteDappToDappModel import io.novafoundation.nova.feature_dapp_impl.presentation.main.model.DAppCategoryState -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filterNotNull -import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch -private const val INITIAL_SELECTED_CATEGORY_ID = "all" - class MainDAppViewModel( private val router: DAppRouter, - private val addressIconGenerator: AddressIconGenerator, private val selectedAccountUseCase: SelectedAccountUseCase, private val actionAwaitableMixinFactory: ActionAwaitableMixin.Factory, private val dappInteractor: DappInteractor, @@ -41,44 +30,34 @@ class MainDAppViewModel( override val openBrowserEvent = MutableLiveData>() - val removeFavouriteConfirmationAwaitable = actionAwaitableMixinFactory.confirmingAction() - val selectedWalletFlow = selectedAccountUseCase.selectedWalletModelFlow() .shareInBackground() + private val favoriteDAppsFlow = dappInteractor.observeFavoriteDApps() + .shareInBackground() + private val groupedDAppsFlow = dappInteractor.observeDAppsByCategory() .inBackground() .share() private val groupedDAppsUiFlow = groupedDAppsFlow .map { - it - .mapValues { (_, dapps) -> dapps.map(::mapDappToDappModel) } - .mapKeys { (category, _) -> category.id } + it.map { (category, dapps) -> mapDappCategoryToDappCategoryModel(category, dapps) } } .inBackground() .share() - private val selectedCategoryId = MutableStateFlow(INITIAL_SELECTED_CATEGORY_ID) - - private val shownDappsFlow = combine(groupedDAppsUiFlow, selectedCategoryId) { grouping, categoryId -> - grouping[categoryId] - } - .share() + val favoriteDAppsUIFlow = favoriteDAppsFlow + .map { dapps -> dapps.map { mapFavoriteDappToDappModel(it) } } + .shareInBackground() - val shownDAppsStateFlow = shownDappsFlow + val shownDAppsStateFlow = groupedDAppsUiFlow .filterNotNull() .withLoading() .share() - private val categoriesFlow = combine(groupedDAppsFlow, selectedCategoryId) { grouping, categoryId -> - grouping.keys.map { dappCategoryToUi(it, isSelected = it.id == categoryId) } - } - .distinctUntilChanged() - .inBackground() - .share() - - val categoriesStateFlow = categoriesFlow + val categoriesStateFlow = groupedDAppsFlow + .map { catalog -> catalog.keys.map { dappCategoryToUi(it, isSelected = false) } } .map { categories -> DAppCategoryState( categories = categories, @@ -91,12 +70,10 @@ class MainDAppViewModel( init { syncDApps() - - watchInvalidSelectedCategory() } - fun categorySelected(categoryId: String) = launch { - selectedCategoryId.emit(categoryId) + fun openCategory(categoryId: String) { + router.openDappSearchWithCategory(categoryId) } fun accountIconClicked() { @@ -104,41 +81,22 @@ class MainDAppViewModel( } fun dappClicked(dapp: DappModel) { - router.openDAppBrowser(dapp.url) + router.openDAppBrowser(DAppBrowserPayload.Address(dapp.url)) } fun searchClicked() { router.openDappSearch() } - fun dappFavouriteClicked(item: DappModel) = launch { - val dApp = mapDappModelToDApp(item) - - if (item.isFavourite) { - removeFavouriteConfirmationAwaitable.awaitAction(item.name) - } - - dappInteractor.toggleDAppFavouritesState(dApp) - } - fun manageClicked() { router.openAuthorizedDApps() } - private fun watchInvalidSelectedCategory() = shownDappsFlow.onEach { - // cannot find selected category in current grouping - if (it == null) selectedCategoryId.value = INITIAL_SELECTED_CATEGORY_ID - }.launchIn(this) - private fun syncDApps() = launch { dappInteractor.dAppsSync() } - private fun dappCategoryToUi(dappCategory: DappCategory, isSelected: Boolean): DAppCategoryModel { - return DAppCategoryModel( - id = dappCategory.id, - name = dappCategory.name, - selected = isSelected - ) + fun openFavorites() { + router.openDAppFavorites() } } diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/main/di/MainDAppModule.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/main/di/MainDAppModule.kt index b6f4497b9d..ac5a9d393d 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/main/di/MainDAppModule.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/main/di/MainDAppModule.kt @@ -6,12 +6,11 @@ import androidx.lifecycle.ViewModelProvider import dagger.Module import dagger.Provides import dagger.multibindings.IntoMap -import io.novafoundation.nova.common.address.AddressIconGenerator 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.feature_account_api.domain.interfaces.SelectedAccountUseCase -import io.novafoundation.nova.feature_dapp_impl.DAppRouter +import io.novafoundation.nova.feature_dapp_impl.presentation.DAppRouter import io.novafoundation.nova.feature_dapp_impl.domain.DappInteractor import io.novafoundation.nova.feature_dapp_impl.presentation.main.MainDAppViewModel @@ -27,7 +26,6 @@ class MainDAppModule { @IntoMap @ViewModelKey(MainDAppViewModel::class) fun provideViewModel( - addressIconGenerator: AddressIconGenerator, selectedAccountUseCase: SelectedAccountUseCase, actionAwaitableMixinFactory: ActionAwaitableMixin.Factory, router: DAppRouter, @@ -35,7 +33,6 @@ class MainDAppModule { ): ViewModel { return MainDAppViewModel( router = router, - addressIconGenerator = addressIconGenerator, selectedAccountUseCase = selectedAccountUseCase, actionAwaitableMixinFactory = actionAwaitableMixinFactory, dappInteractor = dappInteractor diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/main/model/DAppCategoryModel.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/main/model/DAppCategoryModel.kt index 64843be260..9ce73b71ae 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/main/model/DAppCategoryModel.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/main/model/DAppCategoryModel.kt @@ -2,6 +2,7 @@ package io.novafoundation.nova.feature_dapp_impl.presentation.main.model data class DAppCategoryModel( val id: String, + val iconUrl: String?, val name: String, val selected: Boolean ) diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/main/view/TapToSearchView.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/main/view/TapToSearchView.kt index 1904da34eb..2c4558cb1a 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/main/view/TapToSearchView.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/main/view/TapToSearchView.kt @@ -20,13 +20,15 @@ class TapToSearchView @JvmOverloads constructor( get() = context init { - setPaddingRelative(12.dp, 16.dp, 12.dp, 16.dp) + setPaddingRelative(12.dp, 0.dp, 12.dp, 0.dp) + + gravity = android.view.Gravity.CENTER_VERTICAL setDrawableStart( drawableRes = R.drawable.ic_search, - widthInDp = 20, - heightInDp = 20, - paddingInDp = 8, + widthInDp = 16, + heightInDp = 16, + paddingInDp = 6, tint = R.color.icon_secondary ) diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/search/DAppSearchCommunicator.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/search/DAppSearchCommunicator.kt index 30d5db3c11..9df519fa1a 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/search/DAppSearchCommunicator.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/search/DAppSearchCommunicator.kt @@ -12,6 +12,12 @@ interface DAppSearchResponder : InterScreenResponder interface DAppSearchCommunicator : DAppSearchRequester, DAppSearchResponder { - @Parcelize - class Response(val newUrl: String?) : Parcelable + sealed interface Response : Parcelable { + + @Parcelize + class NewUrl(val url: String) : Response + + @Parcelize + object Cancel : Response + } } diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/search/DAppSearchViewModel.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/search/DAppSearchViewModel.kt index ebcea18d1b..9825ca49f8 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/search/DAppSearchViewModel.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/search/DAppSearchViewModel.kt @@ -12,15 +12,20 @@ import io.novafoundation.nova.common.resources.ResourceManager import io.novafoundation.nova.common.utils.Event import io.novafoundation.nova.common.utils.inBackground import io.novafoundation.nova.common.utils.sendEvent -import io.novafoundation.nova.feature_dapp_impl.DAppRouter +import io.novafoundation.nova.common.utils.withSafeLoading +import io.novafoundation.nova.feature_dapp_impl.presentation.DAppRouter +import io.novafoundation.nova.feature_dapp_api.presentation.browser.main.DAppBrowserPayload import io.novafoundation.nova.feature_dapp_impl.R +import io.novafoundation.nova.feature_dapp_impl.domain.DappInteractor import io.novafoundation.nova.feature_dapp_impl.domain.search.DappSearchGroup import io.novafoundation.nova.feature_dapp_impl.domain.search.DappSearchResult import io.novafoundation.nova.feature_dapp_impl.domain.search.SearchDappInteractor +import io.novafoundation.nova.feature_dapp_impl.presentation.common.dappCategoryToUi import io.novafoundation.nova.feature_dapp_impl.presentation.search.model.DappSearchModel import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.mapLatest +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.launch @OptIn(ExperimentalCoroutinesApi::class) @@ -28,6 +33,7 @@ class DAppSearchViewModel( private val router: DAppRouter, private val resourceManager: ResourceManager, private val interactor: SearchDappInteractor, + private val dappInteractor: DappInteractor, private val payload: SearchPayload, private val dAppSearchResponder: DAppSearchResponder, private val actionAwaitableMixinFactory: ActionAwaitableMixin.Factory, @@ -41,13 +47,25 @@ class DAppSearchViewModel( private val _selectQueryTextEvent = MutableLiveData>() val selectQueryTextEvent: LiveData> = _selectQueryTextEvent - val searchResults = query - .mapLatest { - interactor.searchDapps(it) - .mapKeys { (searchGroup, _) -> mapSearchGroupToTextHeader(searchGroup) } - .mapValues { (_, groupItems) -> groupItems.map(::mapSearchResultToSearchModel) } - .toListWithHeaders() - } + private val selectedCategoryId = MutableStateFlow(payload.preselectedCategoryId) + + val categoriesFlow = combine( + interactor.categories(), + selectedCategoryId + ) { categories, categoryId -> + categories.map { dappCategoryToUi(it, isSelected = it.id == categoryId) } + } + .distinctUntilChanged() + .withSafeLoading() + .inBackground() + .share() + + val searchResults = combine(query, selectedCategoryId) { query, categoryId -> + interactor.searchDapps(query, categoryId) + .mapKeys { (searchGroup, _) -> mapSearchGroupToTextHeader(searchGroup) } + .mapValues { (_, groupItems) -> groupItems.map(::mapSearchResultToSearchModel) } + .toListWithHeaders() + } .inBackground() .share() @@ -55,11 +73,15 @@ class DAppSearchViewModel( if (!payload.initialUrl.isNullOrEmpty()) { _selectQueryTextEvent.sendEvent() } + + launch { + dappInteractor.dAppsSync() + } } fun cancelClicked() { if (shouldReportResult()) { - dAppSearchResponder.respond(DAppSearchCommunicator.Response(newUrl = null)) + dAppSearchResponder.respond(DAppSearchCommunicator.Response.Cancel) } router.back() @@ -81,7 +103,7 @@ class DAppSearchViewModel( description = searchResult.dapp.description, icon = searchResult.dapp.iconLink, searchResult = searchResult, - actionIcon = R.drawable.ic_favorite_heart_filled.takeIf { searchResult.dapp.isFavourite } + actionIcon = R.drawable.ic_favorite_heart_filled_20.takeIf { searchResult.dapp.isFavourite } ) is DappSearchResult.Search -> DappSearchModel( @@ -106,18 +128,32 @@ class DAppSearchViewModel( is DappSearchResult.Url -> searchResult.url } - if (searchResult !is DappSearchResult.Dapp) { + if (!searchResult.isTrustedByNova) { dAppNotInCatalogWarning.awaitAction(DappUnknownWarningModel(appLinksProvider.email)) } - if (shouldReportResult()) { - dAppSearchResponder.respond(DAppSearchCommunicator.Response(newUrl)) - router.back() - } else { - router.openDAppBrowser(newUrl) + when (payload.request) { + SearchPayload.Request.GO_TO_URL -> { + dAppSearchResponder.respond(DAppSearchCommunicator.Response.NewUrl(newUrl)) + router.finishDappSearch() + } + + SearchPayload.Request.OPEN_NEW_URL -> router.openDAppBrowser(DAppBrowserPayload.Address(newUrl)) } } } - private fun shouldReportResult() = payload.initialUrl != null + private fun shouldReportResult() = when (payload.request) { + SearchPayload.Request.GO_TO_URL -> true + + SearchPayload.Request.OPEN_NEW_URL -> false + } + + fun onCategoryClicked(id: String) { + if (selectedCategoryId.value == id) { + selectedCategoryId.value = null + } else { + selectedCategoryId.value = id + } + } } diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/search/DappSearchFragment.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/search/DappSearchFragment.kt index ae097430dc..16e0d2ce70 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/search/DappSearchFragment.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/search/DappSearchFragment.kt @@ -5,10 +5,15 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.core.os.bundleOf +import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope +import coil.ImageLoader import dev.chrisbanes.insetter.applyInsetter import io.novafoundation.nova.common.base.BaseBottomSheetFragment import io.novafoundation.nova.common.di.FeatureUtils +import io.novafoundation.nova.common.domain.isLoaded +import io.novafoundation.nova.common.domain.isLoading +import io.novafoundation.nova.common.domain.onLoaded import io.novafoundation.nova.common.utils.applyStatusBarInsets import io.novafoundation.nova.common.utils.bindTo import io.novafoundation.nova.common.utils.keyboard.hideSoftKeyboard @@ -18,11 +23,15 @@ import io.novafoundation.nova.feature_dapp_api.di.DAppFeatureApi import io.novafoundation.nova.feature_dapp_impl.R import io.novafoundation.nova.feature_dapp_impl.di.DAppFeatureComponent import io.novafoundation.nova.feature_dapp_impl.domain.search.DappSearchResult +import io.novafoundation.nova.feature_dapp_impl.presentation.main.DappCategoriesAdapter +import javax.inject.Inject +import kotlinx.android.synthetic.main.fragment_search_dapp.searchDappCategories +import kotlinx.android.synthetic.main.fragment_search_dapp.searchDappCategoriesShimmering import kotlinx.android.synthetic.main.fragment_search_dapp.searchDappList import kotlinx.android.synthetic.main.fragment_search_dapp.searchDappSearch import kotlinx.android.synthetic.main.fragment_search_dapp.searchDappSearhContainer -class DappSearchFragment : BaseBottomSheetFragment(), SearchDappAdapter.Handler { +class DappSearchFragment : BaseBottomSheetFragment(), SearchDappAdapter.Handler, DappCategoriesAdapter.Handler { companion object { @@ -33,6 +42,11 @@ class DappSearchFragment : BaseBottomSheetFragment(), Searc ) } + @Inject + protected lateinit var imageLoader: ImageLoader + + private val categoriesAdapter by lazy(LazyThreadSafetyMode.NONE) { DappCategoriesAdapter(imageLoader, this) } + private val adapter by lazy(LazyThreadSafetyMode.NONE) { SearchDappAdapter(this) } override fun onCreateView( @@ -50,6 +64,8 @@ class DappSearchFragment : BaseBottomSheetFragment(), Searc padding() } } + + searchDappCategories.adapter = categoriesAdapter searchDappList.adapter = adapter searchDappList.setHasFixedSize(true) @@ -75,10 +91,16 @@ class DappSearchFragment : BaseBottomSheetFragment(), Searc searchDappSearch.searchInput.content.bindTo(viewModel.query, lifecycleScope) viewModel.searchResults.observe(::submitListPreservingViewPoint) - viewModel.dAppNotInCatalogWarning + viewModel.selectQueryTextEvent.observeEvent { searchDappSearch.searchInput.content.selectAll() } + + viewModel.categoriesFlow.observe { + searchDappCategoriesShimmering.isVisible = it.isLoading() + searchDappCategories.isVisible = it.isLoaded() + it.onLoaded { categoriesAdapter.submitList(it) } + } } override fun itemClicked(searchResult: DappSearchResult) { @@ -115,4 +137,8 @@ class DappSearchFragment : BaseBottomSheetFragment(), Searc } } } + + override fun onCategoryClicked(id: String) { + viewModel.onCategoryClicked(id) + } } diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/search/SearchPayload.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/search/SearchPayload.kt index 0b65ecc2fe..c7eda77c8a 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/search/SearchPayload.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/search/SearchPayload.kt @@ -4,4 +4,14 @@ import android.os.Parcelable import kotlinx.android.parcel.Parcelize @Parcelize -class SearchPayload(val initialUrl: String?) : Parcelable +class SearchPayload( + val initialUrl: String?, + val request: Request, + val preselectedCategoryId: String? = null +) : Parcelable { + + enum class Request { + GO_TO_URL, + OPEN_NEW_URL, + } +} diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/search/di/DAppSearchModule.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/search/di/DAppSearchModule.kt index 2cf3022203..3e9ebe8188 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/search/di/DAppSearchModule.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/search/di/DAppSearchModule.kt @@ -13,8 +13,9 @@ import io.novafoundation.nova.common.di.viewmodel.ViewModelModule import io.novafoundation.nova.common.mixin.actionAwaitable.ActionAwaitableMixin import io.novafoundation.nova.common.resources.ResourceManager import io.novafoundation.nova.feature_dapp_api.data.repository.DAppMetadataRepository -import io.novafoundation.nova.feature_dapp_impl.DAppRouter +import io.novafoundation.nova.feature_dapp_impl.presentation.DAppRouter import io.novafoundation.nova.feature_dapp_impl.data.repository.FavouritesDAppRepository +import io.novafoundation.nova.feature_dapp_impl.domain.DappInteractor import io.novafoundation.nova.feature_dapp_impl.domain.search.SearchDappInteractor import io.novafoundation.nova.feature_dapp_impl.presentation.search.DAppSearchCommunicator import io.novafoundation.nova.feature_dapp_impl.presentation.search.DAppSearchViewModel @@ -42,6 +43,7 @@ class DAppSearchModule { router: DAppRouter, resourceManager: ResourceManager, interactor: SearchDappInteractor, + dappInteractor: DappInteractor, searchResponder: DAppSearchCommunicator, payload: SearchPayload, actionAwaitableMixinFactory: ActionAwaitableMixin.Factory, @@ -54,7 +56,8 @@ class DAppSearchModule { dAppSearchResponder = searchResponder, payload = payload, actionAwaitableMixinFactory = actionAwaitableMixinFactory, - appLinksProvider = appLinksProvider + appLinksProvider = appLinksProvider, + dappInteractor = dappInteractor ) } } diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/tab/BrowserTabRvItem.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/tab/BrowserTabRvItem.kt new file mode 100644 index 0000000000..576b7674f8 --- /dev/null +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/tab/BrowserTabRvItem.kt @@ -0,0 +1,8 @@ +package io.novafoundation.nova.feature_dapp_impl.presentation.tab + +data class BrowserTabRvItem( + val tabId: String, + val tabName: String?, + val tabFaviconPath: String?, + val tabScreenshotPath: String?, +) diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/tab/BrowserTabsAdapter.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/tab/BrowserTabsAdapter.kt new file mode 100644 index 0000000000..37da187aac --- /dev/null +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/tab/BrowserTabsAdapter.kt @@ -0,0 +1,93 @@ +package io.novafoundation.nova.feature_dapp_impl.presentation.tab + +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import coil.ImageLoader +import coil.clear +import coil.load +import io.novafoundation.nova.common.list.BaseViewHolder +import io.novafoundation.nova.common.utils.ImageMonitor +import io.novafoundation.nova.common.utils.inflateChild +import io.novafoundation.nova.common.utils.loadOrHide +import io.novafoundation.nova.common.utils.setPathOrStopWatching +import io.novafoundation.nova.feature_dapp_impl.R +import java.io.File +import kotlinx.android.synthetic.main.item_browser_tab.view.browserTabCard +import kotlinx.android.synthetic.main.item_browser_tab.view.browserTabClose +import kotlinx.android.synthetic.main.item_browser_tab.view.browserTabFavicon +import kotlinx.android.synthetic.main.item_browser_tab.view.browserTabScreenshot +import kotlinx.android.synthetic.main.item_browser_tab.view.browserTabSiteName + +class BrowserTabsAdapter( + private val imageLoader: ImageLoader, + private val handler: Handler +) : ListAdapter(DiffCallback) { + + interface Handler { + + fun tabClicked(item: BrowserTabRvItem, view: View) + + fun tabCloseClicked(item: BrowserTabRvItem) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BrowserTabViewHolder { + return BrowserTabViewHolder(parent.inflateChild(R.layout.item_browser_tab), imageLoader, handler) + } + + override fun onBindViewHolder(holder: BrowserTabViewHolder, position: Int) { + holder.bind(getItem(position)) + } +} + +private object DiffCallback : DiffUtil.ItemCallback() { + + override fun areItemsTheSame(oldItem: BrowserTabRvItem, newItem: BrowserTabRvItem): Boolean { + return oldItem.tabId == newItem.tabId + } + + override fun areContentsTheSame(oldItem: BrowserTabRvItem, newItem: BrowserTabRvItem): Boolean { + return oldItem == newItem + } +} + +class BrowserTabViewHolder( + private val view: View, + private val imageLoader: ImageLoader, + private val itemHandler: BrowserTabsAdapter.Handler, +) : BaseViewHolder(view) { + + private val screenshotImageMonitor = ImageMonitor( + imageView = itemView.browserTabScreenshot, + imageLoader = imageLoader + ) + + private val tabIconImageMonitor = ImageMonitor( + imageView = itemView.browserTabFavicon, + imageLoader = imageLoader + ) + + fun bind(item: BrowserTabRvItem) = with(itemView) { + browserTabCard.setOnClickListener { itemHandler.tabClicked(item, browserTabScreenshot) } + browserTabClose.setOnClickListener { itemHandler.tabCloseClicked(item) } + browserTabScreenshot.load(item.tabScreenshotPath?.asFile(), imageLoader) + browserTabFavicon.loadOrHide(item.tabFaviconPath?.asFile(), imageLoader) + browserTabSiteName.text = item.tabName + + screenshotImageMonitor.setPathOrStopWatching(item.tabScreenshotPath) + tabIconImageMonitor.setPathOrStopWatching(item.tabFaviconPath) + } + + override fun unbind() { + screenshotImageMonitor.stopMonitoring() + tabIconImageMonitor.stopMonitoring() + + with(itemView) { + browserTabScreenshot.clear() + browserTabFavicon.clear() + } + } + + private fun String.asFile() = File(this) +} diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/tab/BrowserTabsFragment.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/tab/BrowserTabsFragment.kt new file mode 100644 index 0000000000..254a4ce8b2 --- /dev/null +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/tab/BrowserTabsFragment.kt @@ -0,0 +1,81 @@ +package io.novafoundation.nova.feature_dapp_impl.presentation.tab + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.navigation.fragment.FragmentNavigatorExtras +import androidx.recyclerview.widget.GridLayoutManager +import coil.ImageLoader +import io.novafoundation.nova.common.base.BaseFragment +import io.novafoundation.nova.common.di.FeatureUtils +import io.novafoundation.nova.common.utils.applyStatusBarInsets +import io.novafoundation.nova.feature_dapp_api.di.DAppFeatureApi +import io.novafoundation.nova.feature_dapp_impl.R +import io.novafoundation.nova.feature_dapp_impl.di.DAppFeatureComponent +import io.novafoundation.nova.feature_dapp_impl.presentation.browser.main.DAPP_SHARED_ELEMENT_ID_IMAGE_TAB +import javax.inject.Inject +import kotlinx.android.synthetic.main.fragment_browser_tabs.browserTabsAddTab +import kotlinx.android.synthetic.main.fragment_browser_tabs.browserTabsCloseTabs +import kotlinx.android.synthetic.main.fragment_browser_tabs.browserTabsDone +import kotlinx.android.synthetic.main.fragment_browser_tabs.browserTabsList + +class BrowserTabsFragment : BaseFragment(), BrowserTabsAdapter.Handler { + + @Inject + lateinit var imageLoader: ImageLoader + + private val adapter by lazy(LazyThreadSafetyMode.NONE) { + BrowserTabsAdapter(imageLoader, this) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return layoutInflater.inflate(R.layout.fragment_browser_tabs, container, false) + } + + override fun initViews() { + requireView().applyStatusBarInsets() + + onBackPressed { viewModel.done() } + + browserTabsList.layoutManager = GridLayoutManager(requireContext(), 2) + browserTabsList.adapter = adapter + + browserTabsCloseTabs.setOnClickListener { viewModel.closeAllTabs() } + browserTabsAddTab.setOnClickListener { viewModel.addTab() } + browserTabsDone.setOnClickListener { viewModel.done() } + } + + override fun inject() { + FeatureUtils.getFeature(this, DAppFeatureApi::class.java) + .browserTabsComponentFactory() + .create(this) + .inject(this) + } + + override fun subscribe(viewModel: BrowserTabsViewModel) { + setupCloseAllDappTabsDialogue(viewModel.closeAllTabsConfirmation) + + viewModel.tabsFlow.observe { + adapter.submitList(it) + browserTabsList.scrollToPosition(it.size - 1) + } + } + + override fun tabClicked(item: BrowserTabRvItem, view: View) { + view.transitionName = DAPP_SHARED_ELEMENT_ID_IMAGE_TAB + + val extras = FragmentNavigatorExtras( + view to DAPP_SHARED_ELEMENT_ID_IMAGE_TAB + ) + viewModel.openTab(item, extras) + } + + override fun tabCloseClicked(item: BrowserTabRvItem) { + viewModel.closeTab(item.tabId) + } +} diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/tab/BrowserTabsViewModel.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/tab/BrowserTabsViewModel.kt new file mode 100644 index 0000000000..2a497fd30d --- /dev/null +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/tab/BrowserTabsViewModel.kt @@ -0,0 +1,60 @@ +package io.novafoundation.nova.feature_dapp_impl.presentation.tab + +import androidx.navigation.fragment.FragmentNavigator +import io.novafoundation.nova.common.base.BaseViewModel +import io.novafoundation.nova.common.mixin.actionAwaitable.ActionAwaitableMixin +import io.novafoundation.nova.common.mixin.actionAwaitable.awaitAction +import io.novafoundation.nova.common.mixin.actionAwaitable.confirmingAction +import io.novafoundation.nova.common.utils.Urls +import io.novafoundation.nova.common.utils.mapList +import io.novafoundation.nova.feature_account_api.domain.interfaces.SelectedAccountUseCase +import io.novafoundation.nova.feature_dapp_impl.presentation.DAppRouter +import io.novafoundation.nova.feature_dapp_api.presentation.browser.main.DAppBrowserPayload +import io.novafoundation.nova.feature_dapp_impl.utils.tabs.BrowserTabService +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch + +class BrowserTabsViewModel( + private val router: DAppRouter, + private val browserTabService: BrowserTabService, + private val actionAwaitableMixinFactory: ActionAwaitableMixin.Factory, + private val accountUseCase: SelectedAccountUseCase +) : BaseViewModel() { + + val closeAllTabsConfirmation = actionAwaitableMixinFactory.confirmingAction() + + val tabsFlow = browserTabService.tabStateFlow + .map { it.tabs } + .mapList { + BrowserTabRvItem( + tabId = it.id, + tabName = it.pageSnapshot.pageName ?: Urls.domainOf(it.currentUrl), + tabFaviconPath = it.pageSnapshot.pageIconPath, + tabScreenshotPath = it.pageSnapshot.pagePicturePath + ) + }.shareInBackground() + + fun openTab(tab: BrowserTabRvItem, extras: FragmentNavigator.Extras) = launch { + router.openDAppBrowser(DAppBrowserPayload.Tab(tab.tabId), extras) + } + + fun closeTab(tabId: String) = launch { + browserTabService.removeTab(tabId) + } + + fun closeAllTabs() = launch { + closeAllTabsConfirmation.awaitAction() + + val metaAccount = accountUseCase.getSelectedMetaAccount() + browserTabService.removeTabsForMetaAccount(metaAccount.id) + router.closeTabsScreen() + } + + fun addTab() { + router.openDappSearch() + } + + fun done() { + router.closeTabsScreen() + } +} diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/tab/Dialogs.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/tab/Dialogs.kt new file mode 100644 index 0000000000..06626093f3 --- /dev/null +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/tab/Dialogs.kt @@ -0,0 +1,23 @@ +package io.novafoundation.nova.feature_dapp_impl.presentation.tab + +import io.novafoundation.nova.common.base.BaseFragment +import io.novafoundation.nova.common.mixin.actionAwaitable.ConfirmationAwaitable +import io.novafoundation.nova.common.view.dialog.warningDialog +import io.novafoundation.nova.feature_dapp_impl.R + +fun BaseFragment<*>.setupCloseAllDappTabsDialogue(mixin: ConfirmationAwaitable) { + mixin.awaitableActionLiveData.observeEvent { event -> + warningDialog( + context = providedContext, + onPositiveClick = { event.onSuccess(Unit) }, + positiveTextRes = R.string.browser_tabs_close_all, + negativeTextRes = R.string.common_cancel, + onNegativeClick = { event.onCancel() }, + styleRes = R.style.AccentNegativeAlertDialogTheme_Reversed + ) { + setTitle(R.string.close_dapp_tabs_title) + + setMessage(R.string.close_dapp_tabs_message) + } + } +} diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/tab/di/BrowserTabsComponent.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/tab/di/BrowserTabsComponent.kt new file mode 100644 index 0000000000..890998d525 --- /dev/null +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/tab/di/BrowserTabsComponent.kt @@ -0,0 +1,26 @@ +package io.novafoundation.nova.feature_dapp_impl.presentation.tab.di + +import androidx.fragment.app.Fragment +import dagger.BindsInstance +import dagger.Subcomponent +import io.novafoundation.nova.common.di.scope.ScreenScope +import io.novafoundation.nova.feature_dapp_impl.presentation.tab.BrowserTabsFragment + +@Subcomponent( + modules = [ + BrowserTabsModule::class + ] +) +@ScreenScope +interface BrowserTabsComponent { + + @Subcomponent.Factory + interface Factory { + + fun create( + @BindsInstance fragment: Fragment + ): BrowserTabsComponent + } + + fun inject(fragment: BrowserTabsFragment) +} diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/tab/di/BrowserTabsModule.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/tab/di/BrowserTabsModule.kt new file mode 100644 index 0000000000..ef1bfa4ace --- /dev/null +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/tab/di/BrowserTabsModule.kt @@ -0,0 +1,41 @@ +package io.novafoundation.nova.feature_dapp_impl.presentation.tab.di + +import androidx.fragment.app.Fragment +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import dagger.Module +import dagger.Provides +import dagger.multibindings.IntoMap +import io.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.feature_account_api.domain.interfaces.SelectedAccountUseCase +import io.novafoundation.nova.feature_dapp_impl.presentation.DAppRouter +import io.novafoundation.nova.feature_dapp_impl.presentation.tab.BrowserTabsViewModel +import io.novafoundation.nova.feature_dapp_impl.utils.tabs.BrowserTabService + +@Module(includes = [ViewModelModule::class]) +class BrowserTabsModule { + + @Provides + internal fun provideViewModel(fragment: Fragment, factory: ViewModelProvider.Factory): BrowserTabsViewModel { + return ViewModelProvider(fragment, factory).get(BrowserTabsViewModel::class.java) + } + + @Provides + @IntoMap + @ViewModelKey(BrowserTabsViewModel::class) + fun provideViewModel( + router: DAppRouter, + browserTabService: BrowserTabService, + actionAwaitableMixinFactory: ActionAwaitableMixin.Factory, + accountUseCase: SelectedAccountUseCase + ): ViewModel { + return BrowserTabsViewModel( + router = router, + browserTabService = browserTabService, + actionAwaitableMixinFactory = actionAwaitableMixinFactory, + accountUseCase = accountUseCase + ) + } +} diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/utils/tabs/BrowserTabService.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/utils/tabs/BrowserTabService.kt new file mode 100644 index 0000000000..0bb691a1d2 --- /dev/null +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/utils/tabs/BrowserTabService.kt @@ -0,0 +1,27 @@ +package io.novafoundation.nova.feature_dapp_impl.utils.tabs + +import io.novafoundation.nova.feature_dapp_impl.utils.tabs.models.BrowserTab +import io.novafoundation.nova.feature_dapp_impl.utils.tabs.models.TabsState +import kotlinx.coroutines.flow.Flow + +interface BrowserTabService { + + val tabStateFlow: Flow + + fun selectTab(tabId: String?) + + fun detachCurrentSession() + + fun makeCurrentTabSnapshot() + + suspend fun createNewTab(url: String): BrowserTab + + suspend fun removeTab(tabId: String) + + suspend fun removeTabsForMetaAccount(metaId: Long) +} + +suspend fun BrowserTabService.createAndSelectTab(url: String) { + val tab = createNewTab(url) + selectTab(tab.id) +} diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/utils/tabs/RealBrowserTabService.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/utils/tabs/RealBrowserTabService.kt new file mode 100644 index 0000000000..bf1943e836 --- /dev/null +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/utils/tabs/RealBrowserTabService.kt @@ -0,0 +1,174 @@ +package io.novafoundation.nova.feature_dapp_impl.utils.tabs + +import io.novafoundation.nova.common.utils.CallbackLruCache +import io.novafoundation.nova.common.utils.Urls +import io.novafoundation.nova.common.utils.coroutines.RootScope +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.data.repository.tabs.BrowserTabInternalRepository +import io.novafoundation.nova.feature_dapp_impl.utils.tabs.models.BrowserTab +import io.novafoundation.nova.feature_dapp_impl.utils.tabs.models.CurrentTabState +import io.novafoundation.nova.feature_dapp_impl.utils.tabs.models.BrowserTabSession +import io.novafoundation.nova.feature_dapp_impl.utils.tabs.models.BrowserTabSessionFactory +import io.novafoundation.nova.feature_dapp_impl.utils.tabs.models.OnPageChangedCallback +import io.novafoundation.nova.feature_dapp_impl.utils.tabs.models.PageSnapshot +import io.novafoundation.nova.feature_dapp_impl.utils.tabs.models.TabsState +import io.novafoundation.nova.feature_dapp_impl.utils.tabs.models.fromName +import java.util.Date +import java.util.UUID +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +class RealBrowserTabService( + private val dAppMetadataRepository: DAppMetadataRepository, + private val browserTabInternalRepository: BrowserTabInternalRepository, + private val accountRepository: AccountRepository, + private val pageSnapshotBuilder: PageSnapshotBuilder, + private val tabMemoryRestrictionService: TabMemoryRestrictionService, + private val browserTabSessionFactory: BrowserTabSessionFactory, + private val rootScope: RootScope +) : BrowserTabService, CoroutineScope by rootScope, OnPageChangedCallback { + + private val availableSessionsCount = tabMemoryRestrictionService.getMaximumActiveSessions() + + private val selectedTabIdFlow = MutableStateFlow(null) + + private val allTabsFlow = accountRepository.selectedMetaAccountFlow() + .flatMapLatest { browserTabInternalRepository.observeTabs(it.id) } + .map { tabs -> tabs.associateBy { it.id } } + + private val activeSessions = CallbackLruCache(availableSessionsCount) + + override val tabStateFlow = combine( + selectedTabIdFlow, + accountRepository.selectedMetaAccountFlow(), + allTabsFlow + ) { selectedTabId, metaAccount, allTabs -> + TabsState( + tabs = allTabs.values.toList(), + selectedTab = currentTabState(selectedTabId, allTabs) + ) + } + + init { + activeSessions.setOnEntryRemovedCallback { + launch(Dispatchers.Main) { + it.detachFromHost() + it.destroy() + } + } + } + + override fun selectTab(tabId: String?) { + val oldTabId = selectedTabIdFlow.value + detachSession(oldTabId) + + selectedTabIdFlow.value = tabId + } + + override fun detachCurrentSession() { + selectTab(null) + } + + override suspend fun createNewTab(url: String): BrowserTab { + val tab = BrowserTab( + id = UUID.randomUUID().toString(), + metaId = accountRepository.getSelectedMetaAccount().id, + pageSnapshot = PageSnapshot.fromName(Urls.domainOf(url)), + currentUrl = url, + knownDAppMetadata = getKnownDappMetadata(url), + creationTime = Date() + ) + + browserTabInternalRepository.saveTab(tab) + + return tab + } + + override suspend fun removeTab(tabId: String) { + if (tabId == selectedTabIdFlow.value) { + selectTab(null) + } + + browserTabInternalRepository.removeTab(tabId) + activeSessions.remove(tabId) + } + + override suspend fun removeTabsForMetaAccount(metaId: Long) { + selectTab(null) + + val removedTabs = browserTabInternalRepository.removeTabsForMetaAccount(metaId) + removedTabs.forEach { + activeSessions.remove(it) + } + } + + override fun makeCurrentTabSnapshot() { + val currentTab = selectedTabIdFlow.value + + currentTab?.let { makeTabSnapshot(currentTab) } + } + + private suspend fun addNewSession(tab: BrowserTab): BrowserTabSession { + val session = browserTabSessionFactory.create(tabId = tab.id, startUrl = tab.currentUrl, onPageChangedCallback = this) + activeSessions.put(tab.id, session) + return session + } + + private fun detachSession(tabId: String?) { + if (tabId == null) return + + val sessionToDetach = activeSessions[tabId] + sessionToDetach?.detachFromHost() + } + + private suspend fun currentTabState(selectedTabId: String?, allTabs: Map): CurrentTabState { + val tabId = selectedTabId ?: return CurrentTabState.NotSelected + val tab = allTabs[tabId] ?: return CurrentTabState.NotSelected + return CurrentTabState.Selected( + tab, + activeSessions[tabId] ?: addNewSession(tab) + ) + } + + /* + We should update page title and url each time page is changed to have correct tab state in persistent storage + */ + override fun onPageChanged(tabId: String, url: String?, title: String?) { + if (url == null) return + + launch { + withContext(Dispatchers.Default) { + activeSessions[tabId].currentUrl = url + browserTabInternalRepository.changeCurrentUrl(tabId, url) + + val metadata = getKnownDappMetadata(url) + browserTabInternalRepository.changeKnownDAppMetadata(tabId, metadata?.iconLink) + } + } + } + + private fun makeTabSnapshot(tabId: String) { + val pageSession = activeSessions.get(tabId) + + if (pageSession != null) { + val snapshot = pageSnapshotBuilder.getPageSnapshot(pageSession) + + launch(Dispatchers.Default) { + browserTabInternalRepository.savePageSnapshot(pageSession.tabId, snapshot) + } + } + } + + private suspend fun getKnownDappMetadata(url: String): BrowserTab.KnownDAppMetadata? { + return dAppMetadataRepository.getDAppMetadata(Urls.normalizeUrl(url))?.let { + BrowserTab.KnownDAppMetadata(it.iconLink) + } + } +} diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/utils/tabs/RealPageSnapshotBuilder.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/utils/tabs/RealPageSnapshotBuilder.kt new file mode 100644 index 0000000000..bcedada7b3 --- /dev/null +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/utils/tabs/RealPageSnapshotBuilder.kt @@ -0,0 +1,66 @@ +package io.novafoundation.nova.feature_dapp_impl.utils.tabs + +import android.graphics.Bitmap +import androidx.core.view.drawToBitmap +import io.novafoundation.nova.common.interfaces.FileProvider +import io.novafoundation.nova.common.utils.Urls +import io.novafoundation.nova.common.utils.coroutines.RootScope +import io.novafoundation.nova.common.utils.nullIfBlank +import io.novafoundation.nova.feature_dapp_impl.utils.tabs.models.BrowserTabSession +import io.novafoundation.nova.feature_dapp_impl.utils.tabs.models.PageSnapshot +import io.novafoundation.nova.feature_dapp_impl.utils.tabs.models.fromName +import java.io.FileOutputStream +import java.io.IOException +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +interface PageSnapshotBuilder { + + fun getPageSnapshot(browserTabSession: BrowserTabSession): PageSnapshot +} + +class RealPageSnapshotBuilder( + private val fileProvider: FileProvider, + private val rootScope: RootScope +) : PageSnapshotBuilder { + + override fun getPageSnapshot(browserTabSession: BrowserTabSession): PageSnapshot { + val webView = browserTabSession.webView + if (!webView.isLaidOut) { + return PageSnapshot.fromName(Urls.domainOf(browserTabSession.currentUrl)) + } + + val pageName = webView.title.nullIfBlank() ?: Urls.domainOf(browserTabSession.currentUrl) + val icon = webView.favicon + val pageBitmap = webView.drawToBitmap() + + val pageIconPath = saveBitmap(browserTabSession, icon, "icon", 100) + val pagePicturePath = saveBitmap(browserTabSession, pageBitmap, "page", 40) + + return PageSnapshot( + pageName = pageName, + pageIconPath = pageIconPath, + pagePicturePath = pagePicturePath + ) + } + + private fun saveBitmap(browserTabSession: BrowserTabSession, bitmap: Bitmap?, filePrefix: String, quality: Int): String? { + if (bitmap == null) return null + + // Use this pattern to don't create a new image everytime when we rewrite the page snapshot + val fileName = "tab_${browserTabSession.tabId}_$filePrefix.jpeg" + val file = fileProvider.getFileInExternalCacheStorage(fileName) + + try { + rootScope.launch(Dispatchers.IO) { + val outputStream = FileOutputStream(file) + bitmap.compress(Bitmap.CompressFormat.PNG, quality, outputStream) + outputStream.close() + } + + return file.absolutePath + } catch (e: IOException) { + return null + } + } +} diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/utils/tabs/TabMemoryRestrictionService.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/utils/tabs/TabMemoryRestrictionService.kt new file mode 100644 index 0000000000..bbbf637c21 --- /dev/null +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/utils/tabs/TabMemoryRestrictionService.kt @@ -0,0 +1,28 @@ +package io.novafoundation.nova.feature_dapp_impl.utils.tabs + +import android.app.ActivityManager +import android.content.Context +import io.novafoundation.nova.common.utils.InformationSize.Companion.megabytes + +private const val MIN_TABS = 3 +private val MEMORY_STEP = 100.megabytes.inWholeBytes + +interface TabMemoryRestrictionService { + fun getMaximumActiveSessions(): Int +} + +class RealTabMemoryRestrictionService(val context: Context) : TabMemoryRestrictionService { + + // The linear function that starts from 3 and adds 1 tab each MEMORY_STEP of available memory + override fun getMaximumActiveSessions(): Int { + val availableMemory = getAvailableMemory() + return MIN_TABS + (availableMemory / MEMORY_STEP).toInt() + } + + private fun getAvailableMemory(): Long { + val activityManager = context.getSystemService(ActivityManager::class.java) + val memoryInfo = ActivityManager.MemoryInfo() + activityManager.getMemoryInfo(memoryInfo) + return memoryInfo.availMem + } +} diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/utils/tabs/models/BrowserTab.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/utils/tabs/models/BrowserTab.kt new file mode 100644 index 0000000000..36c0555432 --- /dev/null +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/utils/tabs/models/BrowserTab.kt @@ -0,0 +1,15 @@ +package io.novafoundation.nova.feature_dapp_impl.utils.tabs.models + +import java.util.Date + +class BrowserTab( + val id: String, + val metaId: Long, + val pageSnapshot: PageSnapshot, + val knownDAppMetadata: KnownDAppMetadata?, + val currentUrl: String, + val creationTime: Date +) { + + class KnownDAppMetadata(val iconLink: String) +} diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/utils/tabs/models/BrowserTabSession.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/utils/tabs/models/BrowserTabSession.kt new file mode 100644 index 0000000000..e2a6e39c30 --- /dev/null +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/utils/tabs/models/BrowserTabSession.kt @@ -0,0 +1,93 @@ +package io.novafoundation.nova.feature_dapp_impl.utils.tabs.models + +import android.content.Intent +import android.graphics.Bitmap +import android.webkit.WebView +import io.novafoundation.nova.common.resources.ContextManager +import io.novafoundation.nova.feature_dapp_impl.web3.webview.PageCallback +import io.novafoundation.nova.feature_dapp_impl.web3.webview.Web3ChromeClient +import io.novafoundation.nova.feature_dapp_impl.web3.webview.CompoundWeb3Injector +import io.novafoundation.nova.feature_dapp_impl.web3.webview.Web3WebViewClient +import io.novafoundation.nova.feature_dapp_impl.web3.webview.injectWeb3 +import io.novafoundation.nova.feature_dapp_impl.web3.webview.uninjectWeb3 +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +class BrowserTabSessionFactory( + private val compoundWeb3Injector: CompoundWeb3Injector, + private val contextManager: ContextManager +) { + + suspend fun create(tabId: String, startUrl: String, onPageChangedCallback: OnPageChangedCallback): BrowserTabSession { + return withContext(Dispatchers.Main) { + val context = contextManager.getActivity()!! + val webView = WebView(context) + + BrowserTabSession( + tabId = tabId, + startUrl = startUrl, + webView = webView, + compoundWeb3Injector = compoundWeb3Injector, + onPageChangedCallback = onPageChangedCallback + ) + } + } +} + +class BrowserTabSession( + val tabId: String, + val startUrl: String, + val webView: WebView, + compoundWeb3Injector: CompoundWeb3Injector, + private val onPageChangedCallback: OnPageChangedCallback +) : PageCallback { + + val webViewClient: Web3WebViewClient = Web3WebViewClient( + webView = webView, + pageCallback = this + ) + + var currentUrl: String = startUrl + + private var nestedPageCallback: PageCallback? = null + + init { + webView.injectWeb3(webViewClient) + webView.loadUrl(startUrl) + compoundWeb3Injector.initialInject(webView) + } + + fun attachToHost( + chromeClient: Web3ChromeClient, + pageCallback: PageCallback + ) { + webView.webChromeClient = chromeClient + this.nestedPageCallback = pageCallback + + // To provide initial state + pageCallback.onPageChanged(webView, webView.url, webView.title) + } + + fun detachFromHost() { + nestedPageCallback = null + webView.webChromeClient = null + } + + override fun onPageStarted(view: WebView, url: String, favicon: Bitmap?) { + nestedPageCallback?.onPageStarted(view, url, favicon) + } + + override fun handleBrowserIntent(intent: Intent) { + nestedPageCallback?.handleBrowserIntent(intent) + } + + override fun onPageChanged(view: WebView, url: String?, title: String?) { + nestedPageCallback?.onPageChanged(view, url, title) + onPageChangedCallback.onPageChanged(tabId, url, title) + } + + fun destroy() { + webView.uninjectWeb3() + webView.destroy() + } +} diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/utils/tabs/models/CurrentTabState.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/utils/tabs/models/CurrentTabState.kt new file mode 100644 index 0000000000..caf935b1ac --- /dev/null +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/utils/tabs/models/CurrentTabState.kt @@ -0,0 +1,13 @@ +package io.novafoundation.nova.feature_dapp_impl.utils.tabs.models + +sealed interface CurrentTabState { + + object NotSelected : CurrentTabState + + data class Selected(val tab: BrowserTab, val browserTabSession: BrowserTabSession) : CurrentTabState +} + +fun CurrentTabState.stateId() = when (this) { + CurrentTabState.NotSelected -> null + is CurrentTabState.Selected -> tab.id +} diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/utils/tabs/models/PageCallback.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/utils/tabs/models/PageCallback.kt new file mode 100644 index 0000000000..44860b2ea3 --- /dev/null +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/utils/tabs/models/PageCallback.kt @@ -0,0 +1,6 @@ +package io.novafoundation.nova.feature_dapp_impl.utils.tabs.models + +interface OnPageChangedCallback { + + fun onPageChanged(tabId: String, url: String?, title: String?) +} diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/utils/tabs/models/PageSnapshot.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/utils/tabs/models/PageSnapshot.kt new file mode 100644 index 0000000000..5bf765aa6d --- /dev/null +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/utils/tabs/models/PageSnapshot.kt @@ -0,0 +1,16 @@ +package io.novafoundation.nova.feature_dapp_impl.utils.tabs.models + +class PageSnapshot( + val pageName: String?, + val pageIconPath: String?, + val pagePicturePath: String? +) { + + companion object; +} + +fun PageSnapshot.Companion.fromName(name: String) = PageSnapshot( + pageName = name, + pageIconPath = null, + pagePicturePath = null +) diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/utils/tabs/models/TabsState.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/utils/tabs/models/TabsState.kt new file mode 100644 index 0000000000..ec7e8a7ae7 --- /dev/null +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/utils/tabs/models/TabsState.kt @@ -0,0 +1,11 @@ +package io.novafoundation.nova.feature_dapp_impl.utils.tabs.models + +class TabsState( + val tabs: List, + val selectedTab: CurrentTabState +) { + + fun stateId(): String { + return tabs.joinToString { it.id } + "_" + selectedTab.stateId() + } +} diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/metamask/transport/MetamaskInjector.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/metamask/transport/MetamaskInjector.kt index 975fde63c7..6f43492230 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/metamask/transport/MetamaskInjector.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/metamask/transport/MetamaskInjector.kt @@ -24,11 +24,11 @@ class MetamaskInjector( private val webViewScriptInjector: WebViewScriptInjector ) : Web3Injector { - override fun initialInject(into: WebView, extensionStore: ExtensionsStore) { + override fun initialInject(into: WebView) { webViewScriptInjector.injectJsInterface(into, jsInterface, JS_INTERFACE_NAME) } - override fun injectForPage(into: WebView, url: String, extensionStore: ExtensionsStore) { + override fun injectForPage(into: WebView, extensionStore: ExtensionsStore) { webViewScriptInjector.injectScript(R.raw.metamask_min, into, scriptId = "novawallet-metamask-bundle") injectProvider(extensionStore, into) } diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/polkadotJs/PolkadotJsInjector.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/polkadotJs/PolkadotJsInjector.kt index 8d37e14a61..36d3f124d9 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/polkadotJs/PolkadotJsInjector.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/polkadotJs/PolkadotJsInjector.kt @@ -15,11 +15,11 @@ class PolkadotJsInjector( private val webViewScriptInjector: WebViewScriptInjector ) : Web3Injector { - override fun initialInject(into: WebView, extensionStore: ExtensionsStore) { + override fun initialInject(into: WebView) { webViewScriptInjector.injectJsInterface(into, jsInterface, JS_INTERFACE_NAME) } - override fun injectForPage(into: WebView, url: String, extensionStore: ExtensionsStore) { + override fun injectForPage(into: WebView, extensionStore: ExtensionsStore) { webViewScriptInjector.injectScript(R.raw.polkadotjs_min, into, scriptId = "novawallet-polkadotjs-bundle") webViewScriptInjector.injectScript(R.raw.javascript_interface_bridge, into, scriptId = "novawallet-polkadotjs-provider") } diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/webview/Web3ChromeClient.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/webview/Web3ChromeClient.kt new file mode 100644 index 0000000000..448cde9fd6 --- /dev/null +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/webview/Web3ChromeClient.kt @@ -0,0 +1,35 @@ +package io.novafoundation.nova.feature_dapp_impl.web3.webview + +import android.net.Uri +import android.webkit.PermissionRequest +import android.webkit.ValueCallback +import android.webkit.WebChromeClient +import android.webkit.WebView +import android.widget.ProgressBar +import io.novafoundation.nova.common.utils.setVisible +import kotlinx.coroutines.CoroutineScope + +private const val MAX_PROGRESS = 100 + +class Web3ChromeClient( + private val permissionAsker: WebViewPermissionAsker, + private val fileChooser: WebViewFileChooser, + private val progressBar: ProgressBar, + private val coroutineScope: CoroutineScope +) : WebChromeClient() { + + override fun onPermissionRequest(request: PermissionRequest) { + permissionAsker.requestPermission(coroutineScope, request) + } + + override fun onProgressChanged(view: WebView, newProgress: Int) { + progressBar.progress = newProgress + + progressBar.setVisible(newProgress < MAX_PROGRESS) + } + + override fun onShowFileChooser(webView: WebView?, filePathCallback: ValueCallback>?, fileChooserParams: FileChooserParams?): Boolean { + fileChooser.onShowFileChooser(filePathCallback, fileChooserParams) + return true + } +} diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/webview/Web3Injector.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/webview/Web3Injector.kt new file mode 100644 index 0000000000..79663f3b49 --- /dev/null +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/webview/Web3Injector.kt @@ -0,0 +1,22 @@ +package io.novafoundation.nova.feature_dapp_impl.web3.webview + +import android.webkit.WebView +import io.novafoundation.nova.feature_dapp_impl.web3.states.ExtensionsStore + +interface Web3Injector { + + fun initialInject(into: WebView) + + fun injectForPage(into: WebView, extensionStore: ExtensionsStore) +} + +class CompoundWeb3Injector(val injectors: List) : Web3Injector { + + override fun initialInject(into: WebView) { + injectors.forEach { it.initialInject(into) } + } + + override fun injectForPage(into: WebView, extensionStore: ExtensionsStore) { + injectors.forEach { it.injectForPage(into, extensionStore) } + } +} diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/webview/Web3WebView.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/webview/Web3WebView.kt index 314da98cc6..0aad4c3d56 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/webview/Web3WebView.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/webview/Web3WebView.kt @@ -3,15 +3,10 @@ package io.novafoundation.nova.feature_dapp_impl.web3.webview import android.annotation.SuppressLint import android.webkit.WebSettings import android.webkit.WebView -import android.widget.ProgressBar import io.novafoundation.nova.common.BuildConfig @SuppressLint("SetJavaScriptEnabled") -fun WebView.injectWeb3( - progressBar: ProgressBar, - fileChooser: WebViewFileChooser, - web3Client: Web3WebViewClient -) { +fun WebView.injectWeb3(web3Client: Web3WebViewClient) { settings.javaScriptEnabled = true settings.cacheMode = WebSettings.LOAD_DEFAULT settings.builtInZoomControls = true @@ -21,23 +16,11 @@ fun WebView.injectWeb3( settings.domStorageEnabled = true settings.javaScriptCanOpenWindowsAutomatically = true - web3Client.initialInject() this.webViewClient = web3Client - webChromeClient = Web3ChromeClient(fileChooser, progressBar) WebView.setWebContentsDebuggingEnabled(BuildConfig.DEBUG) } -fun WebView.changeUserAgentByDesktopMode(desktopMode: Boolean) { - val defaultUserAgent = WebSettings.getDefaultUserAgent(context) - - settings.userAgentString = if (desktopMode) { - "Mozilla/5.0 (X11; CrOS x86_64 10066.0.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36" - } else { - defaultUserAgent - } -} - fun WebView.uninjectWeb3() { settings.javaScriptEnabled = false diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/webview/Web3WebViewClient.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/webview/Web3WebViewClient.kt index d833cfe059..8e7013b276 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/webview/Web3WebViewClient.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/webview/Web3WebViewClient.kt @@ -2,48 +2,22 @@ package io.novafoundation.nova.feature_dapp_impl.web3.webview import android.content.Intent import android.graphics.Bitmap -import android.net.Uri -import android.webkit.ValueCallback -import android.webkit.WebChromeClient import android.webkit.WebResourceRequest +import android.webkit.WebSettings import android.webkit.WebView import android.webkit.WebViewClient -import android.widget.ProgressBar -import io.novafoundation.nova.common.utils.setVisible -import io.novafoundation.nova.feature_dapp_impl.web3.states.ExtensionsStore -interface Web3Injector { - - fun initialInject(into: WebView, extensionStore: ExtensionsStore) - - fun injectForPage(into: WebView, url: String, extensionStore: ExtensionsStore) -} - -class Web3WebViewClientFactory( - private val injectors: List, -) { - - fun create( - webView: WebView, - extensionStore: ExtensionsStore, - onPageChangedListener: OnPageChangedListener, - pageCallback: PageCallback - ): Web3WebViewClient { - return Web3WebViewClient(injectors, extensionStore, webView, onPageChangedListener, pageCallback) - } -} +interface PageCallback { -typealias OnPageChangedListener = (url: String, title: String?) -> Unit + fun onPageStarted(webView: WebView, url: String, favicon: Bitmap?) -interface PageCallback { fun handleBrowserIntent(intent: Intent) + + fun onPageChanged(webView: WebView, url: String?, title: String?) } class Web3WebViewClient( - private val injectors: List, - private val extensionStore: ExtensionsStore, private val webView: WebView, - private val onPageChangedListener: OnPageChangedListener, private val pageCallback: PageCallback ) : WebViewClient() { @@ -57,10 +31,6 @@ class Web3WebViewClient( } private var desktopModeChanged = false - fun initialInject() { - injectors.forEach { it.initialInject(webView, extensionStore) } - } - override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean { val url = request.url @@ -74,7 +44,7 @@ class Web3WebViewClient( } override fun onPageStarted(view: WebView, url: String, favicon: Bitmap?) { - tryInject(view, url) + pageCallback.onPageStarted(view, url, favicon) if (desktopMode) { setDesktopViewport(view) } @@ -85,11 +55,9 @@ class Web3WebViewClient( } override fun doUpdateVisitedHistory(view: WebView, url: String, isReload: Boolean) { - onPageChangedListener(url, view.title) + pageCallback.onPageChanged(view, url, view.title) } - private fun tryInject(view: WebView, url: String) = injectors.forEach { it.injectForPage(view, url, extensionStore) } - private fun setDesktopViewport(webView: WebView) { val density = webView.context.resources.displayMetrics.density val deviceWidth = webView.measuredWidth @@ -101,21 +69,12 @@ class Web3WebViewClient( } } -private const val MAX_PROGRESS = 100 - -class Web3ChromeClient( - private val fileChooser: WebViewFileChooser, - private val progressBar: ProgressBar -) : WebChromeClient() { - - override fun onProgressChanged(view: WebView, newProgress: Int) { - progressBar.progress = newProgress - - progressBar.setVisible(newProgress < MAX_PROGRESS) - } +private fun WebView.changeUserAgentByDesktopMode(desktopMode: Boolean) { + val defaultUserAgent = WebSettings.getDefaultUserAgent(context) - override fun onShowFileChooser(webView: WebView?, filePathCallback: ValueCallback>?, fileChooserParams: FileChooserParams?): Boolean { - fileChooser.onShowFileChooser(filePathCallback, fileChooserParams) - return true + settings.userAgentString = if (desktopMode) { + "Mozilla/5.0 (X11; CrOS x86_64 10066.0.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36" + } else { + defaultUserAgent } } diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/webview/WebViewHolder.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/webview/WebViewHolder.kt index 0c872691cc..043bb62d8d 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/webview/WebViewHolder.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/webview/WebViewHolder.kt @@ -7,7 +7,7 @@ class WebViewHolder { var webView: WebView? = null private set - fun set(new: WebView) { + fun set(new: WebView?) { webView = new } diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/webview/WebViewPermissionAsker.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/webview/WebViewPermissionAsker.kt new file mode 100644 index 0000000000..a2aa477eae --- /dev/null +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/webview/WebViewPermissionAsker.kt @@ -0,0 +1,38 @@ +package io.novafoundation.nova.feature_dapp_impl.web3.webview + +import android.Manifest +import android.webkit.PermissionRequest +import io.novafoundation.nova.common.utils.permissions.PermissionsAsker +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +class WebViewPermissionAsker( + private val permissionsAsker: PermissionsAsker.Presentation +) { + + fun requestPermission(coroutineScope: CoroutineScope, request: PermissionRequest) { + coroutineScope.launch { + val permissions = mapPermissionRequest(request) + + if (permissions.isNotEmpty()) { + val result = permissionsAsker.requirePermissionsOrExit(*permissions) + + if (result) { + request.grant(request.resources) + } else { + request.deny() + } + } else { + request.deny() + } + } + } + + private fun mapPermissionRequest(request: PermissionRequest) = request.resources.flatMap { resource -> + when (resource) { + PermissionRequest.RESOURCE_VIDEO_CAPTURE -> listOf(Manifest.permission.CAMERA) + PermissionRequest.RESOURCE_PROTECTED_MEDIA_ID -> listOf(Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.CAMERA) + else -> emptyList() + } + }.toTypedArray() +} diff --git a/feature-dapp-impl/src/main/res/drawable/dapp_tab_background.xml b/feature-dapp-impl/src/main/res/drawable/dapp_tab_background.xml index 6a28386744..0300ccd2cb 100644 --- a/feature-dapp-impl/src/main/res/drawable/dapp_tab_background.xml +++ b/feature-dapp-impl/src/main/res/drawable/dapp_tab_background.xml @@ -1,5 +1,5 @@ - + \ No newline at end of file diff --git a/feature-dapp-impl/src/main/res/layout/fragment_browser_tabs.xml b/feature-dapp-impl/src/main/res/layout/fragment_browser_tabs.xml new file mode 100644 index 0000000000..56193db095 --- /dev/null +++ b/feature-dapp-impl/src/main/res/layout/fragment_browser_tabs.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/feature-dapp-impl/src/main/res/layout/fragment_dapp_browser.xml b/feature-dapp-impl/src/main/res/layout/fragment_dapp_browser.xml index 4e772baac2..65b040eace 100644 --- a/feature-dapp-impl/src/main/res/layout/fragment_dapp_browser.xml +++ b/feature-dapp-impl/src/main/res/layout/fragment_dapp_browser.xml @@ -1,11 +1,11 @@ - + android:background="@color/secondary_screen_background" + android:orientation="vertical"> + android:orientation="horizontal" + app:layout_constraintTop_toTopOf="parent"> + + + + + + - - + android:orientation="horizontal" + app:layout_constraintBottom_toBottomOf="parent"> - + android:paddingBottom="11dp"> + + + + + + + + - \ No newline at end of file + \ No newline at end of file diff --git a/feature-dapp-impl/src/main/res/layout/fragment_dapp_main.xml b/feature-dapp-impl/src/main/res/layout/fragment_dapp_main.xml index af149cd8de..e9c0d9400f 100644 --- a/feature-dapp-impl/src/main/res/layout/fragment_dapp_main.xml +++ b/feature-dapp-impl/src/main/res/layout/fragment_dapp_main.xml @@ -2,7 +2,7 @@ + + + + + + \ No newline at end of file diff --git a/feature-dapp-impl/src/main/res/layout/fragment_search_dapp.xml b/feature-dapp-impl/src/main/res/layout/fragment_search_dapp.xml index 6df99a5a57..b9adf82a20 100644 --- a/feature-dapp-impl/src/main/res/layout/fragment_search_dapp.xml +++ b/feature-dapp-impl/src/main/res/layout/fragment_search_dapp.xml @@ -1,25 +1,63 @@ + android:hint="@string/dapp_search_hint" /> + + + + + + + + + + + + + + + android:clipToPadding="false" + android:paddingBottom="24dp" + app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" /> \ No newline at end of file diff --git a/feature-dapp-impl/src/main/res/layout/item_browser_tab.xml b/feature-dapp-impl/src/main/res/layout/item_browser_tab.xml new file mode 100644 index 0000000000..40e6708581 --- /dev/null +++ b/feature-dapp-impl/src/main/res/layout/item_browser_tab.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/feature-dapp-impl/src/main/res/layout/item_dapp_categories_shimmering.xml b/feature-dapp-impl/src/main/res/layout/item_dapp_categories_shimmering.xml new file mode 100644 index 0000000000..09d6edab9d --- /dev/null +++ b/feature-dapp-impl/src/main/res/layout/item_dapp_categories_shimmering.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/feature-dapp-impl/src/main/res/layout/item_dapp_category.xml b/feature-dapp-impl/src/main/res/layout/item_dapp_category.xml index 294ee82431..03aa7b1519 100644 --- a/feature-dapp-impl/src/main/res/layout/item_dapp_category.xml +++ b/feature-dapp-impl/src/main/res/layout/item_dapp_category.xml @@ -1,19 +1,35 @@ - \ No newline at end of file + android:orientation="horizontal" + android:paddingStart="4dp" + android:paddingEnd="8dp"> + + + + + + \ No newline at end of file diff --git a/feature-dapp-impl/src/main/res/layout/item_dapp_category_shimmering.xml b/feature-dapp-impl/src/main/res/layout/item_dapp_category_shimmering.xml new file mode 100644 index 0000000000..15e4d4b6eb --- /dev/null +++ b/feature-dapp-impl/src/main/res/layout/item_dapp_category_shimmering.xml @@ -0,0 +1,24 @@ + + + + + + + + \ No newline at end of file diff --git a/feature-dapp-impl/src/main/res/layout/item_dapp_favorite_dragable.xml b/feature-dapp-impl/src/main/res/layout/item_dapp_favorite_dragable.xml new file mode 100644 index 0000000000..343f412952 --- /dev/null +++ b/feature-dapp-impl/src/main/res/layout/item_dapp_favorite_dragable.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/feature-dapp-impl/src/main/res/layout/item_dapp_group.xml b/feature-dapp-impl/src/main/res/layout/item_dapp_group.xml new file mode 100644 index 0000000000..eb76988913 --- /dev/null +++ b/feature-dapp-impl/src/main/res/layout/item_dapp_group.xml @@ -0,0 +1,31 @@ + + + + + + + + \ No newline at end of file diff --git a/feature-dapp-impl/src/main/res/layout/item_dapp_header.xml b/feature-dapp-impl/src/main/res/layout/item_dapp_header.xml index d773ab90ef..d519e55d80 100644 --- a/feature-dapp-impl/src/main/res/layout/item_dapp_header.xml +++ b/feature-dapp-impl/src/main/res/layout/item_dapp_header.xml @@ -28,27 +28,86 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" /> + + + + + + + + + + + + + + + app:layout_constraintTop_toBottomOf="@+id/categorizedDappsCategoriesBarrier"> + android:layout_marginBottom="24dp" + android:orientation="vertical"> + android:textColor="@color/text_primary" /> - - + app:drawableStartCompat="@drawable/ic_favorite_heart_filled_20" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/dappMainWelcomeGroup" /> - + android:layout_marginHorizontal="16dp" + android:padding="8dp" + android:text="@string/common_see_all" + android:textColor="@color/button_text_accent" + app:layout_constraintBottom_toBottomOf="@+id/dAppMainFavoriteDAppTitle" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="@+id/dAppMainFavoriteDAppTitle" /> diff --git a/feature-dapp-impl/src/main/res/layout/item_favorite_dapp.xml b/feature-dapp-impl/src/main/res/layout/item_favorite_dapp.xml new file mode 100644 index 0000000000..ada219f3fb --- /dev/null +++ b/feature-dapp-impl/src/main/res/layout/item_favorite_dapp.xml @@ -0,0 +1,32 @@ + + + + + + + + \ No newline at end of file diff --git a/feature-deep-linking/src/main/java/io/novafoundation/nova/feature_deep_linking/presentation/handling/DeepLinkingRouter.kt b/feature-deep-linking/src/main/java/io/novafoundation/nova/feature_deep_linking/presentation/handling/DeepLinkingRouter.kt index 9de1451aed..2a28888e49 100644 --- a/feature-deep-linking/src/main/java/io/novafoundation/nova/feature_deep_linking/presentation/handling/DeepLinkingRouter.kt +++ b/feature-deep-linking/src/main/java/io/novafoundation/nova/feature_deep_linking/presentation/handling/DeepLinkingRouter.kt @@ -10,7 +10,7 @@ interface DeepLinkingRouter { fun openDAppBrowser(url: String) - fun openImportAccountScreen(importAccountPayload: ImportAccountPayload) + fun openImportAccountScreen(payload: ImportAccountPayload) fun openReferendum(payload: ReferendumDetailsPayload) diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/GovernanceRouter.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/GovernanceRouter.kt index ae5e9c01f0..dc7ceaeffa 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/GovernanceRouter.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/GovernanceRouter.kt @@ -27,7 +27,7 @@ interface GovernanceRouter : ReturnableRouter { fun openReferendaFilters() - fun openDAppBrowser(initialUrl: String) + fun openDAppBrowser(url: String) fun openReferendumDescription(payload: DescriptionPayload) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/StakingFeatureDependencies.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/StakingFeatureDependencies.kt index e2c51faccc..adee091730 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/StakingFeatureDependencies.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/StakingFeatureDependencies.kt @@ -67,6 +67,58 @@ import javax.inject.Named interface StakingFeatureDependencies { + val amountChooserMixinFactory: AmountChooserMixin.Factory + + val actionAwaitableMixinFactory: ActionAwaitableMixin.Factory + + val walletUiUseCase: WalletUiUseCase + + val resourcesHintsMixinFactory: ResourcesHintsMixinFactory + + val selectedAccountUseCase: SelectedAccountUseCase + + val chainStateRepository: ChainStateRepository + + val sampledBlockTimeStorage: SampledBlockTimeStorage + + val timestampRepository: TimestampRepository + + val totalIssuanceRepository: TotalIssuanceRepository + + val onChainIdentityRepository: OnChainIdentityRepository + + val identityMixinFactory: IdentityMixin.Factory + + val storageStorageSharedRequestsBuilderFactory: StorageSharedRequestsBuilderFactory + + val stakingDashboardDao: StakingDashboardDao + + val dAppMetadataRepository: DAppMetadataRepository + + val runtimeCallsApi: MultiChainRuntimeCallsApi + + val arbitraryAssetUseCase: ArbitraryAssetUseCase + + val locksRepository: BalanceLocksRepository + + val externalBalanceDao: ExternalBalanceDao + + val partialRetriableMixinFactory: PartialRetriableMixin.Factory + + val proxyDepositCalculator: ProxyDepositCalculator + + val getProxyRepository: GetProxyRepository + + val descriptionBottomSheetLauncher: DescriptionBottomSheetLauncher + + val metaAccountGroupingInteractor: MetaAccountGroupingInteractor + + val selectAddressMixinFactory: SelectAddressMixin.Factory + + val proxyConstantsRepository: ProxyConstantsRepository + + val proxySyncService: ProxySyncService + fun contextManager(): ContextManager fun computationalCache(): ComputationalCache @@ -138,58 +190,6 @@ interface StakingFeatureDependencies { fun addressInputMixinFactory(): AddressInputMixinFactory - val amountChooserMixinFactory: AmountChooserMixin.Factory - - val actionAwaitableMixinFactory: ActionAwaitableMixin.Factory - @Caching fun cachingIconGenerator(): AddressIconGenerator - - val walletUiUseCase: WalletUiUseCase - - val resourcesHintsMixinFactory: ResourcesHintsMixinFactory - - val selectedAccountUseCase: SelectedAccountUseCase - - val chainStateRepository: ChainStateRepository - - val sampledBlockTimeStorage: SampledBlockTimeStorage - - val timestampRepository: TimestampRepository - - val totalIssuanceRepository: TotalIssuanceRepository - - val onChainIdentityRepository: OnChainIdentityRepository - - val identityMixinFactory: IdentityMixin.Factory - - val storageStorageSharedRequestsBuilderFactory: StorageSharedRequestsBuilderFactory - - val stakingDashboardDao: StakingDashboardDao - - val dAppMetadataRepository: DAppMetadataRepository - - val runtimeCallsApi: MultiChainRuntimeCallsApi - - val arbitraryAssetUseCase: ArbitraryAssetUseCase - - val locksRepository: BalanceLocksRepository - - val externalBalanceDao: ExternalBalanceDao - - val partialRetriableMixinFactory: PartialRetriableMixin.Factory - - val proxyDepositCalculator: ProxyDepositCalculator - - val getProxyRepository: GetProxyRepository - - val descriptionBottomSheetLauncher: DescriptionBottomSheetLauncher - - val metaAccountGroupingInteractor: MetaAccountGroupingInteractor - - val selectAddressMixinFactory: SelectAddressMixin.Factory - - val proxyConstantsRepository: ProxyConstantsRepository - - val proxySyncService: ProxySyncService } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/StakingRouter.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/StakingRouter.kt index 04bea06c6d..0fa7ecb32f 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/StakingRouter.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/StakingRouter.kt @@ -80,8 +80,6 @@ interface StakingRouter { fun openRebag() - fun openDAppBrowser(url: String) - fun openStakingPeriods() fun openSetupStakingType() @@ -103,4 +101,6 @@ interface StakingRouter { fun openStakingProxyList() fun openConfirmRemoveStakingProxy(payload: ConfirmRemoveStakingProxyPayload) + + fun openDAppBrowser(url: String) } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/dashboard/more/MoreStakingOptionsViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/dashboard/more/MoreStakingOptionsViewModel.kt index 4000d4c953..d95f4d1c65 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/dashboard/more/MoreStakingOptionsViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/dashboard/more/MoreStakingOptionsViewModel.kt @@ -25,9 +25,9 @@ class MoreStakingOptionsViewModel( private val interactor: StakingDashboardInteractor, private val startStakingRouter: StartMultiStakingRouter, private val dashboardRouter: StakingDashboardRouter, - private val router: StakingRouter, private val stakingSharedState: StakingSharedState, private val presentationMapper: StakingDashboardPresentationMapper, + private val stakingRouter: StakingRouter, ) : BaseViewModel() { init { @@ -53,7 +53,7 @@ class MoreStakingOptionsViewModel( } fun onBrowserStakingItemClicked(item: StakingDAppModel) = launch { - router.openDAppBrowser(item.url) + stakingRouter.openDAppBrowser(item.url) } private fun syncDApps() = launch { diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/dashboard/more/di/MoreStakingOptionsModule.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/dashboard/more/di/MoreStakingOptionsModule.kt index c55b8cc876..5b9484788f 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/dashboard/more/di/MoreStakingOptionsModule.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/dashboard/more/di/MoreStakingOptionsModule.kt @@ -26,7 +26,7 @@ class MoreStakingOptionsModule { fun provideViewModel( interactor: StakingDashboardInteractor, dashboardRouter: StakingDashboardRouter, - router: StakingRouter, + stakingRouter: StakingRouter, stakingSharedState: StakingSharedState, presentationMapper: StakingDashboardPresentationMapper, startMultiStakingRouter: StartMultiStakingRouter, @@ -35,7 +35,7 @@ class MoreStakingOptionsModule { interactor = interactor, startStakingRouter = startMultiStakingRouter, dashboardRouter = dashboardRouter, - router = router, + stakingRouter = stakingRouter, stakingSharedState = stakingSharedState, presentationMapper = presentationMapper ) diff --git a/feature-versions-api/src/main/java/io/novafoundation/nova/feature_versions_api/presentation/VersionsRouter.kt b/feature-versions-api/src/main/java/io/novafoundation/nova/feature_versions_api/presentation/VersionsRouter.kt index 66bce89a68..a37fd7e077 100644 --- a/feature-versions-api/src/main/java/io/novafoundation/nova/feature_versions_api/presentation/VersionsRouter.kt +++ b/feature-versions-api/src/main/java/io/novafoundation/nova/feature_versions_api/presentation/VersionsRouter.kt @@ -4,5 +4,5 @@ interface VersionsRouter { fun openAppUpdater() - fun back() + fun closeUpdateNotifications() } diff --git a/feature-versions-impl/src/main/java/io/novafoundation/nova/feature_versions_impl/presentation/update/UpdateNotificationViewModel.kt b/feature-versions-impl/src/main/java/io/novafoundation/nova/feature_versions_impl/presentation/update/UpdateNotificationViewModel.kt index d270019e64..dfad7a795b 100644 --- a/feature-versions-impl/src/main/java/io/novafoundation/nova/feature_versions_impl/presentation/update/UpdateNotificationViewModel.kt +++ b/feature-versions-impl/src/main/java/io/novafoundation/nova/feature_versions_impl/presentation/update/UpdateNotificationViewModel.kt @@ -50,11 +50,11 @@ class UpdateNotificationViewModel( fun skipClicked() = launch { interactor.skipNewUpdates() - router.back() + router.closeUpdateNotifications() } fun installUpdateClicked() { - router.back() + router.closeUpdateNotifications() router.openAppUpdater() } diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/runtime/RuntimeFilesCache.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/runtime/RuntimeFilesCache.kt index 66f659caee..855435950d 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/runtime/RuntimeFilesCache.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/runtime/RuntimeFilesCache.kt @@ -75,7 +75,7 @@ class RuntimeFilesCache( } private suspend fun getCacheFile(name: String): File { - return fileProvider.getFileInInternalCacheStorage(name) + return withContext(Dispatchers.IO) { fileProvider.getFileInInternalCacheStorage(name) } } private fun isMetadataOpaque(chainId: String): Boolean {