Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DApp bottom sheet entry point #1731

Merged
merged 4 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package io.novafoundation.nova.app.root.data.browser

import io.novafoundation.nova.common.utils.mapList
import io.novafoundation.nova.core_db.dao.BrowserTabsDao
import kotlinx.coroutines.flow.Flow

class TabsRepository(private val tabsDao: BrowserTabsDao) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should have an interface in dapp browser api and implementation in dapp browser impl and be provided to root


fun observeTabIds(): Flow<List<String>> {
return tabsDao.observeAllTabs().mapList { it.id }
}

suspend fun removeAllTabs() {
tabsDao.removeAllTabs()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -39,6 +41,34 @@ 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

fun updateNotificationsInteractor(): UpdateNotificationsInteractor

fun contributionsInteractor(): ContributionsInteractor
Expand All @@ -59,8 +89,6 @@ interface RootDependencies {

fun currencyInteractor(): CurrencyInteractor

val balancesUpdateSystem: BalancesUpdateSystem

fun stakingRepository(): StakingRepository

fun chainRegistry(): ChainRegistry
Expand All @@ -86,26 +114,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
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package io.novafoundation.nova.app.root.domain

import io.novafoundation.nova.app.root.data.browser.TabsRepository
import kotlinx.coroutines.flow.Flow

class SplitScreenInteractor(val repository: TabsRepository) {

fun observeTabIds(): Flow<List<String>> {
return repository.observeTabIds()
}

suspend fun removeAllTabs() {
repository.removeAllTabs()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import io.novafoundation.nova.app.root.navigation.navigators.BaseNavigator
import io.novafoundation.nova.feature_dapp_api.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
Expand All @@ -16,7 +17,7 @@ class DAppNavigator(

override fun openChangeAccount() = performNavigation(R.id.action_open_switch_wallet)

override fun openDAppBrowser(initialUrl: String) {
override fun openDAppBrowser(payload: DAppBrowserPayload) {
// Close deapp browser if it is already opened
// TODO it's better to provide new url to existing browser
val currentDestination = rootNavigationHolder.navController?.currentDestination
Expand All @@ -27,7 +28,7 @@ class DAppNavigator(
R.id.dappTabsFragment -> R.id.action_dappTabsFragment_to_dapp_browser_graph
else -> R.id.action_open_dappBrowser
}
performNavigation(destinationId, DAppBrowserFragment.getBundle(initialUrl))
performNavigation(destinationId, DAppBrowserFragment.getBundle(payload))
}

override fun openDappSearch() {
Expand All @@ -50,9 +51,16 @@ class DAppNavigator(
actionId = R.id.action_mainFragment_to_authorizedDAppsFragment
)

override fun openTabs() = performNavigation(
actionId = R.id.action_DAppBrowserFragment_to_browserTabsFragment
)
override fun openTabs() {
val currentDestination = rootNavigationHolder.navController?.currentDestination

val destinationId = when (currentDestination?.id) {
R.id.dappBrowserFragment -> R.id.action_DAppBrowserFragment_to_browserTabsFragment
else -> R.id.action_open_dappTabs
}

performNavigation(destinationId)
}

override fun finishTabs() = performNavigation(
actionId = R.id.action_finish_tabs_fragment
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import io.novafoundation.nova.feature_account_api.presenatation.account.add.Impo
import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter
import io.novafoundation.nova.feature_assets.presentation.AssetsRouter
import io.novafoundation.nova.feature_dapp_api.DAppRouter
import io.novafoundation.nova.feature_dapp_api.presentation.browser.main.DAppBrowserPayload
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
Expand All @@ -21,7 +22,7 @@ class DeepLinkingNavigator(
}

override fun openDAppBrowser(url: String) {
dAppRouter.openDAppBrowser(url)
dAppRouter.openDAppBrowser(DAppBrowserPayload.Address(url))
}

override fun openImportAccountScreen(importAccountPayload: ImportAccountPayload) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import io.novafoundation.nova.app.root.navigation.navigators.BaseNavigator
import io.novafoundation.nova.app.root.navigation.holders.NavigationHolder
import io.novafoundation.nova.app.root.navigation.navigators.Navigator
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
Expand Down Expand Up @@ -207,9 +208,9 @@ class GovernanceNavigator(
args = DescriptionFragment.getBundle(payload)
)

override fun openDAppBrowser(initialUrl: String) = performNavigation(
override fun openDAppBrowser(url: String) = performNavigation(
actionId = R.id.action_referendumDetailsFragment_to_DAppBrowserGraph,
args = DAppBrowserFragment.getBundle(initialUrl)
args = DAppBrowserFragment.getBundle(DAppBrowserPayload.Address(url))
)

override fun openReferendumDescription(payload: DescriptionPayload) = performNavigation(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.novafoundation.nova.app.root.presentation.splitScreen

import android.graphics.Outline
import android.view.View
import android.view.ViewOutlineProvider

class RoundCornersOutlineProvider(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be in common module

private val cornerRadius: Float
) : ViewOutlineProvider() {

override fun getOutline(view: View, outline: Outline) {
outline.setRoundRect(0, 0, view.width, view.height, cornerRadius)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.navigation.NavController
import androidx.navigation.fragment.NavHostFragment
import io.novafoundation.nova.app.R
Expand All @@ -12,7 +13,12 @@ import io.novafoundation.nova.app.root.di.RootComponent
import io.novafoundation.nova.app.root.navigation.holders.MainNavigationHolder
import io.novafoundation.nova.common.base.BaseFragment
import io.novafoundation.nova.common.di.FeatureUtils
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.dappEntryPointText
import kotlinx.android.synthetic.main.fragment_split_screen.mainNavHost

class SplitScreenFragment : BaseFragment<SplitScreenViewModel>() {

Expand Down Expand Up @@ -43,14 +49,37 @@ class SplitScreenFragment : BaseFragment<SplitScreenViewModel>() {
}

override fun initViews() {
dappEntryPoint.setOnClickListener { viewModel.onTabsClicked() }
dappEntryPointClose.setOnClickListener { viewModel.onTabsCloseClicked() }
}

override fun subscribe(viewModel: SplitScreenViewModel) {
setupCloseAllDappTabsDialogue(viewModel.closeAllTabsConfirmation)

viewModel.dappTabsQuantity.observe {
val shouldBeVisible = it > 0
val isVisibilityChanged = dappEntryPoint.isVisible != shouldBeVisible

if (isVisibilityChanged) {
mainNavHost.outlineProvider = RoundCornersOutlineProvider(getOutlineCornerRadius(shouldBeVisible))
}

dappEntryPoint.isVisible = it > 0
dappEntryPointText.text = getString(R.string.dapp_entry_point_title, it)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldnt we display a proper tab name in case there's only one tab?
AlsoI think this formatting logic can go to ViewModel (whether to make entryPointVisible, what text to set to entry point)

}
}

private val mainNavController: NavController by lazy {
val navHostFragment = childFragmentManager.findFragmentById(R.id.mainNavHost) as NavHostFragment

navHostFragment.navController
}

private fun getOutlineCornerRadius(isRounded: Boolean): Float {
return if (isRounded) {
12f
} else {
0f
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,43 @@
package io.novafoundation.nova.app.root.presentation.splitScreen

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.feature_dapp_api.DAppRouter
import io.novafoundation.nova.feature_dapp_api.presentation.browser.main.DAppBrowserPayload
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch

class SplitScreenViewModel() : BaseViewModel()
class SplitScreenViewModel(
private val interactor: SplitScreenInteractor,
private val router: DAppRouter,
private val actionAwaitableMixinFactory: ActionAwaitableMixin.Factory,
) : BaseViewModel() {

val closeAllTabsConfirmation = actionAwaitableMixinFactory.confirmingAction<Unit>()

private val tabIdsFlow = interactor.observeTabIds()
.shareInBackground()

val dappTabsQuantity = tabIdsFlow.map { it.size }

fun onTabsClicked() = launch {
val tabIds = tabIdsFlow.first()

if (tabIds.size == 1) {
val payload = DAppBrowserPayload.Tab(tabIds.single())
router.openDAppBrowser(payload)
} else {
router.openTabs()
}
}

fun onTabsCloseClicked() = launch {
closeAllTabsConfirmation.awaitAction()

interactor.removeAllTabs()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,14 @@ import androidx.lifecycle.ViewModelProvider
import dagger.Module
import dagger.Provides
import dagger.multibindings.IntoMap
import io.novafoundation.nova.app.root.data.browser.TabsRepository
import io.novafoundation.nova.app.root.domain.SplitScreenInteractor
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.core_db.dao.BrowserTabsDao
import io.novafoundation.nova.feature_dapp_api.DAppRouter

@Module(
includes = [
Expand All @@ -17,11 +22,25 @@ import io.novafoundation.nova.common.di.viewmodel.ViewModelModule
)
class SplitScreenFragmentModule {

@Provides
fun provideTabQuantityRepository(tabsDao: BrowserTabsDao): TabsRepository {
return TabsRepository(tabsDao)
}

@Provides
fun provideInteractor(repository: TabsRepository): SplitScreenInteractor {
return SplitScreenInteractor(repository)
}

@Provides
@IntoMap
@ViewModelKey(SplitScreenViewModel::class)
fun provideViewModel(): ViewModel {
return SplitScreenViewModel()
fun provideViewModel(
interactor: SplitScreenInteractor,
dAppRouter: DAppRouter,
actionAwaitableMixinFactory: ActionAwaitableMixin.Factory,
): ViewModel {
return SplitScreenViewModel(interactor, dAppRouter, actionAwaitableMixinFactory)
}

@Provides
Expand Down
Loading
Loading