diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserInteractor.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserInteractor.kt index fbf32a383..f6a5976d7 100644 --- a/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserInteractor.kt +++ b/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserInteractor.kt @@ -17,6 +17,14 @@ open class BrowserToolbarInteractor( private val browserToolbarController: BrowserToolbarController ) : BrowserToolbarViewInteractor { + override fun onBrowserToolbarPaste(text: String) { + browserToolbarController.handleToolbarPaste(text) + } + + override fun onBrowserToolbarPasteAndGo(text: String) { + browserToolbarController.handleToolbarPasteAndGo(text) + } + override fun onBrowserToolbarClicked() { browserToolbarController.handleToolbarClick() } diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarController.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarController.kt index 8ef6f0fbb..3379b9d7a 100644 --- a/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarController.kt +++ b/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarController.kt @@ -34,6 +34,8 @@ import org.mozilla.fenix.quickactionsheet.QuickActionSheetBehavior * An interface that handles the view manipulation of the BrowserToolbar, triggered by the Interactor */ interface BrowserToolbarController { + fun handleToolbarPaste(text: String) + fun handleToolbarPasteAndGo(text: String) fun handleToolbarItemInteraction(item: ToolbarMenu.Item) fun handleToolbarClick() } @@ -51,6 +53,21 @@ class DefaultBrowserToolbarController( private val bottomSheetBehavior: QuickActionSheetBehavior ) : BrowserToolbarController { + override fun handleToolbarPaste(text: String) { + navController.nav( + R.id.browserFragment, + BrowserFragmentDirections.actionBrowserFragmentToSearchFragment( + sessionId = customTabSession?.id ?: context.components.core.sessionManager.selectedSession?.id, + pastedText = text + ) + ) + } + + override fun handleToolbarPasteAndGo(text: String) { + context.components.core.sessionManager.selectedSession?.searchTerms = "" + context.components.useCases.sessionUseCases.loadUrl(text) + } + override fun handleToolbarClick() { context.components.analytics.metrics.track( Event.SearchBarTapped(Event.SearchBarTapped.Source.BROWSER) @@ -58,7 +75,8 @@ class DefaultBrowserToolbarController( navController.nav( R.id.browserFragment, BrowserFragmentDirections.actionBrowserFragmentToSearchFragment( - customTabSession?.id ?: context.components.core.sessionManager.selectedSession?.id + customTabSession?.id ?: context.components.core.sessionManager.selectedSession?.id, + pastedText = null ) ) } @@ -101,12 +119,18 @@ class DefaultBrowserToolbarController( } } ToolbarMenu.Item.NewTab -> { - val directions = BrowserFragmentDirections.actionBrowserFragmentToSearchFragment(null) + val directions = BrowserFragmentDirections.actionBrowserFragmentToSearchFragment( + sessionId = null, + pastedText = null + ) navController.nav(R.id.browserFragment, directions) browsingModeManager.mode = BrowsingMode.Normal } ToolbarMenu.Item.NewPrivateTab -> { - val directions = BrowserFragmentDirections.actionBrowserFragmentToSearchFragment(null) + val directions = BrowserFragmentDirections.actionBrowserFragmentToSearchFragment( + sessionId = null, + pastedText = null + ) navController.nav(R.id.browserFragment, directions) browsingModeManager.mode = BrowsingMode.Private } diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarView.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarView.kt index c4cd07b29..2ec55bba6 100644 --- a/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarView.kt +++ b/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarView.kt @@ -1,8 +1,13 @@ package org.mozilla.fenix.components.toolbar +import android.content.ClipData +import android.content.ClipDescription.MIMETYPE_TEXT_PLAIN +import android.content.ClipboardManager +import android.content.Context.CLIPBOARD_SERVICE import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.PopupMenu import androidx.core.content.ContextCompat import kotlinx.android.extensions.LayoutContainer import mozilla.components.browser.domains.autocomplete.ShippedDomainsProvider @@ -11,11 +16,13 @@ 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.theme.ThemeManager import org.mozilla.fenix.customtabs.CustomTabToolbarMenu import org.mozilla.fenix.ext.components +import org.mozilla.fenix.theme.ThemeManager interface BrowserToolbarViewInteractor { + fun onBrowserToolbarPaste(text: String) + fun onBrowserToolbarPasteAndGo(text: String) fun onBrowserToolbarClicked() fun onBrowserToolbarMenuItemTapped(item: ToolbarMenu.Item) } @@ -39,6 +46,36 @@ class BrowserToolbarView( val toolbarIntegration: ToolbarIntegration init { + + view.setOnUrlLongClickListener { + val popup = PopupMenu(view.context, view) + popup.menuInflater.inflate(R.menu.browser_toolbar_popup_menu, popup.menu) + popup.show() + + val clipboard = view.context.getSystemService(CLIPBOARD_SERVICE) as ClipboardManager + + popup.menu.findItem(R.id.paste)?.isVisible = clipboard.containsText() + popup.menu.findItem(R.id.paste_and_go)?.isVisible = clipboard.containsText() + + popup.setOnMenuItemClickListener { + when (it.itemId) { + R.id.copy -> { + clipboard.primaryClip = ClipData.newPlainText("Text", view.url.toString()) + } + + R.id.paste -> { + interactor.onBrowserToolbarPaste(clipboard.primaryClip?.getItemAt(0)?.text.toString()) + } + + R.id.paste_and_go -> { + interactor.onBrowserToolbarPasteAndGo(clipboard.primaryClip?.getItemAt(0)?.text.toString()) + } + } + true + } + true + } + with(container.context) { val sessionManager = components.core.sessionManager val isCustomTabSession = customTabSession != null @@ -115,6 +152,10 @@ class BrowserToolbarView( // Intentionally leaving this as a stub for now since we don't actually want to update currently } + private fun ClipboardManager.containsText(): Boolean { + return (primaryClipDescription?.hasMimeType(MIMETYPE_TEXT_PLAIN) ?: false && primaryClip?.itemCount != 0) + } + companion object { private const val TOOLBAR_ELEVATION = 16 private const val PROGRESS_BOTTOM = 0 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 7d1cfee11..ad07f09cd 100644 --- a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt @@ -233,7 +233,11 @@ class HomeFragment : Fragment(), AccountObserver { view.toolbar_wrapper.setOnClickListener { invokePendingDeleteJobs() onboarding.finish() - val directions = HomeFragmentDirections.actionHomeFragmentToSearchFragment(null, true) + val directions = HomeFragmentDirections.actionHomeFragmentToSearchFragment( + sessionId = null, + showShortcutEnginePicker = true, + pastedText = null + ) val extras = FragmentNavigator.Extras.Builder() .addSharedElement(toolbar_wrapper, "toolbar_wrapper_transition") @@ -384,7 +388,11 @@ class HomeFragment : Fragment(), AccountObserver { } is TabAction.Add -> { invokePendingDeleteJobs() - val directions = HomeFragmentDirections.actionHomeFragmentToSearchFragment(null, true) + val directions = HomeFragmentDirections.actionHomeFragmentToSearchFragment( + sessionId = null, + showShortcutEnginePicker = true, + pastedText = null + ) nav(R.id.homeFragment, directions) } is TabAction.ShareTabs -> { diff --git a/app/src/main/java/org/mozilla/fenix/home/intent/StartSearchIntentProcessor.kt b/app/src/main/java/org/mozilla/fenix/home/intent/StartSearchIntentProcessor.kt index 3cf31fe9d..05420900d 100644 --- a/app/src/main/java/org/mozilla/fenix/home/intent/StartSearchIntentProcessor.kt +++ b/app/src/main/java/org/mozilla/fenix/home/intent/StartSearchIntentProcessor.kt @@ -24,7 +24,11 @@ class StartSearchIntentProcessor( out.putExtra(HomeActivity.OPEN_TO_SEARCH, false) metrics.track(Event.SearchWidgetNewTabPressed) - val directions = NavGraphDirections.actionGlobalSearch(sessionId = null, showShortcutEnginePicker = true) + val directions = NavGraphDirections.actionGlobalSearch( + sessionId = null, + showShortcutEnginePicker = true, + pastedText = null + ) navController.nav(null, directions) true } else { diff --git a/app/src/main/java/org/mozilla/fenix/search/SearchFragment.kt b/app/src/main/java/org/mozilla/fenix/search/SearchFragment.kt index 62842a6a4..9dfa46943 100644 --- a/app/src/main/java/org/mozilla/fenix/search/SearchFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/search/SearchFragment.kt @@ -74,6 +74,10 @@ class SearchFragment : Fragment(), BackHandler { ?.let { it.sessionId } ?.let(requireComponents.core.sessionManager::findSessionById) + val pastedText = arguments + ?.let(SearchFragmentArgs.Companion::fromBundle) + ?.let { it.pastedText } + val displayShortcutEnginePicker = arguments ?.let(SearchFragmentArgs.Companion::fromBundle) ?.let { it.showShortcutEnginePicker } ?: false @@ -95,7 +99,8 @@ class SearchFragment : Fragment(), BackHandler { showClipboardSuggestions = requireContext().settings.shouldShowClipboardSuggestions, showHistorySuggestions = requireContext().settings.shouldShowHistorySuggestions, showBookmarkSuggestions = requireContext().settings.shouldShowBookmarkSuggestions, - session = session + session = session, + pastedText = pastedText ) ) } diff --git a/app/src/main/java/org/mozilla/fenix/search/SearchFragmentStore.kt b/app/src/main/java/org/mozilla/fenix/search/SearchFragmentStore.kt index c69cfb522..6bdfee5d1 100644 --- a/app/src/main/java/org/mozilla/fenix/search/SearchFragmentStore.kt +++ b/app/src/main/java/org/mozilla/fenix/search/SearchFragmentStore.kt @@ -41,6 +41,7 @@ sealed class SearchEngineSource { * @property showHistorySuggestions Whether or not to show history suggestions in the AwesomeBar * @property showBookmarkSuggestions Whether or not to show the bookmark suggestion in the AwesomeBar * @property session The current session if available + * @property pastedText The text pasted from the long press toolbar menu */ data class SearchFragmentState( val query: String, @@ -51,7 +52,8 @@ data class SearchFragmentState( val showClipboardSuggestions: Boolean, val showHistorySuggestions: Boolean, val showBookmarkSuggestions: Boolean, - val session: Session? + val session: Session?, + val pastedText: String? = null ) : State /** diff --git a/app/src/main/java/org/mozilla/fenix/search/toolbar/ToolbarView.kt b/app/src/main/java/org/mozilla/fenix/search/toolbar/ToolbarView.kt index 761dfd200..bf7e6a7ca 100644 --- a/app/src/main/java/org/mozilla/fenix/search/toolbar/ToolbarView.kt +++ b/app/src/main/java/org/mozilla/fenix/search/toolbar/ToolbarView.kt @@ -111,8 +111,14 @@ class ToolbarView( fun update(searchState: SearchFragmentState) { if (!isInitialized) { - view.url = searchState.query - view.setSearchTerms(searchState.session?.searchTerms ?: "") + view.url = searchState.pastedText ?: searchState.query + + /* Only set the search terms if pasted text is null so that the search term doesn't + overwrite pastedText when view enters `editMode` */ + if (searchState.pastedText.isNullOrEmpty()) { + view.setSearchTerms(searchState.session?.searchTerms ?: "") + } + view.editMode() isInitialized = true } diff --git a/app/src/main/res/menu/browser_toolbar_popup_menu.xml b/app/src/main/res/menu/browser_toolbar_popup_menu.xml new file mode 100644 index 000000000..958b1533b --- /dev/null +++ b/app/src/main/res/menu/browser_toolbar_popup_menu.xml @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml index 2ab7193be..2c3c2e5cb 100644 --- a/app/src/main/res/navigation/nav_graph.xml +++ b/app/src/main/res/navigation/nav_graph.xml @@ -106,6 +106,10 @@ android:id="@+id/action_searchFragment_to_searchEngineFragment" app:destination="@id/searchEngineFragment" app:popUpTo="@+id/searchFragment" /> + Share session + Paste Bookmark menu @@ -871,4 +872,12 @@ Cancel Add + + + + Copy + + Paste & Go + + Paste diff --git a/app/src/test/java/org/mozilla/fenix/components/toolbar/DefaultBrowserToolbarControllerTest.kt b/app/src/test/java/org/mozilla/fenix/components/toolbar/DefaultBrowserToolbarControllerTest.kt index c43a5de0a..7f6cb9e19 100644 --- a/app/src/test/java/org/mozilla/fenix/components/toolbar/DefaultBrowserToolbarControllerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/components/toolbar/DefaultBrowserToolbarControllerTest.kt @@ -97,7 +97,10 @@ class DefaultBrowserToolbarControllerTest { verify { navController.nav( R.id.browserFragment, - BrowserFragmentDirections.actionBrowserFragmentToSearchFragment("1") + BrowserFragmentDirections.actionBrowserFragmentToSearchFragment( + sessionId = "1", + pastedText = null + ) ) } } @@ -243,7 +246,7 @@ class DefaultBrowserToolbarControllerTest { verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.NEW_PRIVATE_TAB)) } verify { val directions = BrowserFragmentDirections - .actionBrowserFragmentToSearchFragment(null) + .actionBrowserFragmentToSearchFragment(sessionId = null, pastedText = null) navController.nav(R.id.browserFragment, directions) } verify { browsingModeManager.mode = BrowsingMode.Private } @@ -310,7 +313,7 @@ class DefaultBrowserToolbarControllerTest { verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.NEW_TAB)) } verify { val directions = BrowserFragmentDirections - .actionBrowserFragmentToSearchFragment(null) + .actionBrowserFragmentToSearchFragment(sessionId = null, pastedText = null) navController.nav(R.id.browserFragment, directions) } verify { browsingModeManager.mode = BrowsingMode.Normal } @@ -337,7 +340,7 @@ class DefaultBrowserToolbarControllerTest { verify { viewModel.previousFragmentId = R.id.browserFragment } verify { val directions = BrowserFragmentDirections - .actionBrowserFragmentToSearchFragment(null) + .actionBrowserFragmentToSearchFragment(sessionId = null, pastedText = null) navController.nav(R.id.browserFragment, directions) } } diff --git a/app/src/test/java/org/mozilla/fenix/home/intent/StartSearchIntentProcessorTest.kt b/app/src/test/java/org/mozilla/fenix/home/intent/StartSearchIntentProcessorTest.kt index 9f20453c6..8d3400796 100644 --- a/app/src/test/java/org/mozilla/fenix/home/intent/StartSearchIntentProcessorTest.kt +++ b/app/src/test/java/org/mozilla/fenix/home/intent/StartSearchIntentProcessorTest.kt @@ -65,7 +65,11 @@ class StartSearchIntentProcessorTest { verify { metrics.track(Event.SearchWidgetNewTabPressed) } verify { navController.navigate( - NavGraphDirections.actionGlobalSearch(sessionId = null, showShortcutEnginePicker = true) + NavGraphDirections.actionGlobalSearch( + sessionId = null, + showShortcutEnginePicker = true, + pastedText = null + ) ) } verify { out.putExtra(HomeActivity.OPEN_TO_SEARCH, false) }