diff --git a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt index 520c21fde3..44ec66b96d 100644 --- a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt @@ -698,6 +698,20 @@ class HomeFragment : Fragment() { } private fun navigateToSearch() { + // Dismisses the search dialog when the home content is scrolled + val recyclerView = sessionControlView!!.view + val listener = object : RecyclerView.OnScrollListener() { + override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { + super.onScrollStateChanged(recyclerView, newState) + if (newState == RecyclerView.SCROLL_STATE_DRAGGING || newState == RecyclerView.SCROLL_STATE_SETTLING) { + findNavController().navigateUp() + recyclerView.removeOnScrollListener(this) + } + } + } + + recyclerView.addOnScrollListener(listener) + val directions = HomeFragmentDirections.actionGlobalSearchDialog( sessionId = null diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlController.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlController.kt index 9bc8855771..d52c1ac183 100644 --- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlController.kt +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlController.kt @@ -34,7 +34,6 @@ import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.metrics import org.mozilla.fenix.ext.nav import org.mozilla.fenix.ext.sessionsOfType -import org.mozilla.fenix.ext.settings import org.mozilla.fenix.home.HomeFragment import org.mozilla.fenix.home.HomeFragmentAction import org.mozilla.fenix.home.HomeFragmentDirections @@ -158,6 +157,11 @@ interface SessionControlController { * @see [CollectionInteractor.onRemoveCollectionsPlaceholder] */ fun handleRemoveCollectionsPlaceholder() + + /** + * @see [CollectionInteractor.onCollectionMenuOpened] and [TopSiteInteractor.onTopSiteMenuOpened] + */ + fun handleMenuOpened() } @Suppress("TooManyFunctions", "LargeClass") @@ -193,7 +197,12 @@ class DefaultSessionControlController( ) } + override fun handleMenuOpened() { + dismissSearchDialogIfDisplayed() + } + override fun handleCollectionOpenTabClicked(tab: ComponentTab) { + dismissSearchDialogIfDisplayed() sessionManager.restore( activity, engine, @@ -256,6 +265,7 @@ class DefaultSessionControlController( } override fun handleCollectionShareTabsClicked(collection: TabCollection) { + dismissSearchDialogIfDisplayed() showShareFragment( collection.title, collection.tabs.map { ShareData(url = it.url, title = it.title) } @@ -282,6 +292,7 @@ class DefaultSessionControlController( } override fun handlePrivateBrowsingLearnMoreClicked() { + dismissSearchDialogIfDisplayed() activity.openToBrowserAndLoad( searchTermOrURL = SupportUtils.getGenericSumoURLForTopic (SupportUtils.SumoTopic.PRIVATE_BROWSING_MYTHS), @@ -293,9 +304,9 @@ class DefaultSessionControlController( override fun handleRenameTopSiteClicked(topSite: TopSite) { activity.let { val customLayout = - LayoutInflater.from(it).inflate(R.layout.top_sites_rename_dialog, null) + LayoutInflater.from(it).inflate(R.layout.top_sites_rename_dialog, null) val topSiteLabelEditText: EditText = - customLayout.findViewById(R.id.top_site_title) + customLayout.findViewById(R.id.top_site_title) topSiteLabelEditText.setText(topSite.title) AlertDialog.Builder(it).apply { @@ -344,6 +355,7 @@ class DefaultSessionControlController( } override fun handleSelectTopSite(url: String, type: TopSite.Type) { + dismissSearchDialogIfDisplayed() metrics.track(Event.TopSiteOpenInNewTab) when (type) { TopSite.Type.DEFAULT -> metrics.track(Event.TopSiteOpenDefault) @@ -362,6 +374,12 @@ class DefaultSessionControlController( activity.openToBrowser(BrowserDirection.FromHome) } + private fun dismissSearchDialogIfDisplayed() { + if (navController.currentDestination?.id == R.id.searchDialogFragment) { + navController.navigateUp() + } + } + override fun handleStartBrowsingClicked() { hideOnboarding() } diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlInteractor.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlInteractor.kt index 6237920ae9..33bdd0f31a 100644 --- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlInteractor.kt +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlInteractor.kt @@ -23,6 +23,7 @@ interface TabSessionInteractor { /** * Interface for collection related actions in the [SessionControlInteractor]. */ +@SuppressWarnings("TooManyFunctions") interface CollectionInteractor { /** * Shows the Collection Creation fragment for selecting the tabs to add to the given tab @@ -98,6 +99,11 @@ interface CollectionInteractor { * User has removed the collections placeholder from home. */ fun onRemoveCollectionsPlaceholder() + + /** + * User has opened collection 3 dot menu. + */ + fun onCollectionMenuOpened() } interface ToolbarInteractor { @@ -177,6 +183,11 @@ interface TopSiteInteractor { * @param type The type of the top site. */ fun onSelectTopSite(url: String, type: TopSite.Type) + + /** + * Called when top site menu is opened. + */ + fun onTopSiteMenuOpened() } /** @@ -276,4 +287,12 @@ class SessionControlInteractor( override fun onRemoveCollectionsPlaceholder() { controller.handleRemoveCollectionsPlaceholder() } + + override fun onCollectionMenuOpened() { + controller.handleMenuOpened() + } + + override fun onTopSiteMenuOpened() { + controller.handleMenuOpened() + } } diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/CollectionViewHolder.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/CollectionViewHolder.kt index c643f43d9a..6e8e3ea9c3 100644 --- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/CollectionViewHolder.kt +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/CollectionViewHolder.kt @@ -47,6 +47,7 @@ class CollectionViewHolder( } collection_overflow_button.setOnClickListener { + interactor.onCollectionMenuOpened() collectionMenu.menuBuilder .build(view.context) .show(anchor = it) diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/topsites/TopSiteItemViewHolder.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/topsites/TopSiteItemViewHolder.kt index efc572aec5..48c4ee3e16 100644 --- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/topsites/TopSiteItemViewHolder.kt +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/topsites/TopSiteItemViewHolder.kt @@ -36,6 +36,7 @@ class TopSiteItemViewHolder( } top_site_item.setOnLongClickListener { + interactor.onTopSiteMenuOpened() it.context.components.analytics.metrics.track(Event.TopSiteLongPress(topSite.type)) val topSiteMenu = TopSiteItemMenu(view.context, topSite.type != FRECENT) { item -> diff --git a/app/src/main/java/org/mozilla/fenix/search/SearchDialogFragment.kt b/app/src/main/java/org/mozilla/fenix/search/SearchDialogFragment.kt index a6b3213277..33c00362ec 100644 --- a/app/src/main/java/org/mozilla/fenix/search/SearchDialogFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/search/SearchDialogFragment.kt @@ -10,7 +10,9 @@ import android.app.Dialog import android.content.Context import android.content.DialogInterface import android.content.Intent +import android.graphics.Color import android.graphics.Typeface +import android.graphics.drawable.ColorDrawable import android.os.Build import android.os.Bundle import android.os.StrictMode @@ -21,6 +23,7 @@ import android.view.View import android.view.ViewGroup import android.view.ViewStub import android.view.WindowManager +import android.view.inputmethod.InputMethodManager import android.view.accessibility.AccessibilityEvent import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatDialogFragment @@ -183,6 +186,15 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler { requireComponents.core.engine.speculativeCreateSession(isPrivate) + if (findNavController().previousBackStackEntry?.destination?.id == R.id.homeFragment) { + // When displayed above home, dispatches the touch events to scrim area to the HomeFragment + view.search_wrapper.background = ColorDrawable(Color.TRANSPARENT) + dialog?.window?.decorView?.setOnTouchListener { _, event -> + requireActivity().dispatchTouchEvent(event) + false + } + } + return view } @@ -193,9 +205,12 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler { setupConstraints(view) - search_wrapper.setOnClickListener { - it.hideKeyboardAndSave() - dismissAllowingStateLoss() + // When displayed above browser, dismisses dialog on clicking scrim area + if (findNavController().previousBackStackEntry?.destination?.id == R.id.browserFragment) { + search_wrapper.setOnClickListener { + it.hideKeyboardAndSave() + dismissAllowingStateLoss() + } } view.search_engines_shortcut_button.setOnClickListener { @@ -327,6 +342,21 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler { toolbarView.view.requestFocus() } + /* + * This way of dismissing the keyboard is needed to smoothly dismiss the keyboard while the dialog + * is also dismissing. For example, when clicking a top site on home while this dialog is showing. + */ + private fun hideDeviceKeyboard() { + val imm = + requireContext().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + imm.toggleSoftInput(InputMethodManager.HIDE_IMPLICIT_ONLY, 0) + } + + override fun onDismiss(dialog: DialogInterface) { + super.onDismiss(dialog) + hideDeviceKeyboard() + } + override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) { if (requestCode == VoiceSearchActivity.SPEECH_REQUEST_CODE && resultCode == Activity.RESULT_OK) { intent?.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS)?.first()?.also { diff --git a/app/src/test/java/org/mozilla/fenix/home/DefaultSessionControlControllerTest.kt b/app/src/test/java/org/mozilla/fenix/home/DefaultSessionControlControllerTest.kt index 7a4080fe82..4041b63050 100644 --- a/app/src/test/java/org/mozilla/fenix/home/DefaultSessionControlControllerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/home/DefaultSessionControlControllerTest.kt @@ -434,4 +434,30 @@ class DefaultSessionControlControllerTest { fragmentStore.dispatch(HomeFragmentAction.RemoveCollectionsPlaceholder) } } + + @Test + fun handleMenuOpenedWhileSearchShowing() { + every { navController.currentDestination } returns mockk { + every { id } returns R.id.searchDialogFragment + } + + controller.handleMenuOpened() + + verify { + navController.navigateUp() + } + } + + @Test + fun handleMenuOpenedWhileSearchNotShowing() { + every { navController.currentDestination } returns mockk { + every { id } returns R.id.homeFragment + } + + controller.handleMenuOpened() + + verify(exactly = 0) { + navController.navigateUp() + } + } } diff --git a/app/src/test/java/org/mozilla/fenix/home/SessionControlInteractorTest.kt b/app/src/test/java/org/mozilla/fenix/home/SessionControlInteractorTest.kt index 6357cf51fd..70372c1d72 100644 --- a/app/src/test/java/org/mozilla/fenix/home/SessionControlInteractorTest.kt +++ b/app/src/test/java/org/mozilla/fenix/home/SessionControlInteractorTest.kt @@ -116,4 +116,16 @@ class SessionControlInteractorTest { interactor.onRemoveCollectionsPlaceholder() verify { controller.handleRemoveCollectionsPlaceholder() } } + + @Test + fun onCollectionMenuOpened() { + interactor.onCollectionMenuOpened() + verify { controller.handleMenuOpened() } + } + + @Test + fun onTopSiteMenuOpened() { + interactor.onTopSiteMenuOpened() + verify { controller.handleMenuOpened() } + } } diff --git a/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/topsites/TopSiteItemViewHolderTest.kt b/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/topsites/TopSiteItemViewHolderTest.kt index 54d1b91f00..03036eb875 100644 --- a/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/topsites/TopSiteItemViewHolderTest.kt +++ b/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/topsites/TopSiteItemViewHolderTest.kt @@ -44,4 +44,12 @@ class TopSiteItemViewHolderTest { view.top_site_item.performClick() verify { interactor.onSelectTopSite("https://getpocket.com", TopSite.Type.DEFAULT) } } + + @Test + fun `calls interactor on long click`() { + TopSiteItemViewHolder(view, interactor).bind(pocket) + + view.top_site_item.performLongClick() + verify { interactor.onTopSiteMenuOpened() } + } }