* For #4124: Migrate BrowserToolbar to Libstate * Restores QuickActionSheetReducer * Improve tests * Make QuickActionSheetController * Finalize tests * Breaks out QuickActionSheetState * Fix comments * Adds BrowserStoreTestmaster
parent
87d8f3b037
commit
6fa022c2f8
|
@ -16,13 +16,12 @@ import android.widget.RadioButton
|
|||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.net.toUri
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.NavHostFragment.findNavController
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import kotlinx.android.synthetic.main.component_search.*
|
||||
import kotlinx.android.synthetic.main.fragment_browser.*
|
||||
|
@ -55,56 +54,46 @@ import mozilla.components.feature.tab.collections.TabCollection
|
|||
import mozilla.components.support.base.feature.BackHandler
|
||||
import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
|
||||
import mozilla.components.support.ktx.android.view.exitImmersiveModeIfNeeded
|
||||
import org.mozilla.fenix.BrowsingModeManager
|
||||
import mozilla.components.support.ktx.kotlin.toUri
|
||||
import org.mozilla.fenix.FeatureFlags
|
||||
import org.mozilla.fenix.FenixViewModelProvider
|
||||
import org.mozilla.fenix.HomeActivity
|
||||
import org.mozilla.fenix.IntentReceiverActivity
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.ThemeManager
|
||||
import org.mozilla.fenix.browser.readermode.DefaultReaderModeController
|
||||
import org.mozilla.fenix.collections.CreateCollectionViewModel
|
||||
import org.mozilla.fenix.collections.SaveCollectionStep
|
||||
import org.mozilla.fenix.collections.getStepForCollectionsSize
|
||||
import org.mozilla.fenix.components.FenixSnackbar
|
||||
import org.mozilla.fenix.components.FindInPageIntegration
|
||||
import org.mozilla.fenix.components.StoreProvider
|
||||
import org.mozilla.fenix.components.TabCollectionStorage
|
||||
import org.mozilla.fenix.components.metrics.Event
|
||||
import org.mozilla.fenix.components.toolbar.SearchAction
|
||||
import org.mozilla.fenix.components.toolbar.SearchState
|
||||
import org.mozilla.fenix.components.toolbar.ToolbarComponent
|
||||
import org.mozilla.fenix.components.toolbar.BrowserInteractor
|
||||
import org.mozilla.fenix.components.toolbar.BrowserState
|
||||
import org.mozilla.fenix.components.toolbar.BrowserStore
|
||||
import org.mozilla.fenix.components.toolbar.BrowserToolbarView
|
||||
import org.mozilla.fenix.components.toolbar.DefaultBrowserToolbarController
|
||||
import org.mozilla.fenix.components.toolbar.QuickActionSheetAction
|
||||
import org.mozilla.fenix.components.toolbar.QuickActionSheetState
|
||||
import org.mozilla.fenix.components.toolbar.ToolbarIntegration
|
||||
import org.mozilla.fenix.components.toolbar.ToolbarMenu
|
||||
import org.mozilla.fenix.components.toolbar.ToolbarUIView
|
||||
import org.mozilla.fenix.components.toolbar.ToolbarViewModel
|
||||
import org.mozilla.fenix.components.toolbar.trackToolbarItemInteraction
|
||||
import org.mozilla.fenix.customtabs.CustomTabsIntegration
|
||||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.ext.enterToImmersiveMode
|
||||
import org.mozilla.fenix.ext.nav
|
||||
import org.mozilla.fenix.ext.requireComponents
|
||||
import org.mozilla.fenix.ext.toTab
|
||||
import org.mozilla.fenix.home.sessioncontrol.SessionControlChange
|
||||
import org.mozilla.fenix.lib.Do
|
||||
import org.mozilla.fenix.mvi.ActionBusFactory
|
||||
import org.mozilla.fenix.mvi.getAutoDisposeObservable
|
||||
import org.mozilla.fenix.mvi.getManagedEmitter
|
||||
import org.mozilla.fenix.quickactionsheet.QuickActionInteractor
|
||||
import org.mozilla.fenix.quickactionsheet.QuickActionSheetAction
|
||||
import org.mozilla.fenix.quickactionsheet.QuickActionSheetBehavior
|
||||
import org.mozilla.fenix.quickactionsheet.QuickActionSheetState
|
||||
import org.mozilla.fenix.quickactionsheet.QuickActionSheetStore
|
||||
import org.mozilla.fenix.quickactionsheet.QuickActionView
|
||||
import org.mozilla.fenix.settings.SupportUtils
|
||||
import org.mozilla.fenix.quickactionsheet.DefaultQuickActionSheetController
|
||||
import org.mozilla.fenix.quickactionsheet.QuickActionSheetView
|
||||
import org.mozilla.fenix.utils.Settings
|
||||
import java.net.MalformedURLException
|
||||
import java.net.URL
|
||||
|
||||
@SuppressWarnings("TooManyFunctions", "LargeClass")
|
||||
class BrowserFragment : Fragment(), BackHandler {
|
||||
private lateinit var toolbarComponent: ToolbarComponent
|
||||
private lateinit var quickActionSheetStore: QuickActionSheetStore
|
||||
private lateinit var browserStore: BrowserStore
|
||||
private lateinit var browserInteractor: BrowserInteractor
|
||||
|
||||
private lateinit var browserToolbarView: BrowserToolbarView
|
||||
private lateinit var quickActionSheetView: QuickActionSheetView
|
||||
|
||||
private var tabCollectionObserver: Observer<List<TabCollection>>? = null
|
||||
private var sessionObserver: Session.Observer? = null
|
||||
|
@ -151,24 +140,26 @@ class BrowserFragment : Fragment(), BackHandler {
|
|||
val view = inflater.inflate(R.layout.fragment_browser, container, false)
|
||||
view.browserLayout.transitionName = "$TAB_ITEM_TRANSITION_NAME${getSessionById()?.id}"
|
||||
|
||||
toolbarComponent = ToolbarComponent(
|
||||
view.browserLayout,
|
||||
ActionBusFactory.get(this),
|
||||
customTabSessionId,
|
||||
(activity as HomeActivity).browsingModeManager.isPrivate,
|
||||
FenixViewModelProvider.create(
|
||||
this,
|
||||
ToolbarViewModel::class.java
|
||||
) {
|
||||
ToolbarViewModel(SearchState())
|
||||
}
|
||||
)
|
||||
|
||||
startPostponedEnterTransition()
|
||||
|
||||
val activity = activity as HomeActivity
|
||||
ThemeManager.applyStatusBarTheme(activity.window, activity.themeManager, activity)
|
||||
|
||||
val appLink = requireComponents.useCases.appLinksUseCases.appLinkRedirect
|
||||
browserStore = StoreProvider.get(this) {
|
||||
BrowserStore(
|
||||
BrowserState(
|
||||
quickActionSheetState = QuickActionSheetState(
|
||||
readable = getSessionById()?.readerable ?: false,
|
||||
bookmarked = findBookmarkedURL(getSessionById()),
|
||||
readerActive = getSessionById()?.readerMode ?: false,
|
||||
bounceNeeded = false,
|
||||
isAppLink = getSessionById()?.let { appLink.invoke(it.url).hasExternalApp() } ?: false
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
|
@ -176,13 +167,62 @@ class BrowserFragment : Fragment(), BackHandler {
|
|||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val sessionManager = requireComponents.core.sessionManager
|
||||
|
||||
val viewModel = activity!!.run {
|
||||
ViewModelProviders.of(this).get(CreateCollectionViewModel::class.java)
|
||||
}
|
||||
|
||||
browserInteractor = BrowserInteractor(
|
||||
context = context!!,
|
||||
store = browserStore,
|
||||
browserToolbarController = DefaultBrowserToolbarController(
|
||||
context!!,
|
||||
findNavController(),
|
||||
findInPageLauncher = { findInPageIntegration.withFeature { it.launch() } },
|
||||
nestedScrollQuickActionView = nestedScrollQuickAction,
|
||||
engineView = engineView,
|
||||
currentSession = getSessionById() ?: requireComponents.core.sessionManager.selectedSessionOrThrow,
|
||||
viewModel = viewModel
|
||||
),
|
||||
quickActionSheetController = DefaultQuickActionSheetController(
|
||||
context = context!!,
|
||||
navController = findNavController(),
|
||||
currentSession = getSessionById() ?: requireComponents.core.sessionManager.selectedSessionOrThrow,
|
||||
appLinksUseCases = requireComponents.useCases.appLinksUseCases,
|
||||
bookmarkTapped = {
|
||||
lifecycleScope.launch { bookmarkTapped(it) }
|
||||
}
|
||||
),
|
||||
readerModeController = DefaultReaderModeController(readerViewFeature),
|
||||
currentSession = getSessionById() ?: requireComponents.core.sessionManager.selectedSessionOrThrow
|
||||
)
|
||||
|
||||
browserToolbarView = BrowserToolbarView(
|
||||
container = view.browserLayout,
|
||||
interactor = browserInteractor,
|
||||
currentSession = getSessionById() ?: requireComponents.core.sessionManager.selectedSessionOrThrow
|
||||
)
|
||||
|
||||
toolbarIntegration.set(
|
||||
feature = (toolbarComponent.uiView as ToolbarUIView).toolbarIntegration,
|
||||
feature = browserToolbarView.toolbarIntegration,
|
||||
owner = this,
|
||||
view = view
|
||||
)
|
||||
|
||||
val sessionManager = requireComponents.core.sessionManager
|
||||
findInPageIntegration.set(
|
||||
feature = FindInPageIntegration(
|
||||
requireComponents.core.sessionManager, customTabSessionId, view.findInPageView, view.engineView, toolbar
|
||||
),
|
||||
owner = this,
|
||||
view = view
|
||||
)
|
||||
|
||||
quickActionSheetView = QuickActionSheetView(view.nestedScrollQuickAction, browserInteractor)
|
||||
|
||||
browserToolbarView.view.setOnSiteSecurityClickedListener {
|
||||
showQuickSettingsDialog()
|
||||
}
|
||||
|
||||
contextMenuFeature.set(
|
||||
feature = ContextMenuFeature(
|
||||
|
@ -252,14 +292,6 @@ class BrowserFragment : Fragment(), BackHandler {
|
|||
view = view
|
||||
)
|
||||
|
||||
findInPageIntegration.set(
|
||||
feature = FindInPageIntegration(
|
||||
requireComponents.core.sessionManager, customTabSessionId, view.findInPageView, view.engineView, toolbar
|
||||
),
|
||||
owner = this,
|
||||
view = view
|
||||
)
|
||||
|
||||
val accentHighContrastColor = ThemeManager.resolveAttribute(R.attr.accentHighContrast, requireContext())
|
||||
|
||||
sitePermissionsFeature.set(
|
||||
|
@ -357,7 +389,7 @@ class BrowserFragment : Fragment(), BackHandler {
|
|||
) { available ->
|
||||
if (available) { requireComponents.analytics.metrics.track(Event.ReaderModeAvailable) }
|
||||
|
||||
quickActionSheetStore.apply {
|
||||
browserStore.apply {
|
||||
dispatch(QuickActionSheetAction.ReadableStateChange(available))
|
||||
dispatch(QuickActionSheetAction.ReaderActiveStateChange(
|
||||
sessionManager.selectedSession?.readerMode ?: false
|
||||
|
@ -368,8 +400,6 @@ class BrowserFragment : Fragment(), BackHandler {
|
|||
view = view
|
||||
)
|
||||
|
||||
val actionEmitter = ActionBusFactory.get(this).getManagedEmitter(SearchAction::class.java)
|
||||
|
||||
customTabSessionId?.let {
|
||||
customTabsIntegration.set(
|
||||
feature = CustomTabsIntegration(
|
||||
|
@ -380,46 +410,19 @@ class BrowserFragment : Fragment(), BackHandler {
|
|||
activity,
|
||||
view.nestedScrollQuickAction,
|
||||
view.swipeRefresh,
|
||||
onItemTapped = { actionEmitter.onNext(SearchAction.ToolbarMenuItemTapped(it)) }
|
||||
onItemTapped = { browserInteractor.onBrowserToolbarMenuItemTapped(it) }
|
||||
),
|
||||
owner = this,
|
||||
view = view)
|
||||
}
|
||||
|
||||
toolbarComponent.setOnSiteSecurityClickedListener {
|
||||
browserToolbarView.view.setOnSiteSecurityClickedListener {
|
||||
showQuickSettingsDialog()
|
||||
}
|
||||
|
||||
val appLink = requireComponents.useCases.appLinksUseCases.appLinkRedirect
|
||||
quickActionSheetStore = StoreProvider.get(this) {
|
||||
QuickActionSheetStore(
|
||||
QuickActionSheetState(
|
||||
readable = getSessionById()?.readerable ?: false,
|
||||
bookmarked = findBookmarkedURL(getSessionById()),
|
||||
readerActive = getSessionById()?.readerMode ?: false,
|
||||
bounceNeeded = false,
|
||||
isAppLink = getSessionById()?.let { appLink.invoke(it.url).hasExternalApp() } ?: false
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val quickActionSheetView = QuickActionView(
|
||||
view.nestedScrollQuickAction,
|
||||
|
||||
QuickActionInteractor(
|
||||
context!!,
|
||||
DefaultReaderModeController(readerViewFeature),
|
||||
quickActionSheetStore,
|
||||
shareUrl = ::shareUrl,
|
||||
bookmarkTapped = {
|
||||
lifecycleScope.launch { bookmarkTapped(it) }
|
||||
},
|
||||
appLinksUseCases = requireComponents.useCases.appLinksUseCases
|
||||
)
|
||||
)
|
||||
|
||||
consumeFrom(quickActionSheetStore) {
|
||||
consumeFrom(browserStore) {
|
||||
quickActionSheetView.update(it)
|
||||
browserToolbarView.update(it)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -484,28 +487,6 @@ class BrowserFragment : Fragment(), BackHandler {
|
|||
getSessionById()?.let { (activity as HomeActivity).updateThemeForSession(it) }
|
||||
(activity as AppCompatActivity).supportActionBar?.hide()
|
||||
|
||||
getAutoDisposeObservable<SearchAction>()
|
||||
.subscribe {
|
||||
when (it) {
|
||||
is SearchAction.ToolbarClicked -> {
|
||||
nav(
|
||||
R.id.browserFragment,
|
||||
BrowserFragmentDirections.actionBrowserFragmentToSearchFragment(
|
||||
getSessionById()?.id
|
||||
)
|
||||
)
|
||||
|
||||
requireComponents.analytics.metrics.track(
|
||||
Event.SearchBarTapped(Event.SearchBarTapped.Source.BROWSER)
|
||||
)
|
||||
}
|
||||
is SearchAction.ToolbarMenuItemTapped -> {
|
||||
val metrics = requireComponents.analytics.metrics
|
||||
trackToolbarItemInteraction(metrics, it)
|
||||
handleToolbarItemInteraction(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
assignSitePermissionsRules()
|
||||
}
|
||||
|
||||
|
@ -530,14 +511,14 @@ class BrowserFragment : Fragment(), BackHandler {
|
|||
)
|
||||
|
||||
withContext(Main) {
|
||||
quickActionSheetStore.dispatch(
|
||||
browserStore.dispatch(
|
||||
QuickActionSheetAction.BookmarkedStateChange(bookmarked = true)
|
||||
)
|
||||
requireComponents.analytics.metrics.track(Event.AddBookmark)
|
||||
|
||||
view?.let {
|
||||
FenixSnackbar.make(it.rootView, Snackbar.LENGTH_LONG)
|
||||
.setAnchorView(toolbarComponent.uiView.view)
|
||||
.setAnchorView(browserToolbarView.view)
|
||||
.setAction(getString(R.string.edit_bookmark_snackbar_action)) {
|
||||
nav(
|
||||
R.id.browserFragment,
|
||||
|
@ -624,110 +605,6 @@ class BrowserFragment : Fragment(), BackHandler {
|
|||
promptsFeature.withFeature { it.onActivityResult(requestCode, resultCode, data) }
|
||||
}
|
||||
|
||||
// This method triggers the complexity warning. However it's actually not that hard to understand.
|
||||
@SuppressWarnings("ComplexMethod")
|
||||
private fun handleToolbarItemInteraction(action: SearchAction.ToolbarMenuItemTapped) {
|
||||
val sessionUseCases = requireComponents.useCases.sessionUseCases
|
||||
Do exhaustive when (action.item) {
|
||||
ToolbarMenu.Item.Back -> sessionUseCases.goBack.invoke(getSessionById())
|
||||
ToolbarMenu.Item.Forward -> sessionUseCases.goForward.invoke(getSessionById())
|
||||
ToolbarMenu.Item.Reload -> sessionUseCases.reload.invoke(getSessionById())
|
||||
ToolbarMenu.Item.Stop -> sessionUseCases.stopLoading.invoke(getSessionById())
|
||||
ToolbarMenu.Item.Settings -> nav(
|
||||
R.id.browserFragment,
|
||||
BrowserFragmentDirections.actionBrowserFragmentToSettingsFragment()
|
||||
)
|
||||
ToolbarMenu.Item.Library -> nav(
|
||||
R.id.browserFragment,
|
||||
BrowserFragmentDirections.actionBrowserFragmentToLibraryFragment()
|
||||
)
|
||||
is ToolbarMenu.Item.RequestDesktop -> getSessionById()?.let { session ->
|
||||
sessionUseCases.requestDesktopSite.invoke(action.item.isChecked, session)
|
||||
}
|
||||
ToolbarMenu.Item.Share -> getSessionById()?.let { session ->
|
||||
session.url.apply {
|
||||
shareUrl(this)
|
||||
}
|
||||
}
|
||||
ToolbarMenu.Item.NewPrivateTab -> {
|
||||
val directions = BrowserFragmentDirections
|
||||
.actionBrowserFragmentToSearchFragment(null)
|
||||
nav(R.id.browserFragment, directions)
|
||||
(activity as HomeActivity).browsingModeManager.mode = BrowsingModeManager.Mode.Private
|
||||
}
|
||||
ToolbarMenu.Item.FindInPage -> {
|
||||
(BottomSheetBehavior.from(nestedScrollQuickAction as View) as QuickActionSheetBehavior).apply {
|
||||
state = BottomSheetBehavior.STATE_COLLAPSED
|
||||
}
|
||||
findInPageIntegration.get()?.launch()
|
||||
requireComponents.analytics.metrics.track(Event.FindInPageOpened)
|
||||
}
|
||||
ToolbarMenu.Item.ReportIssue -> getSessionById()?.let { session ->
|
||||
session.url.apply {
|
||||
val reportUrl = String.format(REPORT_SITE_ISSUE_URL, this)
|
||||
requireComponents.useCases.tabsUseCases.addTab.invoke(reportUrl)
|
||||
}
|
||||
}
|
||||
ToolbarMenu.Item.Help -> {
|
||||
requireComponents.useCases.tabsUseCases.addTab.invoke(
|
||||
SupportUtils.getSumoURLForTopic(
|
||||
requireContext(),
|
||||
SupportUtils.SumoTopic.HELP
|
||||
)
|
||||
)
|
||||
}
|
||||
ToolbarMenu.Item.NewTab -> {
|
||||
val directions = BrowserFragmentDirections
|
||||
.actionBrowserFragmentToSearchFragment(null)
|
||||
nav(R.id.browserFragment, directions)
|
||||
(activity as HomeActivity).browsingModeManager.mode =
|
||||
BrowsingModeManager.Mode.Normal
|
||||
}
|
||||
ToolbarMenu.Item.SaveToCollection -> showSaveToCollection()
|
||||
ToolbarMenu.Item.OpenInFenix -> {
|
||||
// Release the session from this view so that it can immediately be rendered by a different view
|
||||
engineView.release()
|
||||
|
||||
// Strip the CustomTabConfig to turn this Session into a regular tab and then select it
|
||||
getSessionById()?.let {
|
||||
it.customTabConfig = null
|
||||
requireComponents.core.sessionManager.select(it)
|
||||
}
|
||||
|
||||
// Switch to the actual browser which should now display our new selected session
|
||||
startActivity(Intent(context, IntentReceiverActivity::class.java).also {
|
||||
it.action = Intent.ACTION_VIEW
|
||||
it.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
})
|
||||
|
||||
// Close this activity since it is no longer displaying any session
|
||||
activity?.finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showSaveToCollection() {
|
||||
val context = context ?: return
|
||||
getSessionById()?.let {
|
||||
val tabs = it.toTab(context)
|
||||
val viewModel = activity?.run {
|
||||
ViewModelProviders.of(this).get(CreateCollectionViewModel::class.java)
|
||||
}
|
||||
viewModel?.tabs = listOf(tabs)
|
||||
val selectedSet = mutableSetOf(tabs)
|
||||
viewModel?.selectedTabs = selectedSet
|
||||
viewModel?.tabCollections = requireComponents.core.tabCollectionStorage.cachedTabCollections.reversed()
|
||||
viewModel?.saveCollectionStep =
|
||||
viewModel?.tabCollections?.getStepForCollectionsSize() ?: SaveCollectionStep.SelectCollection
|
||||
viewModel?.snackbarAnchorView = nestedScrollQuickAction
|
||||
viewModel?.previousFragmentId = R.id.browserFragment
|
||||
view?.let {
|
||||
val directions = BrowserFragmentDirections.actionBrowserFragmentToCreateCollectionFragment()
|
||||
nav(R.id.browserFragment, directions)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignSitePermissionsRules() {
|
||||
val settings = Settings.getInstance(requireContext())
|
||||
|
||||
|
@ -787,7 +664,7 @@ class BrowserFragment : Fragment(), BackHandler {
|
|||
override fun onLoadingStateChanged(session: Session, loading: Boolean) {
|
||||
if (!loading) {
|
||||
updateBookmarkState(session)
|
||||
quickActionSheetStore.dispatch(QuickActionSheetAction.BounceNeededChange)
|
||||
browserStore.dispatch(QuickActionSheetAction.BounceNeededChange)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -827,7 +704,7 @@ class BrowserFragment : Fragment(), BackHandler {
|
|||
findBookmarkJob = lifecycleScope.launch(IO) {
|
||||
val found = findBookmarkedURL(session)
|
||||
withContext(Main) {
|
||||
quickActionSheetStore.dispatch(QuickActionSheetAction.BookmarkedStateChange(found))
|
||||
browserStore.dispatch(QuickActionSheetAction.BookmarkedStateChange(found))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -835,7 +712,7 @@ class BrowserFragment : Fragment(), BackHandler {
|
|||
private fun updateAppLinksState(session: Session) {
|
||||
val url = session.url
|
||||
val appLinks = requireComponents.useCases.appLinksUseCases.appLinkRedirect
|
||||
quickActionSheetStore.dispatch(QuickActionSheetAction.AppLinkStateChange(appLinks.invoke(url).hasExternalApp()))
|
||||
browserStore.dispatch(QuickActionSheetAction.AppLinkStateChange(appLinks.invoke(url).hasExternalApp()))
|
||||
}
|
||||
|
||||
private val collectionStorageObserver = object : TabCollectionStorage.Observer {
|
||||
|
@ -852,7 +729,7 @@ class BrowserFragment : Fragment(), BackHandler {
|
|||
view?.let { view ->
|
||||
FenixSnackbar.make(view, Snackbar.LENGTH_SHORT)
|
||||
.setText(view.context.getString(R.string.create_collection_tab_saved))
|
||||
.setAnchorView(toolbarComponent.uiView.view)
|
||||
.setAnchorView(browserToolbarView.view)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.fenix.components.toolbar
|
||||
|
||||
import android.content.Context
|
||||
import mozilla.components.browser.session.Session
|
||||
import org.mozilla.fenix.browser.readermode.ReaderModeController
|
||||
import org.mozilla.fenix.components.metrics.Event
|
||||
import org.mozilla.fenix.ext.metrics
|
||||
import org.mozilla.fenix.quickactionsheet.QuickActionSheetController
|
||||
import org.mozilla.fenix.quickactionsheet.QuickActionSheetViewInteractor
|
||||
|
||||
class BrowserInteractor(
|
||||
private val context: Context,
|
||||
private val store: BrowserStore,
|
||||
private val browserToolbarController: BrowserToolbarController,
|
||||
private val quickActionSheetController: QuickActionSheetController,
|
||||
private val readerModeController: ReaderModeController,
|
||||
private val currentSession: Session
|
||||
) : BrowserToolbarViewInteractor, QuickActionSheetViewInteractor {
|
||||
|
||||
override fun onBrowserToolbarClicked() {
|
||||
browserToolbarController.handleToolbarClick()
|
||||
}
|
||||
|
||||
override fun onBrowserToolbarMenuItemTapped(item: ToolbarMenu.Item) {
|
||||
browserToolbarController.handleToolbarItemInteraction(item)
|
||||
}
|
||||
|
||||
override fun onQuickActionSheetOpened() {
|
||||
context.metrics.track(Event.QuickActionSheetOpened)
|
||||
}
|
||||
|
||||
override fun onQuickActionSheetClosed() {
|
||||
context.metrics.track(Event.QuickActionSheetClosed)
|
||||
}
|
||||
|
||||
override fun onQuickActionSheetSharePressed() {
|
||||
quickActionSheetController.handleShare()
|
||||
}
|
||||
|
||||
override fun onQuickActionSheetDownloadPressed() {
|
||||
quickActionSheetController.handleDownload()
|
||||
}
|
||||
|
||||
override fun onQuickActionSheetBookmarkPressed() {
|
||||
quickActionSheetController.handleBookmark()
|
||||
}
|
||||
|
||||
override fun onQuickActionSheetReadPressed() {
|
||||
context.metrics.track(Event.QuickActionSheetReadTapped)
|
||||
val enabled = currentSession.readerMode
|
||||
if (enabled) {
|
||||
readerModeController.hideReaderView()
|
||||
} else {
|
||||
readerModeController.showReaderView()
|
||||
}
|
||||
store.dispatch(QuickActionSheetAction.ReaderActiveStateChange(!enabled))
|
||||
}
|
||||
|
||||
override fun onQuickActionSheetOpenLinkPressed() {
|
||||
quickActionSheetController.handleOpenLink()
|
||||
}
|
||||
|
||||
override fun onQuickActionSheetAppearancePressed() {
|
||||
// TODO telemetry: https://github.com/mozilla-mobile/fenix/issues/2267
|
||||
readerModeController.showControls()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.fenix.components.toolbar
|
||||
|
||||
import mozilla.components.lib.state.Action
|
||||
import mozilla.components.lib.state.State
|
||||
import mozilla.components.lib.state.Store
|
||||
|
||||
class BrowserStore(initialState: BrowserState) :
|
||||
Store<BrowserState, BrowserAction>(initialState, ::browserStateReducer)
|
||||
|
||||
/**
|
||||
* The state for the Browser Screen
|
||||
* @property quickActionSheetState: state of the quick action sheet
|
||||
*/
|
||||
data class BrowserState(
|
||||
val quickActionSheetState: QuickActionSheetState
|
||||
) : State
|
||||
|
||||
/**
|
||||
* The state for the QuickActionSheet
|
||||
* @property readable Whether or not the current session can display a reader view
|
||||
* @property bookmarked Whether or not the current session is already bookmarked
|
||||
* @property readerActive Whether or not the current session is in reader mode
|
||||
* @property bounceNeeded Whether or not the quick action sheet should bounce
|
||||
*/
|
||||
data class QuickActionSheetState(
|
||||
val readable: Boolean,
|
||||
val bookmarked: Boolean,
|
||||
val readerActive: Boolean,
|
||||
val bounceNeeded: Boolean,
|
||||
val isAppLink: Boolean
|
||||
) : State
|
||||
|
||||
sealed class BrowserAction : Action
|
||||
|
||||
/**
|
||||
* Actions to dispatch through the [QuickActionSheetStore] to modify [QuickActionSheetState] through the reducer.
|
||||
*/
|
||||
sealed class QuickActionSheetAction : BrowserAction() {
|
||||
data class BookmarkedStateChange(val bookmarked: Boolean) : QuickActionSheetAction()
|
||||
data class ReadableStateChange(val readable: Boolean) : QuickActionSheetAction()
|
||||
data class ReaderActiveStateChange(val active: Boolean) : QuickActionSheetAction()
|
||||
data class AppLinkStateChange(val isAppLink: Boolean) : QuickActionSheetAction()
|
||||
object BounceNeededChange : QuickActionSheetAction()
|
||||
}
|
||||
|
||||
/**
|
||||
* Reducers for [BrowserStore].
|
||||
*
|
||||
* A top level reducer that receives the current [BrowserState] and an [Action] and then delegates to the proper child
|
||||
*
|
||||
*/
|
||||
fun browserStateReducer(
|
||||
state: BrowserState,
|
||||
action: BrowserAction
|
||||
): BrowserState {
|
||||
return when (action) {
|
||||
is QuickActionSheetAction -> {
|
||||
QuickActionSheetStateReducer.reduce(state, action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduces [QuickActionSheetAction]s to update [BrowserState].
|
||||
*/
|
||||
internal object QuickActionSheetStateReducer {
|
||||
fun reduce(state: BrowserState, action: QuickActionSheetAction): BrowserState {
|
||||
return when (action) {
|
||||
is QuickActionSheetAction.BookmarkedStateChange ->
|
||||
state.copy(quickActionSheetState = state.quickActionSheetState.copy(bookmarked = action.bookmarked))
|
||||
is QuickActionSheetAction.ReadableStateChange ->
|
||||
state.copy(quickActionSheetState = state.quickActionSheetState.copy(readable = action.readable))
|
||||
is QuickActionSheetAction.ReaderActiveStateChange ->
|
||||
state.copy(quickActionSheetState = state.quickActionSheetState.copy(readerActive = action.active))
|
||||
is QuickActionSheetAction.BounceNeededChange ->
|
||||
state.copy(quickActionSheetState = state.quickActionSheetState.copy(bounceNeeded = true))
|
||||
is QuickActionSheetAction.AppLinkStateChange -> {
|
||||
state.copy(quickActionSheetState = state.quickActionSheetState.copy(isAppLink = action.isAppLink))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,187 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.fenix.components.toolbar
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.core.widget.NestedScrollView
|
||||
import androidx.navigation.NavController
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import mozilla.components.browser.session.Session
|
||||
import mozilla.components.concept.engine.EngineView
|
||||
import org.mozilla.fenix.BrowsingModeManager
|
||||
import org.mozilla.fenix.HomeActivity
|
||||
import org.mozilla.fenix.IntentReceiverActivity
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.browser.BrowserFragment
|
||||
import org.mozilla.fenix.browser.BrowserFragmentDirections
|
||||
import org.mozilla.fenix.collections.CreateCollectionViewModel
|
||||
import org.mozilla.fenix.collections.getStepForCollectionsSize
|
||||
import org.mozilla.fenix.components.metrics.Event
|
||||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.ext.nav
|
||||
import org.mozilla.fenix.ext.toTab
|
||||
import org.mozilla.fenix.lib.Do
|
||||
import org.mozilla.fenix.quickactionsheet.QuickActionSheetBehavior
|
||||
import org.mozilla.fenix.settings.SupportUtils
|
||||
|
||||
/**
|
||||
* An interface that handles the view manipulation of the BrowserToolbar, triggered by the Interactor
|
||||
*/
|
||||
interface BrowserToolbarController {
|
||||
fun handleToolbarItemInteraction(item: ToolbarMenu.Item)
|
||||
fun handleToolbarClick()
|
||||
}
|
||||
|
||||
class DefaultBrowserToolbarController(
|
||||
private val context: Context,
|
||||
private val navController: NavController,
|
||||
private val findInPageLauncher: () -> Unit,
|
||||
private val nestedScrollQuickActionView: NestedScrollView,
|
||||
private val engineView: EngineView,
|
||||
private val currentSession: Session,
|
||||
private val viewModel: CreateCollectionViewModel
|
||||
) : BrowserToolbarController {
|
||||
|
||||
override fun handleToolbarClick() {
|
||||
context.components.analytics.metrics.track(
|
||||
Event.SearchBarTapped(Event.SearchBarTapped.Source.BROWSER)
|
||||
)
|
||||
navController.nav(
|
||||
R.id.browserFragment,
|
||||
BrowserFragmentDirections.actionBrowserFragmentToSearchFragment(
|
||||
currentSession.id
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@SuppressWarnings("ComplexMethod")
|
||||
override fun handleToolbarItemInteraction(item: ToolbarMenu.Item) {
|
||||
val sessionUseCases = context.components.useCases.sessionUseCases
|
||||
trackToolbarItemInteraction(item)
|
||||
|
||||
Do exhaustive when (item) {
|
||||
ToolbarMenu.Item.Back -> sessionUseCases.goBack.invoke(currentSession)
|
||||
ToolbarMenu.Item.Forward -> sessionUseCases.goForward.invoke(currentSession)
|
||||
ToolbarMenu.Item.Reload -> sessionUseCases.reload.invoke(currentSession)
|
||||
ToolbarMenu.Item.Stop -> sessionUseCases.stopLoading.invoke(currentSession)
|
||||
ToolbarMenu.Item.Settings -> navController.nav(
|
||||
R.id.browserFragment,
|
||||
BrowserFragmentDirections.actionBrowserFragmentToSettingsFragment()
|
||||
)
|
||||
ToolbarMenu.Item.Library -> navController.nav(
|
||||
R.id.browserFragment,
|
||||
BrowserFragmentDirections.actionBrowserFragmentToLibraryFragment()
|
||||
)
|
||||
is ToolbarMenu.Item.RequestDesktop -> sessionUseCases.requestDesktopSite.invoke(
|
||||
item.isChecked,
|
||||
currentSession
|
||||
)
|
||||
ToolbarMenu.Item.Share -> {
|
||||
currentSession.url.apply {
|
||||
val directions = BrowserFragmentDirections.actionBrowserFragmentToShareFragment(this)
|
||||
navController.nav(R.id.browserFragment, directions)
|
||||
}
|
||||
}
|
||||
ToolbarMenu.Item.NewPrivateTab -> {
|
||||
val directions = BrowserFragmentDirections
|
||||
.actionBrowserFragmentToSearchFragment(null)
|
||||
navController.nav(R.id.browserFragment, directions)
|
||||
(context as HomeActivity).browsingModeManager.mode = BrowsingModeManager.Mode.Private
|
||||
}
|
||||
ToolbarMenu.Item.FindInPage -> {
|
||||
(BottomSheetBehavior.from(nestedScrollQuickActionView) as QuickActionSheetBehavior).apply {
|
||||
state = BottomSheetBehavior.STATE_COLLAPSED
|
||||
}
|
||||
findInPageLauncher()
|
||||
context.components.analytics.metrics.track(Event.FindInPageOpened)
|
||||
}
|
||||
ToolbarMenu.Item.ReportIssue -> {
|
||||
currentSession.url.apply {
|
||||
val reportUrl = String.format(BrowserFragment.REPORT_SITE_ISSUE_URL, this)
|
||||
context.components.useCases.tabsUseCases.addTab.invoke(reportUrl)
|
||||
}
|
||||
}
|
||||
ToolbarMenu.Item.Help -> {
|
||||
context.components.useCases.tabsUseCases.addTab.invoke(
|
||||
SupportUtils.getSumoURLForTopic(
|
||||
context,
|
||||
SupportUtils.SumoTopic.HELP
|
||||
)
|
||||
)
|
||||
}
|
||||
ToolbarMenu.Item.NewTab -> {
|
||||
val directions = BrowserFragmentDirections
|
||||
.actionBrowserFragmentToSearchFragment(null)
|
||||
navController.nav(R.id.browserFragment, directions)
|
||||
(context as HomeActivity).browsingModeManager.mode =
|
||||
BrowsingModeManager.Mode.Normal
|
||||
}
|
||||
ToolbarMenu.Item.SaveToCollection -> {
|
||||
currentSession.let {
|
||||
val tab = it.toTab(context)
|
||||
viewModel.tabs = listOf(tab)
|
||||
val selectedSet = mutableSetOf(tab)
|
||||
viewModel.selectedTabs = selectedSet
|
||||
viewModel.tabCollections =
|
||||
context.components.core.tabCollectionStorage.cachedTabCollections.reversed()
|
||||
viewModel.saveCollectionStep = viewModel.tabCollections.getStepForCollectionsSize()
|
||||
viewModel.snackbarAnchorView = nestedScrollQuickActionView
|
||||
viewModel.previousFragmentId = R.id.browserFragment
|
||||
|
||||
val directions = BrowserFragmentDirections.actionBrowserFragmentToCreateCollectionFragment()
|
||||
navController.nav(R.id.browserFragment, directions)
|
||||
}
|
||||
}
|
||||
ToolbarMenu.Item.OpenInFenix -> {
|
||||
// Release the session from this view so that it can immediately be rendered by a different view
|
||||
engineView.release()
|
||||
|
||||
// Strip the CustomTabConfig to turn this Session into a regular tab and then select it
|
||||
currentSession.customTabConfig = null
|
||||
context.components.core.sessionManager.select(currentSession)
|
||||
|
||||
// Switch to the actual browser which should now display our new selected session
|
||||
context.startActivity(Intent(context, IntentReceiverActivity::class.java).also {
|
||||
it.action = Intent.ACTION_VIEW
|
||||
it.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
})
|
||||
|
||||
// Close this activity since it is no longer displaying any session
|
||||
(context as Activity).finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("ComplexMethod")
|
||||
private fun trackToolbarItemInteraction(item: ToolbarMenu.Item) {
|
||||
val eventItem = when (item) {
|
||||
ToolbarMenu.Item.Back -> Event.BrowserMenuItemTapped.Item.BACK
|
||||
ToolbarMenu.Item.Forward -> Event.BrowserMenuItemTapped.Item.FORWARD
|
||||
ToolbarMenu.Item.Reload -> Event.BrowserMenuItemTapped.Item.RELOAD
|
||||
ToolbarMenu.Item.Stop -> Event.BrowserMenuItemTapped.Item.STOP
|
||||
ToolbarMenu.Item.Settings -> Event.BrowserMenuItemTapped.Item.SETTINGS
|
||||
ToolbarMenu.Item.Library -> Event.BrowserMenuItemTapped.Item.LIBRARY
|
||||
is ToolbarMenu.Item.RequestDesktop ->
|
||||
if (item.isChecked) {
|
||||
Event.BrowserMenuItemTapped.Item.DESKTOP_VIEW_ON
|
||||
} else {
|
||||
Event.BrowserMenuItemTapped.Item.DESKTOP_VIEW_OFF
|
||||
}
|
||||
|
||||
ToolbarMenu.Item.NewPrivateTab -> Event.BrowserMenuItemTapped.Item.NEW_PRIVATE_TAB
|
||||
ToolbarMenu.Item.FindInPage -> Event.BrowserMenuItemTapped.Item.FIND_IN_PAGE
|
||||
ToolbarMenu.Item.ReportIssue -> Event.BrowserMenuItemTapped.Item.REPORT_SITE_ISSUE
|
||||
ToolbarMenu.Item.Help -> Event.BrowserMenuItemTapped.Item.HELP
|
||||
ToolbarMenu.Item.NewTab -> Event.BrowserMenuItemTapped.Item.NEW_TAB
|
||||
ToolbarMenu.Item.OpenInFenix -> Event.BrowserMenuItemTapped.Item.OPEN_IN_FENIX
|
||||
ToolbarMenu.Item.Share -> Event.BrowserMenuItemTapped.Item.SHARE
|
||||
ToolbarMenu.Item.SaveToCollection -> Event.BrowserMenuItemTapped.Item.SAVE_TO_COLLECTION
|
||||
}
|
||||
|
||||
context.components.analytics.metrics.track(Event.BrowserMenuItemTapped(eventItem))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,124 @@
|
|||
package org.mozilla.fenix.components.toolbar
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.content.ContextCompat
|
||||
import kotlinx.android.extensions.LayoutContainer
|
||||
import mozilla.components.browser.domains.autocomplete.ShippedDomainsProvider
|
||||
import mozilla.components.browser.session.Session
|
||||
import mozilla.components.browser.toolbar.BrowserToolbar
|
||||
import mozilla.components.support.ktx.android.util.dpToFloat
|
||||
import mozilla.components.support.ktx.android.util.dpToPx
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.ThemeManager
|
||||
import org.mozilla.fenix.customtabs.CustomTabToolbarMenu
|
||||
import org.mozilla.fenix.ext.components
|
||||
|
||||
interface BrowserToolbarViewInteractor {
|
||||
fun onBrowserToolbarClicked()
|
||||
fun onBrowserToolbarMenuItemTapped(item: ToolbarMenu.Item)
|
||||
}
|
||||
|
||||
class BrowserToolbarView(
|
||||
private val container: ViewGroup,
|
||||
private val interactor: BrowserToolbarViewInteractor,
|
||||
private val currentSession: Session
|
||||
) : LayoutContainer {
|
||||
|
||||
override val containerView: View?
|
||||
get() = container
|
||||
|
||||
private val urlBackground = LayoutInflater.from(container.context)
|
||||
.inflate(R.layout.layout_url_background, container, false)
|
||||
|
||||
val view: BrowserToolbar = LayoutInflater.from(container.context)
|
||||
.inflate(R.layout.component_search, container, true)
|
||||
.findViewById(R.id.toolbar)
|
||||
|
||||
val toolbarIntegration: ToolbarIntegration
|
||||
|
||||
init {
|
||||
// We need access to the customSessionId. We don't directly have access since we aren't passing session id in
|
||||
// So we need to access it through the store...?
|
||||
|
||||
with(container.context) {
|
||||
val sessionManager = components.core.sessionManager
|
||||
val isCustomTabSession = currentSession.isCustomTabSession()
|
||||
|
||||
view.apply {
|
||||
elevation = TOOLBAR_ELEVATION.dpToFloat(resources.displayMetrics)
|
||||
|
||||
onUrlClicked = {
|
||||
interactor.onBrowserToolbarClicked()
|
||||
false
|
||||
}
|
||||
|
||||
browserActionMargin = browserActionMarginDp.dpToPx(resources.displayMetrics)
|
||||
|
||||
urlBoxView = if (isCustomTabSession) null else urlBackground
|
||||
progressBarGravity = if (isCustomTabSession) PROGRESS_BOTTOM else PROGRESS_TOP
|
||||
|
||||
textColor = ContextCompat.getColor(context, R.color.photonGrey30)
|
||||
|
||||
hint = context.getString(R.string.search_hint)
|
||||
|
||||
suggestionBackgroundColor = ContextCompat.getColor(
|
||||
container.context,
|
||||
R.color.suggestion_highlight_color
|
||||
)
|
||||
|
||||
textColor = ContextCompat.getColor(
|
||||
container.context,
|
||||
ThemeManager.resolveAttribute(R.attr.primaryText, container.context)
|
||||
)
|
||||
|
||||
hintColor = ContextCompat.getColor(
|
||||
container.context,
|
||||
ThemeManager.resolveAttribute(R.attr.secondaryText, container.context)
|
||||
)
|
||||
}
|
||||
|
||||
val menuToolbar = if (isCustomTabSession) {
|
||||
CustomTabToolbarMenu(
|
||||
this,
|
||||
sessionManager,
|
||||
currentSession.id,
|
||||
onItemTapped = {
|
||||
interactor.onBrowserToolbarMenuItemTapped(it)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
DefaultToolbarMenu(
|
||||
this,
|
||||
hasAccountProblem = components.backgroundServices.accountManager.accountNeedsReauth(),
|
||||
requestDesktopStateProvider = { currentSession.desktopMode },
|
||||
onItemTapped = { interactor.onBrowserToolbarMenuItemTapped(it) }
|
||||
)
|
||||
}
|
||||
|
||||
toolbarIntegration = ToolbarIntegration(
|
||||
this,
|
||||
view,
|
||||
container,
|
||||
menuToolbar,
|
||||
ShippedDomainsProvider().also { it.initialize(this) },
|
||||
components.core.historyStorage,
|
||||
components.core.sessionManager,
|
||||
currentSession.id,
|
||||
currentSession.private
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun update(state: BrowserState) {
|
||||
// TODO Leaving this as a stub for now since we don't actually have anything to update ever...?
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TOOLBAR_ELEVATION = 16
|
||||
private const val PROGRESS_BOTTOM = 0
|
||||
private const val PROGRESS_TOP = 1
|
||||
const val browserActionMarginDp = 8
|
||||
}
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.fenix.components.toolbar
|
||||
|
||||
import android.view.ViewGroup
|
||||
import kotlinx.android.synthetic.main.component_search.*
|
||||
import org.mozilla.fenix.mvi.Action
|
||||
import org.mozilla.fenix.mvi.ActionBusFactory
|
||||
import org.mozilla.fenix.mvi.Change
|
||||
import org.mozilla.fenix.mvi.Reducer
|
||||
import org.mozilla.fenix.mvi.UIComponent
|
||||
import org.mozilla.fenix.mvi.UIComponentViewModelBase
|
||||
import org.mozilla.fenix.mvi.UIComponentViewModelProvider
|
||||
import org.mozilla.fenix.mvi.ViewState
|
||||
|
||||
class ToolbarComponent(
|
||||
private val container: ViewGroup,
|
||||
bus: ActionBusFactory,
|
||||
private val sessionId: String?,
|
||||
private val isPrivate: Boolean,
|
||||
viewModelProvider: UIComponentViewModelProvider<SearchState, SearchChange>
|
||||
) :
|
||||
UIComponent<SearchState, SearchAction, SearchChange>(
|
||||
bus.getManagedEmitter(SearchAction::class.java),
|
||||
bus.getSafeManagedObservable(SearchChange::class.java),
|
||||
viewModelProvider
|
||||
) {
|
||||
|
||||
override fun initView() = ToolbarUIView(
|
||||
sessionId,
|
||||
isPrivate,
|
||||
container,
|
||||
actionEmitter,
|
||||
changesObservable
|
||||
)
|
||||
|
||||
init {
|
||||
bind()
|
||||
}
|
||||
|
||||
fun setOnSiteSecurityClickedListener(listener: () -> Unit) {
|
||||
uiView.toolbar.setOnSiteSecurityClickedListener(listener)
|
||||
}
|
||||
}
|
||||
|
||||
class SearchState : ViewState
|
||||
|
||||
sealed class SearchAction : Action {
|
||||
object ToolbarClicked : SearchAction()
|
||||
data class ToolbarMenuItemTapped(val item: ToolbarMenu.Item) : SearchAction()
|
||||
}
|
||||
|
||||
sealed class SearchChange : Change
|
||||
|
||||
class ToolbarViewModel(initialState: SearchState) :
|
||||
UIComponentViewModelBase<SearchState, SearchChange>(initialState, reducer) {
|
||||
|
||||
companion object {
|
||||
val reducer: Reducer<SearchState, SearchChange> = { state, _ -> state }
|
||||
}
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.fenix.components.toolbar
|
||||
|
||||
import org.mozilla.fenix.components.metrics.Event
|
||||
import org.mozilla.fenix.components.metrics.MetricController
|
||||
|
||||
// This method triggers the complexity warning. However it's actually not that hard to understand.
|
||||
@SuppressWarnings("ComplexMethod")
|
||||
fun trackToolbarItemInteraction(metrics: MetricController, action: SearchAction.ToolbarMenuItemTapped) {
|
||||
val item = when (action.item) {
|
||||
ToolbarMenu.Item.Back -> Event.BrowserMenuItemTapped.Item.BACK
|
||||
ToolbarMenu.Item.Forward -> Event.BrowserMenuItemTapped.Item.FORWARD
|
||||
ToolbarMenu.Item.Reload -> Event.BrowserMenuItemTapped.Item.RELOAD
|
||||
ToolbarMenu.Item.Stop -> Event.BrowserMenuItemTapped.Item.STOP
|
||||
ToolbarMenu.Item.Settings -> Event.BrowserMenuItemTapped.Item.SETTINGS
|
||||
ToolbarMenu.Item.Library -> Event.BrowserMenuItemTapped.Item.LIBRARY
|
||||
is ToolbarMenu.Item.RequestDesktop -> if (action.item.isChecked) {
|
||||
Event.BrowserMenuItemTapped.Item.DESKTOP_VIEW_ON
|
||||
} else {
|
||||
Event.BrowserMenuItemTapped.Item.DESKTOP_VIEW_OFF
|
||||
}
|
||||
ToolbarMenu.Item.NewPrivateTab -> Event.BrowserMenuItemTapped.Item.NEW_PRIVATE_TAB
|
||||
ToolbarMenu.Item.FindInPage -> Event.BrowserMenuItemTapped.Item.FIND_IN_PAGE
|
||||
ToolbarMenu.Item.ReportIssue -> Event.BrowserMenuItemTapped.Item.REPORT_SITE_ISSUE
|
||||
ToolbarMenu.Item.Help -> Event.BrowserMenuItemTapped.Item.HELP
|
||||
ToolbarMenu.Item.NewTab -> Event.BrowserMenuItemTapped.Item.NEW_TAB
|
||||
ToolbarMenu.Item.OpenInFenix -> Event.BrowserMenuItemTapped.Item.OPEN_IN_FENIX
|
||||
ToolbarMenu.Item.Share -> Event.BrowserMenuItemTapped.Item.SHARE
|
||||
ToolbarMenu.Item.SaveToCollection -> Event.BrowserMenuItemTapped.Item.SAVE_TO_COLLECTION
|
||||
}
|
||||
|
||||
metrics.track(Event.BrowserMenuItemTapped(item))
|
||||
}
|
|
@ -1,107 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.fenix.components.toolbar
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.content.ContextCompat
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Observer
|
||||
import io.reactivex.functions.Consumer
|
||||
import mozilla.components.browser.domains.autocomplete.ShippedDomainsProvider
|
||||
import mozilla.components.browser.toolbar.BrowserToolbar
|
||||
import mozilla.components.support.ktx.android.util.dpToFloat
|
||||
import mozilla.components.support.ktx.android.util.dpToPx
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.customtabs.CustomTabToolbarMenu
|
||||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.ext.getColorFromAttr
|
||||
import org.mozilla.fenix.mvi.UIView
|
||||
|
||||
class ToolbarUIView(
|
||||
sessionId: String?,
|
||||
isPrivate: Boolean,
|
||||
container: ViewGroup,
|
||||
actionEmitter: Observer<SearchAction>,
|
||||
changesObservable: Observable<SearchChange>
|
||||
) :
|
||||
UIView<SearchState, SearchAction, SearchChange>(container, actionEmitter, changesObservable) {
|
||||
|
||||
val toolbarIntegration: ToolbarIntegration
|
||||
|
||||
override val view: BrowserToolbar = LayoutInflater.from(container.context)
|
||||
.inflate(R.layout.component_search, container, true)
|
||||
.findViewById(R.id.toolbar)
|
||||
|
||||
private val urlBackground = LayoutInflater.from(container.context)
|
||||
.inflate(R.layout.layout_url_background, container, false)
|
||||
|
||||
init {
|
||||
val sessionManager = view.context.components.core.sessionManager
|
||||
val session = sessionId?.let { sessionManager.findSessionById(it) }
|
||||
?: sessionManager.selectedSession
|
||||
val isCustomTabSession = session?.isCustomTabSession() == true
|
||||
|
||||
view.apply {
|
||||
elevation = TOOLBAR_ELEVATION.dpToFloat(resources.displayMetrics)
|
||||
|
||||
onUrlClicked = {
|
||||
actionEmitter.onNext(SearchAction.ToolbarClicked)
|
||||
false
|
||||
}
|
||||
|
||||
browserActionMargin = browserActionMarginDp.dpToPx(resources.displayMetrics)
|
||||
|
||||
urlBoxView = if (isCustomTabSession) null else urlBackground
|
||||
progressBarGravity = if (isCustomTabSession) PROGRESS_BOTTOM else PROGRESS_TOP
|
||||
|
||||
textColor = ContextCompat.getColor(context, R.color.photonGrey30)
|
||||
|
||||
hint = context.getString(R.string.search_hint)
|
||||
|
||||
suggestionBackgroundColor = ContextCompat.getColor(context, R.color.suggestion_highlight_color)
|
||||
textColor = context.getColorFromAttr(R.attr.primaryText)
|
||||
hintColor = context.getColorFromAttr(R.attr.secondaryText)
|
||||
}
|
||||
|
||||
with(view.context) {
|
||||
val menuToolbar = if (isCustomTabSession) {
|
||||
CustomTabToolbarMenu(
|
||||
this,
|
||||
sessionManager,
|
||||
sessionId,
|
||||
onItemTapped = { actionEmitter.onNext(SearchAction.ToolbarMenuItemTapped(it)) }
|
||||
)
|
||||
} else {
|
||||
DefaultToolbarMenu(this,
|
||||
hasAccountProblem = components.backgroundServices.accountManager.accountNeedsReauth(),
|
||||
requestDesktopStateProvider = { session?.desktopMode ?: false },
|
||||
onItemTapped = { actionEmitter.onNext(SearchAction.ToolbarMenuItemTapped(it)) }
|
||||
)
|
||||
}
|
||||
|
||||
toolbarIntegration = ToolbarIntegration(
|
||||
this,
|
||||
view,
|
||||
container,
|
||||
menuToolbar,
|
||||
ShippedDomainsProvider().also { it.initialize(this) },
|
||||
components.core.historyStorage,
|
||||
components.core.sessionManager,
|
||||
sessionId,
|
||||
isPrivate
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateView() = Consumer<SearchState> {}
|
||||
|
||||
companion object {
|
||||
private const val TOOLBAR_ELEVATION = 16
|
||||
private const val PROGRESS_BOTTOM = 0
|
||||
private const val PROGRESS_TOP = 1
|
||||
const val browserActionMarginDp = 8
|
||||
}
|
||||
}
|
|
@ -1,89 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.fenix.quickactionsheet
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.annotation.CallSuper
|
||||
import mozilla.components.browser.session.Session
|
||||
import mozilla.components.feature.app.links.AppLinksUseCases
|
||||
import org.mozilla.fenix.browser.readermode.ReaderModeController
|
||||
import org.mozilla.fenix.components.metrics.Event
|
||||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.ext.metrics
|
||||
import org.mozilla.fenix.utils.ItsNotBrokenSnack
|
||||
|
||||
/**
|
||||
* Interactor for the QuickActionSheet
|
||||
*/
|
||||
class QuickActionInteractor(
|
||||
private val context: Context,
|
||||
private val readerModeController: ReaderModeController,
|
||||
private val quickActionStore: QuickActionSheetStore,
|
||||
private val shareUrl: (String) -> Unit,
|
||||
private val bookmarkTapped: (Session) -> Unit,
|
||||
private val appLinksUseCases: AppLinksUseCases
|
||||
) : QuickActionSheetInteractor {
|
||||
|
||||
private val selectedSession
|
||||
inline get() = context.components.core.sessionManager.selectedSession
|
||||
|
||||
@CallSuper
|
||||
override fun onOpened() {
|
||||
context.metrics.track(Event.QuickActionSheetOpened)
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
override fun onClosed() {
|
||||
context.metrics.track(Event.QuickActionSheetClosed)
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
override fun onSharedPressed() {
|
||||
context.metrics.track(Event.QuickActionSheetShareTapped)
|
||||
selectedSession?.url?.let(shareUrl)
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
override fun onDownloadsPressed() {
|
||||
context.metrics.track(Event.QuickActionSheetDownloadTapped)
|
||||
ItsNotBrokenSnack(context).showSnackbar(issueNumber = "348")
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
override fun onBookmarkPressed() {
|
||||
context.metrics.track(Event.QuickActionSheetBookmarkTapped)
|
||||
selectedSession?.let(bookmarkTapped)
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
override fun onReadPressed() {
|
||||
context.metrics.track(Event.QuickActionSheetReadTapped)
|
||||
val enabled = selectedSession?.readerMode ?: false
|
||||
if (enabled) {
|
||||
readerModeController.hideReaderView()
|
||||
} else {
|
||||
readerModeController.showReaderView()
|
||||
}
|
||||
quickActionStore.dispatch(QuickActionSheetAction.ReaderActiveStateChange(!enabled))
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
override fun onOpenAppLinkPressed() {
|
||||
val getRedirect = appLinksUseCases.appLinkRedirect
|
||||
val redirect = selectedSession?.let {
|
||||
getRedirect.invoke(it.url)
|
||||
} ?: return
|
||||
|
||||
redirect.appIntent?.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
appLinksUseCases.openAppLink.invoke(redirect)
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
override fun onAppearancePressed() {
|
||||
// TODO telemetry: https://github.com/mozilla-mobile/fenix/issues/2267
|
||||
readerModeController.showControls()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.fenix.quickactionsheet
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.navigation.NavController
|
||||
import mozilla.components.browser.session.Session
|
||||
import mozilla.components.feature.app.links.AppLinksUseCases
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.browser.BrowserFragmentDirections
|
||||
import org.mozilla.fenix.components.metrics.Event
|
||||
import org.mozilla.fenix.ext.metrics
|
||||
import org.mozilla.fenix.ext.nav
|
||||
import org.mozilla.fenix.utils.ItsNotBrokenSnack
|
||||
|
||||
/**
|
||||
* An interface that handles the view manipulation of the QuickActionSheet, triggered by the Interactor
|
||||
*/
|
||||
interface QuickActionSheetController {
|
||||
fun handleShare()
|
||||
fun handleDownload()
|
||||
fun handleBookmark()
|
||||
fun handleOpenLink()
|
||||
}
|
||||
|
||||
class DefaultQuickActionSheetController(
|
||||
private val context: Context,
|
||||
private val navController: NavController,
|
||||
private val currentSession: Session,
|
||||
private val appLinksUseCases: AppLinksUseCases,
|
||||
private val bookmarkTapped: (Session) -> Unit
|
||||
) : QuickActionSheetController {
|
||||
|
||||
override fun handleShare() {
|
||||
context.metrics.track(Event.QuickActionSheetShareTapped)
|
||||
currentSession.url.let {
|
||||
val directions = BrowserFragmentDirections.actionBrowserFragmentToShareFragment(it)
|
||||
navController.nav(R.id.browserFragment, directions)
|
||||
}
|
||||
}
|
||||
|
||||
override fun handleDownload() {
|
||||
context.metrics.track(Event.QuickActionSheetDownloadTapped)
|
||||
ItsNotBrokenSnack(context).showSnackbar(issueNumber = "348")
|
||||
}
|
||||
|
||||
override fun handleBookmark() {
|
||||
context.metrics.track(Event.QuickActionSheetBookmarkTapped)
|
||||
bookmarkTapped(currentSession)
|
||||
}
|
||||
|
||||
override fun handleOpenLink() {
|
||||
val getRedirect = appLinksUseCases.appLinkRedirect
|
||||
val redirect = currentSession.let {
|
||||
getRedirect.invoke(it.url)
|
||||
}
|
||||
|
||||
redirect.appIntent?.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
appLinksUseCases.openAppLink.invoke(redirect)
|
||||
}
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.fenix.quickactionsheet
|
||||
|
||||
import mozilla.components.lib.state.Action
|
||||
import mozilla.components.lib.state.State
|
||||
import mozilla.components.lib.state.Store
|
||||
|
||||
/**
|
||||
* The [Store] for holding the [QuickActionSheetState] and applying [QuickActionSheetAction]s.
|
||||
*/
|
||||
class QuickActionSheetStore(initialState: QuickActionSheetState) :
|
||||
Store<QuickActionSheetState, QuickActionSheetAction>(initialState, ::quickActionSheetStateReducer)
|
||||
|
||||
/**
|
||||
* The state for the QuickActionSheet found in the Browser Fragment
|
||||
* @property readable Whether or not the current session can display a reader view
|
||||
* @property bookmarked Whether or not the current session is already bookmarked
|
||||
* @property readerActive Whether or not the current session is in reader mode
|
||||
* @property bounceNeeded Whether or not the quick action sheet should bounce
|
||||
*/
|
||||
data class QuickActionSheetState(
|
||||
val readable: Boolean,
|
||||
val bookmarked: Boolean,
|
||||
val readerActive: Boolean,
|
||||
val bounceNeeded: Boolean,
|
||||
val isAppLink: Boolean
|
||||
) : State
|
||||
|
||||
/**
|
||||
* Actions to dispatch through the [QuickActionSheetStore] to modify [QuickActionSheetState] through the reducer.
|
||||
*/
|
||||
sealed class QuickActionSheetAction : Action {
|
||||
data class BookmarkedStateChange(val bookmarked: Boolean) : QuickActionSheetAction()
|
||||
data class ReadableStateChange(val readable: Boolean) : QuickActionSheetAction()
|
||||
data class ReaderActiveStateChange(val active: Boolean) : QuickActionSheetAction()
|
||||
data class AppLinkStateChange(val isAppLink: Boolean) : QuickActionSheetAction()
|
||||
object BounceNeededChange : QuickActionSheetAction()
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduces [QuickActionSheetAction]s to update [QuickActionSheetState].
|
||||
*/
|
||||
fun quickActionSheetStateReducer(
|
||||
state: QuickActionSheetState,
|
||||
action: QuickActionSheetAction
|
||||
): QuickActionSheetState {
|
||||
return when (action) {
|
||||
is QuickActionSheetAction.BookmarkedStateChange ->
|
||||
state.copy(bookmarked = action.bookmarked)
|
||||
is QuickActionSheetAction.ReadableStateChange ->
|
||||
state.copy(readable = action.readable)
|
||||
is QuickActionSheetAction.ReaderActiveStateChange ->
|
||||
state.copy(readerActive = action.active)
|
||||
is QuickActionSheetAction.BounceNeededChange ->
|
||||
state.copy(bounceNeeded = true)
|
||||
is QuickActionSheetAction.AppLinkStateChange -> {
|
||||
state.copy(isAppLink = action.isAppLink)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,24 +18,25 @@ import kotlinx.android.synthetic.main.layout_quick_action_sheet.*
|
|||
import kotlinx.android.synthetic.main.layout_quick_action_sheet.view.*
|
||||
import mozilla.components.support.ktx.android.view.putCompoundDrawablesRelativeWithIntrinsicBounds
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.components.toolbar.BrowserState
|
||||
import org.mozilla.fenix.utils.Settings
|
||||
|
||||
interface QuickActionSheetInteractor {
|
||||
fun onOpened()
|
||||
fun onClosed()
|
||||
fun onSharedPressed()
|
||||
fun onDownloadsPressed()
|
||||
fun onBookmarkPressed()
|
||||
fun onReadPressed()
|
||||
fun onAppearancePressed()
|
||||
fun onOpenAppLinkPressed()
|
||||
interface QuickActionSheetViewInteractor {
|
||||
fun onQuickActionSheetOpened()
|
||||
fun onQuickActionSheetClosed()
|
||||
fun onQuickActionSheetSharePressed()
|
||||
fun onQuickActionSheetDownloadPressed()
|
||||
fun onQuickActionSheetBookmarkPressed()
|
||||
fun onQuickActionSheetReadPressed()
|
||||
fun onQuickActionSheetAppearancePressed()
|
||||
fun onQuickActionSheetOpenLinkPressed()
|
||||
}
|
||||
/**
|
||||
* View for the quick action sheet that slides out from the toolbar.
|
||||
*/
|
||||
class QuickActionView(
|
||||
class QuickActionSheetView(
|
||||
override val containerView: ViewGroup,
|
||||
private val interactor: QuickActionSheetInteractor
|
||||
private val interactor: QuickActionSheetViewInteractor
|
||||
) : LayoutContainer, View.OnClickListener {
|
||||
|
||||
val view: NestedScrollView = LayoutInflater.from(containerView.context)
|
||||
|
@ -51,9 +52,9 @@ class QuickActionView(
|
|||
updateImportantForAccessibility(state)
|
||||
|
||||
if (state == BottomSheetBehavior.STATE_EXPANDED) {
|
||||
interactor.onOpened()
|
||||
interactor.onQuickActionSheetOpened()
|
||||
} else if (state == BottomSheetBehavior.STATE_COLLAPSED) {
|
||||
interactor.onClosed()
|
||||
interactor.onQuickActionSheetClosed()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -77,12 +78,12 @@ class QuickActionView(
|
|||
*/
|
||||
override fun onClick(button: View) {
|
||||
when (button.id) {
|
||||
R.id.quick_action_share -> interactor.onSharedPressed()
|
||||
R.id.quick_action_downloads -> interactor.onDownloadsPressed()
|
||||
R.id.quick_action_bookmark -> interactor.onBookmarkPressed()
|
||||
R.id.quick_action_read -> interactor.onReadPressed()
|
||||
R.id.quick_action_appearance -> interactor.onAppearancePressed()
|
||||
R.id.quick_action_open_app_link -> interactor.onOpenAppLinkPressed()
|
||||
R.id.quick_action_share -> interactor.onQuickActionSheetSharePressed()
|
||||
R.id.quick_action_downloads -> interactor.onQuickActionSheetDownloadPressed()
|
||||
R.id.quick_action_bookmark -> interactor.onQuickActionSheetBookmarkPressed()
|
||||
R.id.quick_action_read -> interactor.onQuickActionSheetReadPressed()
|
||||
R.id.quick_action_appearance -> interactor.onQuickActionSheetAppearancePressed()
|
||||
R.id.quick_action_open_app_link -> interactor.onQuickActionSheetOpenLinkPressed()
|
||||
else -> return
|
||||
}
|
||||
quickActionSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
|
||||
|
@ -108,27 +109,32 @@ class QuickActionView(
|
|||
}
|
||||
}
|
||||
|
||||
fun update(state: QuickActionSheetState) {
|
||||
view.quick_action_read.isVisible = state.readable
|
||||
view.quick_action_read.isSelected = state.readerActive
|
||||
fun update(state: BrowserState) {
|
||||
val quickActionSheetState = state.quickActionSheetState
|
||||
view.quick_action_read.isVisible = quickActionSheetState.readable
|
||||
view.quick_action_read.isSelected = quickActionSheetState.readerActive
|
||||
view.quick_action_read.text = view.context.getString(
|
||||
if (state.readerActive) R.string.quick_action_read_close else R.string.quick_action_read
|
||||
if (quickActionSheetState.readerActive) R.string.quick_action_read_close else R.string.quick_action_read
|
||||
)
|
||||
notifyReaderModeButton(state.readable)
|
||||
notifyReaderModeButton(quickActionSheetState.readable)
|
||||
|
||||
view.quick_action_appearance.isVisible = state.readerActive
|
||||
view.quick_action_appearance.isVisible = quickActionSheetState.readerActive
|
||||
|
||||
view.quick_action_bookmark.isSelected = state.bookmarked
|
||||
view.quick_action_bookmark.isSelected = quickActionSheetState.bookmarked
|
||||
view.quick_action_bookmark.text = view.context.getString(
|
||||
if (state.bookmarked) R.string.quick_action_bookmark_edit else R.string.quick_action_bookmark
|
||||
if (quickActionSheetState.bookmarked) {
|
||||
R.string.quick_action_bookmark_edit
|
||||
} else {
|
||||
R.string.quick_action_bookmark
|
||||
}
|
||||
)
|
||||
|
||||
if (state.bounceNeeded && Settings.getInstance(view.context).shouldAutoBounceQuickActionSheet) {
|
||||
if (quickActionSheetState.bounceNeeded && Settings.getInstance(view.context).shouldAutoBounceQuickActionSheet) {
|
||||
quickActionSheet.bounceSheet()
|
||||
}
|
||||
|
||||
view.quick_action_open_app_link.apply {
|
||||
visibility = if (state.isAppLink) View.VISIBLE else View.GONE
|
||||
visibility = if (quickActionSheetState.isAppLink) View.VISIBLE else View.GONE
|
||||
}
|
||||
}
|
||||
|
|
@ -21,24 +21,24 @@ import org.mozilla.fenix.search.SearchState
|
|||
|
||||
/**
|
||||
* Interface for the Toolbar Interactor. This interface is implemented by objects that want
|
||||
* to respond to user interaction on the ToolbarView
|
||||
* to respond to user interaction on the [BrowserToolbarView]
|
||||
*/
|
||||
interface ToolbarInteractor {
|
||||
|
||||
/**
|
||||
* Called when a user hits the return key while ToolbarView has focus.
|
||||
* @param url the text inside the ToolbarView when committed
|
||||
* Called when a user hits the return key while [BrowserToolbarView] has focus.
|
||||
* @param url the text inside the [BrowserToolbarView] when committed
|
||||
*/
|
||||
fun onUrlCommitted(url: String)
|
||||
|
||||
/**
|
||||
* Called when a removes focus from the ToolbarView
|
||||
* Called when a user removes focus from the [BrowserToolbarView]
|
||||
*/
|
||||
fun onEditingCanceled()
|
||||
|
||||
/**
|
||||
* Called whenever the text inside the ToolbarView changes
|
||||
* @param text the current text displayed by ToolbarView
|
||||
* Called whenever the text inside the [BrowserToolbarView] changes
|
||||
* @param text the current text displayed by [BrowserToolbarView]
|
||||
*/
|
||||
fun onTextChanged(text: String)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,258 @@
|
|||
package org.mozilla.fenix.components.toolbar
|
||||
|
||||
import android.content.Context
|
||||
import io.mockk.Runs
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import mozilla.components.browser.session.Session
|
||||
import org.junit.Test
|
||||
|
||||
import org.mozilla.fenix.browser.readermode.ReaderModeController
|
||||
import org.mozilla.fenix.components.metrics.Event
|
||||
import org.mozilla.fenix.components.metrics.MetricController
|
||||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.ext.metrics
|
||||
import org.mozilla.fenix.quickactionsheet.QuickActionSheetController
|
||||
|
||||
class BrowserInteractorTest {
|
||||
|
||||
@Test
|
||||
fun onBrowserToolbarClicked() {
|
||||
val context: Context = mockk()
|
||||
val browserToolbarController: BrowserToolbarController = mockk(relaxed = true)
|
||||
|
||||
val interactor = BrowserInteractor(
|
||||
context,
|
||||
mockk(),
|
||||
browserToolbarController,
|
||||
mockk(),
|
||||
mockk(),
|
||||
mockk()
|
||||
)
|
||||
|
||||
interactor.onBrowserToolbarClicked()
|
||||
|
||||
verify { browserToolbarController.handleToolbarClick() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onBrowserToolbarMenuItemTapped() {
|
||||
val context: Context = mockk()
|
||||
val browserToolbarController: BrowserToolbarController = mockk(relaxed = true)
|
||||
val item: ToolbarMenu.Item = mockk()
|
||||
|
||||
val interactor = BrowserInteractor(
|
||||
context,
|
||||
mockk(),
|
||||
browserToolbarController,
|
||||
mockk(),
|
||||
mockk(),
|
||||
mockk()
|
||||
)
|
||||
|
||||
interactor.onBrowserToolbarMenuItemTapped(item)
|
||||
|
||||
verify { browserToolbarController.handleToolbarItemInteraction(item) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onQuickActionSheetOpened() {
|
||||
val context: Context = mockk()
|
||||
val metrics: MetricController = mockk()
|
||||
val interactor = BrowserInteractor(
|
||||
context,
|
||||
mockk(),
|
||||
mockk(),
|
||||
mockk(),
|
||||
mockk(),
|
||||
mockk()
|
||||
)
|
||||
|
||||
every { context.metrics } returns metrics
|
||||
every { metrics.track(Event.QuickActionSheetOpened) } just Runs
|
||||
|
||||
interactor.onQuickActionSheetOpened()
|
||||
|
||||
verify { metrics.track(Event.QuickActionSheetOpened) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onQuickActionSheetClosed() {
|
||||
val context: Context = mockk()
|
||||
val metrics: MetricController = mockk()
|
||||
val interactor = BrowserInteractor(
|
||||
context,
|
||||
mockk(),
|
||||
mockk(),
|
||||
mockk(),
|
||||
mockk(),
|
||||
mockk()
|
||||
)
|
||||
|
||||
every { context.metrics } returns metrics
|
||||
every { metrics.track(Event.QuickActionSheetClosed) } just Runs
|
||||
|
||||
interactor.onQuickActionSheetClosed()
|
||||
|
||||
verify { metrics.track(Event.QuickActionSheetClosed) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onQuickActionSheetSharePressed() {
|
||||
val context: Context = mockk()
|
||||
val session: Session = mockk()
|
||||
val quickActionSheetController: QuickActionSheetController = mockk(relaxed = true)
|
||||
|
||||
val interactor = BrowserInteractor(
|
||||
context,
|
||||
mockk(),
|
||||
mockk(),
|
||||
quickActionSheetController,
|
||||
mockk(),
|
||||
session
|
||||
)
|
||||
|
||||
interactor.onQuickActionSheetSharePressed()
|
||||
|
||||
verify { quickActionSheetController.handleShare() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onQuickActionSheetDownloadPressed() {
|
||||
val context: Context = mockk()
|
||||
val metrics: MetricController = mockk()
|
||||
val quickActionSheetController: QuickActionSheetController = mockk(relaxed = true)
|
||||
|
||||
val interactor = BrowserInteractor(
|
||||
context,
|
||||
mockk(),
|
||||
mockk(),
|
||||
quickActionSheetController,
|
||||
mockk(),
|
||||
mockk()
|
||||
)
|
||||
|
||||
every { context.metrics } returns metrics
|
||||
every { metrics.track(Event.QuickActionSheetDownloadTapped) } just Runs
|
||||
|
||||
interactor.onQuickActionSheetDownloadPressed()
|
||||
|
||||
verify { quickActionSheetController.handleDownload() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onQuickActionSheetBookmarkPressed() {
|
||||
val context: Context = mockk()
|
||||
val session: Session = mockk()
|
||||
val quickActionSheetController: QuickActionSheetController = mockk(relaxed = true)
|
||||
|
||||
val interactor = BrowserInteractor(
|
||||
context,
|
||||
mockk(),
|
||||
mockk(),
|
||||
quickActionSheetController,
|
||||
mockk(),
|
||||
session
|
||||
)
|
||||
|
||||
interactor.onQuickActionSheetBookmarkPressed()
|
||||
|
||||
verify { quickActionSheetController.handleBookmark() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onQuickActionSheetReadPressed() {
|
||||
val context: Context = mockk()
|
||||
val metrics: MetricController = mockk()
|
||||
val session: Session = mockk()
|
||||
val readerModeController: ReaderModeController = mockk(relaxed = true)
|
||||
val browserStore: BrowserStore = mockk(relaxed = true)
|
||||
val interactor = BrowserInteractor(
|
||||
context,
|
||||
browserStore,
|
||||
mockk(),
|
||||
mockk(),
|
||||
readerModeController,
|
||||
session
|
||||
)
|
||||
|
||||
every { context.metrics } returns metrics
|
||||
every { context.components.core.sessionManager.selectedSession } returns session
|
||||
every { session.readerMode } returns false
|
||||
every { metrics.track(Event.QuickActionSheetReadTapped) } just Runs
|
||||
|
||||
interactor.onQuickActionSheetReadPressed()
|
||||
|
||||
verify { metrics.track(Event.QuickActionSheetReadTapped) }
|
||||
verify { readerModeController.showReaderView() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onQuickActionSheetReadPressedWithActiveReaderMode() {
|
||||
val context: Context = mockk()
|
||||
val metrics: MetricController = mockk()
|
||||
val session: Session = mockk()
|
||||
val readerModeController: ReaderModeController = mockk(relaxed = true)
|
||||
val browserStore: BrowserStore = mockk(relaxed = true)
|
||||
|
||||
val interactor = BrowserInteractor(
|
||||
context,
|
||||
browserStore,
|
||||
mockk(),
|
||||
mockk(),
|
||||
readerModeController,
|
||||
session
|
||||
)
|
||||
|
||||
every { context.metrics } returns metrics
|
||||
every { context.components.core.sessionManager.selectedSession } returns session
|
||||
every { session.readerMode } returns true
|
||||
every { metrics.track(Event.QuickActionSheetReadTapped) } just Runs
|
||||
|
||||
interactor.onQuickActionSheetReadPressed()
|
||||
|
||||
verify { metrics.track(Event.QuickActionSheetReadTapped) }
|
||||
verify { readerModeController.hideReaderView() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onQuickActionSheetOpenLinkPressed() {
|
||||
val context: Context = mockk()
|
||||
val session: Session = mockk()
|
||||
val quickActionSheetController: QuickActionSheetController = mockk(relaxed = true)
|
||||
|
||||
val interactor = BrowserInteractor(
|
||||
context,
|
||||
mockk(),
|
||||
mockk(),
|
||||
quickActionSheetController,
|
||||
mockk(),
|
||||
session
|
||||
)
|
||||
|
||||
interactor.onQuickActionSheetOpenLinkPressed()
|
||||
|
||||
verify { quickActionSheetController.handleOpenLink() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onQuickActionSheetAppearancePressed() {
|
||||
val context: Context = mockk()
|
||||
val readerModeController: ReaderModeController = mockk(relaxed = true)
|
||||
|
||||
val interactor = BrowserInteractor(
|
||||
context,
|
||||
mockk(),
|
||||
mockk(),
|
||||
mockk(),
|
||||
readerModeController,
|
||||
mockk()
|
||||
)
|
||||
|
||||
interactor.onQuickActionSheetAppearancePressed()
|
||||
|
||||
verify { readerModeController.showControls() }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.fenix.components.toolbar
|
||||
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotSame
|
||||
import org.junit.Test
|
||||
|
||||
class BrowserStoreTest {
|
||||
|
||||
@Test
|
||||
fun bookmarkStateChange() = runBlocking {
|
||||
val initialState = defaultBrowserState()
|
||||
val store = BrowserStore(initialState)
|
||||
|
||||
store.dispatch(QuickActionSheetAction.BookmarkedStateChange(true)).join()
|
||||
assertNotSame(initialState, store.state)
|
||||
assertEquals(store.state.quickActionSheetState.bookmarked, true)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun readableStateChange() = runBlocking {
|
||||
val initialState = defaultBrowserState()
|
||||
val store = BrowserStore(initialState)
|
||||
|
||||
store.dispatch(QuickActionSheetAction.ReadableStateChange(true)).join()
|
||||
assertNotSame(initialState, store.state)
|
||||
assertEquals(store.state.quickActionSheetState.readable, true)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun readerActiveStateChange() = runBlocking {
|
||||
val initialState = defaultBrowserState()
|
||||
val store = BrowserStore(initialState)
|
||||
|
||||
store.dispatch(QuickActionSheetAction.ReaderActiveStateChange(true)).join()
|
||||
assertNotSame(initialState, store.state)
|
||||
assertEquals(store.state.quickActionSheetState.readerActive, true)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun bounceNeededChange() = runBlocking {
|
||||
val initialState = defaultBrowserState()
|
||||
val store = BrowserStore(initialState)
|
||||
|
||||
store.dispatch(QuickActionSheetAction.BounceNeededChange).join()
|
||||
assertNotSame(initialState, store.state)
|
||||
assertEquals(store.state.quickActionSheetState.bounceNeeded, true)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun appLinkStateChange() = runBlocking {
|
||||
val initialState = defaultBrowserState()
|
||||
val store = BrowserStore(initialState)
|
||||
|
||||
store.dispatch(QuickActionSheetAction.AppLinkStateChange(true)).join()
|
||||
assertNotSame(initialState, store.state)
|
||||
assertEquals(store.state.quickActionSheetState.isAppLink, true)
|
||||
}
|
||||
|
||||
private fun defaultBrowserState(): BrowserState = BrowserState(
|
||||
quickActionSheetState = defaultQuickActionSheetState()
|
||||
)
|
||||
|
||||
private fun defaultQuickActionSheetState(): QuickActionSheetState = QuickActionSheetState(
|
||||
readable = false,
|
||||
bookmarked = false,
|
||||
readerActive = false,
|
||||
bounceNeeded = false,
|
||||
isAppLink = false
|
||||
)
|
||||
}
|
|
@ -1,274 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.fenix.quickactionsheet
|
||||
|
||||
import android.content.Context
|
||||
import io.mockk.Runs
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import junit.framework.Assert.assertEquals
|
||||
import mozilla.components.browser.session.Session
|
||||
import mozilla.components.browser.session.SessionManager
|
||||
import mozilla.components.feature.app.links.AppLinkRedirect
|
||||
import mozilla.components.feature.app.links.AppLinksUseCases
|
||||
import org.junit.Test
|
||||
import org.mozilla.fenix.browser.readermode.ReaderModeController
|
||||
import org.mozilla.fenix.components.Analytics
|
||||
import org.mozilla.fenix.components.Components
|
||||
import org.mozilla.fenix.components.Core
|
||||
import org.mozilla.fenix.components.metrics.Event
|
||||
import org.mozilla.fenix.components.metrics.MetricController
|
||||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.ext.metrics
|
||||
|
||||
class QuickActionInteractorTest {
|
||||
@Test
|
||||
fun onOpened() {
|
||||
val context: Context = mockk()
|
||||
val metrics: MetricController = mockk()
|
||||
val interactor = QuickActionInteractor(
|
||||
context,
|
||||
mockk(),
|
||||
mockk(),
|
||||
mockk(),
|
||||
mockk(),
|
||||
mockk()
|
||||
)
|
||||
|
||||
every { context.metrics } returns metrics
|
||||
every { metrics.track(Event.QuickActionSheetOpened) } just Runs
|
||||
|
||||
interactor.onOpened()
|
||||
|
||||
verify { metrics.track(Event.QuickActionSheetOpened) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onClosed() {
|
||||
val context: Context = mockk()
|
||||
val metrics: MetricController = mockk()
|
||||
val interactor = QuickActionInteractor(
|
||||
context,
|
||||
mockk(),
|
||||
mockk(),
|
||||
mockk(),
|
||||
mockk(),
|
||||
mockk()
|
||||
)
|
||||
|
||||
every { context.metrics } returns metrics
|
||||
every { metrics.track(Event.QuickActionSheetClosed) } just Runs
|
||||
|
||||
interactor.onClosed()
|
||||
|
||||
verify { metrics.track(Event.QuickActionSheetClosed) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onSharedPressed() {
|
||||
val context: Context = mockk()
|
||||
val session: Session = mockk()
|
||||
var selectedSessionUrl = ""
|
||||
|
||||
val metrics: MetricController = mockk()
|
||||
val interactor = QuickActionInteractor(
|
||||
context,
|
||||
mockk(),
|
||||
mockk(),
|
||||
{ selectedSessionUrl = it },
|
||||
mockk(),
|
||||
mockk()
|
||||
)
|
||||
|
||||
val components: Components = mockk()
|
||||
val core: Core = mockk()
|
||||
val sessionManager: SessionManager = mockk()
|
||||
|
||||
val analytics: Analytics = mockk()
|
||||
|
||||
every { session.url } returns "mozilla.org"
|
||||
every { context.components } returns components
|
||||
every { components.analytics } returns analytics
|
||||
every { metrics.track(Event.QuickActionSheetShareTapped) } just Runs
|
||||
// Since we are mocking components, we must manually define metrics as `analytics.metrics`
|
||||
every { analytics.metrics } returns metrics
|
||||
every { components.core } returns core
|
||||
every { core.sessionManager } returns sessionManager
|
||||
every { sessionManager.selectedSession } returns session
|
||||
|
||||
interactor.onSharedPressed()
|
||||
|
||||
verify { metrics.track(Event.QuickActionSheetShareTapped) }
|
||||
assertEquals("mozilla.org", selectedSessionUrl)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onDownloadsPressed() {
|
||||
val context: Context = mockk()
|
||||
val metrics: MetricController = mockk()
|
||||
val interactor = QuickActionInteractor(
|
||||
context,
|
||||
mockk(),
|
||||
mockk(),
|
||||
mockk(),
|
||||
mockk(),
|
||||
mockk()
|
||||
)
|
||||
|
||||
every { context.metrics } returns metrics
|
||||
every { metrics.track(Event.QuickActionSheetDownloadTapped) } just Runs
|
||||
|
||||
interactor.onDownloadsPressed()
|
||||
|
||||
verify { metrics.track(Event.QuickActionSheetDownloadTapped) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onBookmarkPressed() {
|
||||
val context: Context = mockk()
|
||||
val session: Session = mockk()
|
||||
var bookmarkedSession: Session? = null
|
||||
|
||||
val metrics: MetricController = mockk()
|
||||
val interactor = QuickActionInteractor(
|
||||
context,
|
||||
mockk(),
|
||||
mockk(),
|
||||
mockk(),
|
||||
{ bookmarkedSession = it },
|
||||
mockk()
|
||||
)
|
||||
|
||||
val components: Components = mockk()
|
||||
val core: Core = mockk()
|
||||
val sessionManager: SessionManager = mockk()
|
||||
|
||||
val analytics: Analytics = mockk()
|
||||
|
||||
every { session.url } returns "mozilla.org"
|
||||
every { context.components } returns components
|
||||
every { components.analytics } returns analytics
|
||||
every { metrics.track(Event.QuickActionSheetBookmarkTapped) } just Runs
|
||||
// Since we are mocking components, we must manually define metrics as `analytics.metrics`
|
||||
every { analytics.metrics } returns metrics
|
||||
every { components.core } returns core
|
||||
every { core.sessionManager } returns sessionManager
|
||||
every { sessionManager.selectedSession } returns session
|
||||
|
||||
interactor.onBookmarkPressed()
|
||||
|
||||
verify { metrics.track(Event.QuickActionSheetBookmarkTapped) }
|
||||
assertEquals("mozilla.org", bookmarkedSession?.url)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onReadPressed() {
|
||||
val context: Context = mockk()
|
||||
val metrics: MetricController = mockk()
|
||||
val session: Session = mockk()
|
||||
val readerModeController: ReaderModeController = mockk(relaxed = true)
|
||||
val quickActionSheetStore: QuickActionSheetStore = mockk(relaxed = true)
|
||||
|
||||
val interactor = QuickActionInteractor(
|
||||
context,
|
||||
readerModeController,
|
||||
quickActionSheetStore,
|
||||
mockk(),
|
||||
mockk(),
|
||||
mockk()
|
||||
)
|
||||
|
||||
every { context.metrics } returns metrics
|
||||
every { context.components.core.sessionManager.selectedSession } returns session
|
||||
every { session.readerMode } returns false
|
||||
every { metrics.track(Event.QuickActionSheetReadTapped) } just Runs
|
||||
|
||||
interactor.onReadPressed()
|
||||
|
||||
verify { metrics.track(Event.QuickActionSheetReadTapped) }
|
||||
verify { readerModeController.showReaderView() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onReadPressedWithActiveReaderMode() {
|
||||
val context: Context = mockk()
|
||||
val metrics: MetricController = mockk()
|
||||
val session: Session = mockk()
|
||||
val readerModeController: ReaderModeController = mockk(relaxed = true)
|
||||
val quickActionSheetStore: QuickActionSheetStore = mockk(relaxed = true)
|
||||
|
||||
val interactor = QuickActionInteractor(
|
||||
context,
|
||||
readerModeController,
|
||||
quickActionSheetStore,
|
||||
mockk(),
|
||||
mockk(),
|
||||
mockk()
|
||||
)
|
||||
|
||||
every { context.metrics } returns metrics
|
||||
every { context.components.core.sessionManager.selectedSession } returns session
|
||||
every { session.readerMode } returns true
|
||||
every { metrics.track(Event.QuickActionSheetReadTapped) } just Runs
|
||||
|
||||
interactor.onReadPressed()
|
||||
|
||||
verify { metrics.track(Event.QuickActionSheetReadTapped) }
|
||||
verify { readerModeController.hideReaderView() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onAppearancePressed() {
|
||||
val context: Context = mockk()
|
||||
val readerModeController: ReaderModeController = mockk(relaxed = true)
|
||||
|
||||
val interactor = QuickActionInteractor(
|
||||
context,
|
||||
readerModeController,
|
||||
mockk(),
|
||||
mockk(),
|
||||
mockk(),
|
||||
mockk()
|
||||
)
|
||||
|
||||
interactor.onAppearancePressed()
|
||||
|
||||
verify { readerModeController.showControls() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onOpenAppLink() {
|
||||
val context: Context = mockk()
|
||||
val session: Session = mockk()
|
||||
val appLinksUseCases: AppLinksUseCases = mockk()
|
||||
|
||||
val interactor = QuickActionInteractor(
|
||||
context,
|
||||
mockk(),
|
||||
mockk(),
|
||||
mockk(),
|
||||
mockk(),
|
||||
appLinksUseCases
|
||||
)
|
||||
|
||||
every { context.components.core.sessionManager.selectedSession } returns session
|
||||
every { session.url } returns "mozilla.org"
|
||||
|
||||
val getAppLinkRedirect: AppLinksUseCases.GetAppLinkRedirect = mockk()
|
||||
val appLinkRedirect: AppLinkRedirect = mockk()
|
||||
val openAppLink: AppLinksUseCases.OpenAppLinkRedirect = mockk(relaxed = true)
|
||||
|
||||
every { appLinksUseCases.appLinkRedirect } returns getAppLinkRedirect
|
||||
every { getAppLinkRedirect.invoke("mozilla.org") } returns appLinkRedirect
|
||||
every { appLinksUseCases.openAppLink } returns openAppLink
|
||||
every { appLinkRedirect.appIntent } returns mockk(relaxed = true)
|
||||
|
||||
interactor.onOpenAppLinkPressed()
|
||||
|
||||
verify { openAppLink.invoke(appLinkRedirect) }
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue