For #4155: Adds popup menu for browser toolbar
parent
017fb625dd
commit
fdd2b59fb8
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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<NestedScrollView>
|
||||
) : 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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 -> {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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/. -->
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:id="@+id/copy"
|
||||
android:iconTint="?primaryText"
|
||||
android:title="@string/browser_toolbar_long_press_popup_copy"/>
|
||||
<item
|
||||
android:id="@+id/paste"
|
||||
android:iconTint="?primaryText"
|
||||
android:title="@string/browser_toolbar_long_press_popup_paste"/>
|
||||
<item
|
||||
android:id="@+id/paste_and_go"
|
||||
android:iconTint="?primaryText"
|
||||
android:title="@string/browser_toolbar_long_press_popup_paste_and_go"/>
|
||||
</menu>
|
|
@ -106,6 +106,10 @@
|
|||
android:id="@+id/action_searchFragment_to_searchEngineFragment"
|
||||
app:destination="@id/searchEngineFragment"
|
||||
app:popUpTo="@+id/searchFragment" />
|
||||
<argument
|
||||
android:name="pastedText"
|
||||
app:argType="string"
|
||||
app:nullable="true"/>
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
|
|
|
@ -422,6 +422,7 @@
|
|||
<!-- Content Description for session item share button -->
|
||||
<string name="content_description_session_share">Share session</string>
|
||||
|
||||
<string name="paste">Paste</string>
|
||||
<!-- Bookmarks -->
|
||||
<!-- Content description for bookmarks library menu -->
|
||||
<string name="bookmark_menu_content_description">Bookmark menu</string>
|
||||
|
@ -871,4 +872,12 @@
|
|||
<string name="add_to_homescreen_cancel">Cancel</string>
|
||||
<!-- Add button text for the Add to Homescreen dialog -->
|
||||
<string name="add_to_homescreen_add">Add</string>
|
||||
|
||||
<!-- Browser long press popup menu -->
|
||||
<!-- Copy the current url -->
|
||||
<string name="browser_toolbar_long_press_popup_copy">Copy</string>
|
||||
<!-- Paste & go the text in the clipboard. '&' is replaced with the ampersand symbol: & -->
|
||||
<string name="browser_toolbar_long_press_popup_paste_and_go">Paste & Go</string>
|
||||
<!-- Paste the text in the clipboard -->
|
||||
<string name="browser_toolbar_long_press_popup_paste">Paste</string>
|
||||
</resources>
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) }
|
||||
|
|
Loading…
Reference in New Issue