Unverified Commit 1adf4672 authored by Elise Richards's avatar Elise Richards Committed by GitHub
Browse files

For #14280, #14743: Remove old search fragment (#15169)

* Remove search fragment

* Use new folder to search dialog

* Rebase and lint

* Update tests with search dialog nav directions

* Rename interactor to match naming convention. Remove old controller and point everything to the dialog controller.
parent add60611
......@@ -16,7 +16,6 @@ import androidx.annotation.IdRes
enum class BrowserDirection(@IdRes val fragmentId: Int) {
FromGlobal(0),
FromHome(R.id.homeFragment),
FromSearch(R.id.searchFragment),
FromSearchDialog(R.id.searchDialogFragment),
FromSettings(R.id.settingsFragment),
FromSyncedTabs(R.id.syncedTabsFragment),
......
......@@ -21,11 +21,6 @@ object FeatureFlags {
*/
val syncedTabsInTabsTray = Config.channel.isNightlyOrDebug
/**
* Enables the new search experience
*/
const val newSearchExperience = true
/**
* Enables showing the top frequently visited sites
*/
......
......@@ -86,8 +86,7 @@ import org.mozilla.fenix.library.history.HistoryFragmentDirections
import org.mozilla.fenix.library.recentlyclosed.RecentlyClosedFragmentDirections
import org.mozilla.fenix.perf.Performance
import org.mozilla.fenix.perf.StartupTimeline
import org.mozilla.fenix.search.SearchFragmentDirections
import org.mozilla.fenix.searchdialog.SearchDialogFragmentDirections
import org.mozilla.fenix.search.SearchDialogFragmentDirections
import org.mozilla.fenix.session.PrivateNotificationService
import org.mozilla.fenix.settings.SettingsFragmentDirections
import org.mozilla.fenix.settings.TrackingProtectionFragmentDirections
......@@ -670,8 +669,6 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
NavGraphDirections.actionGlobalBrowser(customTabSessionId)
BrowserDirection.FromHome ->
HomeFragmentDirections.actionGlobalBrowser(customTabSessionId)
BrowserDirection.FromSearch ->
SearchFragmentDirections.actionGlobalBrowser(customTabSessionId)
BrowserDirection.FromSearchDialog ->
SearchDialogFragmentDirections.actionGlobalBrowser(customTabSessionId)
BrowserDirection.FromSettings ->
......
......@@ -848,7 +848,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session
@CallSuper
override fun onPause() {
super.onPause()
if (findNavController().currentDestination?.id != R.id.searchFragment) {
if (findNavController().currentDestination?.id != R.id.searchDialogFragment) {
view?.hideKeyboard()
}
}
......
......@@ -9,7 +9,6 @@ import mozilla.components.browser.session.Session
import mozilla.components.browser.session.SessionManager
import mozilla.components.concept.engine.EngineView
import mozilla.components.support.ktx.kotlin.isUrl
import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.browser.BrowserAnimator
......@@ -46,7 +45,6 @@ class DefaultBrowserToolbarController(
private val engineView: EngineView,
private val browserAnimator: BrowserAnimator,
private val customTabSession: Session?,
private val useNewSearchExperience: Boolean = FeatureFlags.newSearchExperience,
private val onTabCounterClicked: () -> Unit,
private val onCloseTab: (Session) -> Unit
) : BrowserToolbarController {
......@@ -55,27 +53,14 @@ class DefaultBrowserToolbarController(
get() = customTabSession ?: sessionManager.selectedSession
override fun handleToolbarPaste(text: String) {
if (useNewSearchExperience) {
navController.nav(
R.id.browserFragment,
BrowserFragmentDirections.actionGlobalSearchDialog(
sessionId = currentSession?.id,
pastedText = text
),
getToolbarNavOptions(activity)
)
} else {
browserAnimator.captureEngineViewAndDrawStatically {
navController.nav(
R.id.browserFragment,
BrowserFragmentDirections.actionBrowserFragmentToSearchFragment(
sessionId = currentSession?.id,
pastedText = text
),
getToolbarNavOptions(activity)
)
}
}
navController.nav(
R.id.browserFragment,
BrowserFragmentDirections.actionGlobalSearchDialog(
sessionId = currentSession?.id,
pastedText = text
),
getToolbarNavOptions(activity)
)
}
override fun handleToolbarPasteAndGo(text: String) {
......@@ -94,26 +79,13 @@ class DefaultBrowserToolbarController(
override fun handleToolbarClick() {
metrics.track(Event.SearchBarTapped(Event.SearchBarTapped.Source.BROWSER))
if (useNewSearchExperience) {
navController.nav(
R.id.browserFragment,
BrowserFragmentDirections.actionGlobalSearchDialog(
currentSession?.id
),
getToolbarNavOptions(activity)
)
} else {
browserAnimator.captureEngineViewAndDrawStatically {
navController.nav(
R.id.browserFragment,
BrowserFragmentDirections.actionBrowserFragmentToSearchFragment(
currentSession?.id
),
getToolbarNavOptions(activity)
)
}
}
navController.nav(
R.id.browserFragment,
BrowserFragmentDirections.actionGlobalSearchDialog(
currentSession?.id
),
getToolbarNavOptions(activity)
)
}
override fun handleTabCounterClick() {
......
......@@ -72,7 +72,6 @@ import mozilla.components.lib.state.ext.consumeFrom
import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
import mozilla.components.support.ktx.android.content.res.resolveAttribute
import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.browser.BrowserAnimator.Companion.getToolbarNavOptions
......@@ -431,8 +430,7 @@ class HomeFragment : Fragment() {
// We call this onLayout so that the bottom bar width is correctly set for us to center
// the CFR in.
view.toolbar_wrapper.doOnLayout {
val willNavigateToSearch =
!bundleArgs.getBoolean(FOCUS_ON_ADDRESS_BAR) && FeatureFlags.newSearchExperience
val willNavigateToSearch = !bundleArgs.getBoolean(FOCUS_ON_ADDRESS_BAR)
if (!browsingModeManager.mode.isPrivate && !willNavigateToSearch) {
SearchWidgetCFR(
context = view.context,
......@@ -464,7 +462,7 @@ class HomeFragment : Fragment() {
updateTabCounter(requireComponents.core.store.state)
if (bundleArgs.getBoolean(FOCUS_ON_ADDRESS_BAR) && FeatureFlags.newSearchExperience) {
if (bundleArgs.getBoolean(FOCUS_ON_ADDRESS_BAR)) {
navigateToSearch()
}
}
......@@ -739,15 +737,10 @@ class HomeFragment : Fragment() {
}
private fun navigateToSearch() {
val directions = if (FeatureFlags.newSearchExperience) {
val directions =
HomeFragmentDirections.actionGlobalSearchDialog(
sessionId = null
)
} else {
HomeFragmentDirections.actionGlobalSearch(
sessionId = null
)
}
nav(R.id.homeFragment, directions, getToolbarNavOptions(requireContext()))
}
......
......@@ -46,7 +46,7 @@ class StartSearchIntentProcessor(
out.removeExtra(HomeActivity.OPEN_TO_SEARCH)
val directions = source?.let {
NavGraphDirections.actionGlobalSearch(
NavGraphDirections.actionGlobalSearchDialog(
sessionId = null,
searchAccessPoint = it
)
......
......@@ -428,7 +428,7 @@ class DefaultSessionControlController(
}
override fun handlePaste(clipboardText: String) {
val directions = HomeFragmentDirections.actionGlobalSearch(
val directions = HomeFragmentDirections.actionGlobalSearchDialog(
sessionId = null,
pastedText = clipboardText
)
......
/* 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.search
import android.content.DialogInterface
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.text.SpannableString
import androidx.annotation.VisibleForTesting
import androidx.appcompat.app.AlertDialog
import androidx.navigation.NavController
import mozilla.components.browser.search.SearchEngine
import mozilla.components.browser.session.Session
import mozilla.components.browser.session.SessionManager
import mozilla.components.support.ktx.kotlin.isUrl
import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.metrics.Event.PerformedSearch.SearchAccessPoint.ACTION
import org.mozilla.fenix.components.metrics.Event.PerformedSearch.SearchAccessPoint.NONE
import org.mozilla.fenix.components.metrics.Event.PerformedSearch.SearchAccessPoint.SUGGESTION
import org.mozilla.fenix.components.metrics.MetricController
import org.mozilla.fenix.components.metrics.MetricsUtils
import org.mozilla.fenix.components.searchengine.CustomSearchEngineStore
import org.mozilla.fenix.crashes.CrashListActivity
import org.mozilla.fenix.ext.navigateSafe
import org.mozilla.fenix.settings.SupportUtils
import org.mozilla.fenix.settings.SupportUtils.MozillaPage.MANIFESTO
import org.mozilla.fenix.utils.Settings
/**
* An interface that handles the view manipulation of the Search, triggered by the Interactor
*/
@Suppress("TooManyFunctions")
interface SearchController {
fun handleUrlCommitted(url: String)
fun handleEditingCancelled()
fun handleTextChanged(text: String)
fun handleUrlTapped(url: String)
fun handleSearchTermsTapped(searchTerms: String)
fun handleSearchShortcutEngineSelected(searchEngine: SearchEngine)
fun handleClickSearchEngineSettings()
fun handleExistingSessionSelected(session: Session)
fun handleExistingSessionSelected(tabId: String)
fun handleSearchShortcutsButtonClicked()
fun handleCameraPermissionsNeeded()
}
@Suppress("TooManyFunctions", "LongParameterList")
class DefaultSearchController(
private val activity: HomeActivity,
private val sessionManager: SessionManager,
private val store: SearchFragmentStore,
private val navController: NavController,
private val settings: Settings,
private val metrics: MetricController,
private val clearToolbarFocus: () -> Unit
) : SearchController {
override fun handleUrlCommitted(url: String) {
when (url) {
"about:crashes" -> {
// The list of past crashes can be accessed via "settings > about", but desktop and
// fennec users may be used to navigating to "about:crashes". So we intercept this here
// and open the crash list activity instead.
activity.startActivity(Intent(activity, CrashListActivity::class.java))
}
"about:addons" -> {
val directions = SearchFragmentDirections.actionGlobalAddonsManagementFragment()
navController.navigateSafe(R.id.searchFragment, directions)
}
"moz://a" -> openSearchOrUrl(SupportUtils.getMozillaPageUrl(MANIFESTO))
else -> if (url.isNotBlank()) {
openSearchOrUrl(url)
}
}
}
private fun openSearchOrUrl(url: String) {
activity.openToBrowserAndLoad(
searchTermOrURL = url,
newTab = store.state.tabId == null,
from = BrowserDirection.FromSearch,
engine = store.state.searchEngineSource.searchEngine
)
val event = if (url.isUrl()) {
Event.EnteredUrl(false)
} else {
settings.incrementActiveSearchCount()
val searchAccessPoint = when (store.state.searchAccessPoint) {
NONE -> ACTION
else -> store.state.searchAccessPoint
}
searchAccessPoint?.let { sap ->
MetricsUtils.createSearchEvent(
store.state.searchEngineSource.searchEngine,
activity,
sap
)
}
}
event?.let { metrics.track(it) }
}
override fun handleEditingCancelled() {
clearToolbarFocus()
}
override fun handleTextChanged(text: String) {
// Display the search shortcuts on each entry of the search fragment (see #5308)
val textMatchesCurrentUrl = store.state.url == text
val textMatchesCurrentSearch = store.state.searchTerms == text
store.dispatch(SearchFragmentAction.UpdateQuery(text))
store.dispatch(
SearchFragmentAction.ShowSearchShortcutEnginePicker(
(textMatchesCurrentUrl || textMatchesCurrentSearch || text.isEmpty()) &&
settings.shouldShowSearchShortcuts
)
)
store.dispatch(
SearchFragmentAction.AllowSearchSuggestionsInPrivateModePrompt(
text.isNotEmpty() &&
activity.browsingModeManager.mode.isPrivate &&
!settings.shouldShowSearchSuggestionsInPrivate &&
!settings.showSearchSuggestionsInPrivateOnboardingFinished
)
)
}
override fun handleUrlTapped(url: String) {
activity.openToBrowserAndLoad(
searchTermOrURL = url,
newTab = store.state.tabId == null,
from = BrowserDirection.FromSearch
)
metrics.track(Event.EnteredUrl(false))
}
override fun handleSearchTermsTapped(searchTerms: String) {
settings.incrementActiveSearchCount()
activity.openToBrowserAndLoad(
searchTermOrURL = searchTerms,
newTab = store.state.tabId == null,
from = BrowserDirection.FromSearch,
engine = store.state.searchEngineSource.searchEngine,
forceSearch = true
)
val searchAccessPoint = when (store.state.searchAccessPoint) {
NONE -> SUGGESTION
else -> store.state.searchAccessPoint
}
val event = searchAccessPoint?.let { sap ->
MetricsUtils.createSearchEvent(
store.state.searchEngineSource.searchEngine,
activity,
sap
)
}
event?.let { metrics.track(it) }
}
override fun handleSearchShortcutEngineSelected(searchEngine: SearchEngine) {
store.dispatch(SearchFragmentAction.SearchShortcutEngineSelected(searchEngine))
val isCustom =
CustomSearchEngineStore.isCustomSearchEngine(activity, searchEngine.identifier)
metrics.track(Event.SearchShortcutSelected(searchEngine, isCustom))
}
override fun handleSearchShortcutsButtonClicked() {
val isOpen = store.state.showSearchShortcuts
store.dispatch(SearchFragmentAction.ShowSearchShortcutEnginePicker(!isOpen))
}
override fun handleClickSearchEngineSettings() {
val directions = SearchFragmentDirections.actionGlobalSearchEngineFragment()
navController.navigateSafe(R.id.searchFragment, directions)
}
override fun handleExistingSessionSelected(session: Session) {
sessionManager.select(session)
activity.openToBrowser(
from = BrowserDirection.FromSearch
)
}
override fun handleExistingSessionSelected(tabId: String) {
val session = sessionManager.findSessionById(tabId)
if (session != null) {
handleExistingSessionSelected(session)
}
}
/**
* Creates and shows an [AlertDialog] when camera permissions are needed.
*
* In versions above M, [AlertDialog.BUTTON_POSITIVE] takes the user to the app settings. This
* intent only exists in M and above. Below M, [AlertDialog.BUTTON_POSITIVE] routes to a SUMO
* help page to find the app settings.
*
* [AlertDialog.BUTTON_NEGATIVE] dismisses the dialog.
*/
override fun handleCameraPermissionsNeeded() {
val dialog = buildDialog()
dialog.show()
}
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
fun buildDialog(): AlertDialog.Builder {
return AlertDialog.Builder(activity).apply {
val spannableText = SpannableString(
activity.resources.getString(R.string.camera_permissions_needed_message)
)
setMessage(spannableText)
setNegativeButton(R.string.camera_permissions_needed_negative_button_text) {
dialog: DialogInterface, _ ->
dialog.cancel()
}
setPositiveButton(R.string.camera_permissions_needed_positive_button_text) {
dialog: DialogInterface, _ ->
val intent: Intent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
} else {
SupportUtils.createCustomTabIntent(
activity,
SupportUtils.getSumoURLForTopic(
activity,
SupportUtils.SumoTopic.QR_CAMERA_ACCESS
)
)
}
val uri = Uri.fromParts("package", activity.packageName, null)
intent.data = uri
dialog.cancel()
activity.startActivity(intent)
}
create()
}
}
}
......@@ -2,7 +2,7 @@
* 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.searchdialog
package org.mozilla.fenix.search
import android.content.DialogInterface
import android.content.Intent
......@@ -25,12 +25,27 @@ import org.mozilla.fenix.components.metrics.MetricsUtils
import org.mozilla.fenix.components.searchengine.CustomSearchEngineStore
import org.mozilla.fenix.crashes.CrashListActivity
import org.mozilla.fenix.ext.navigateSafe
import org.mozilla.fenix.search.SearchController
import org.mozilla.fenix.search.SearchFragmentAction
import org.mozilla.fenix.search.SearchFragmentStore
import org.mozilla.fenix.settings.SupportUtils
import org.mozilla.fenix.utils.Settings
/**
* An interface that handles the view manipulation of the Search, triggered by the Interactor
*/
@Suppress("TooManyFunctions")
interface SearchController {
fun handleUrlCommitted(url: String)
fun handleEditingCancelled()
fun handleTextChanged(text: String)
fun handleUrlTapped(url: String)
fun handleSearchTermsTapped(searchTerms: String)
fun handleSearchShortcutEngineSelected(searchEngine: SearchEngine)
fun handleClickSearchEngineSettings()
fun handleExistingSessionSelected(session: Session)
fun handleExistingSessionSelected(tabId: String)
fun handleSearchShortcutsButtonClicked()
fun handleCameraPermissionsNeeded()
}
@Suppress("TooManyFunctions", "LongParameterList")
class SearchDialogController(
private val activity: HomeActivity,
......
......@@ -2,7 +2,7 @@
* 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.searchdialog
package org.mozilla.fenix.search
import android.Manifest
import android.app.Activity
......@@ -56,19 +56,13 @@ import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.isKeyboardVisible
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.search.SearchFragmentAction
import org.mozilla.fenix.search.SearchFragmentState
import org.mozilla.fenix.search.SearchFragmentStore
import org.mozilla.fenix.search.SearchInteractor
import org.mozilla.fenix.search.awesomebar.AwesomeBarView
import org.mozilla.fenix.search.createInitialSearchFragmentState
import org.mozilla.fenix.search.toolbar.ToolbarView
import org.mozilla.fenix.settings.SupportUtils
import org.mozilla.fenix.settings.registerOnSharedPreferenceChangeListener
import org.mozilla.fenix.widget.VoiceSearchActivity
typealias SearchDialogFragmentStore = SearchFragmentStore
typealias SearchDialogInteractor = SearchInteractor
@SuppressWarnings("LargeClass", "TooManyFunctions")
class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler {
......
......@@ -14,8 +14,8 @@ import org.mozilla.fenix.search.toolbar.ToolbarInteractor
* Provides implementations for the AwesomeBarView and ToolbarView
*/
@Suppress("TooManyFunctions")
class SearchInteractor(
private val searchController: SearchController
class SearchDialogInteractor(
private val searchController: SearchDialogController
) : AwesomeBarInteractor, ToolbarInteractor {
override fun onUrlCommitted(url: String) {
......
/* 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.search
import android.Manifest
import android.app.Activity.RESULT_OK
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.graphics.Typeface.BOLD
import android.graphics.Typeface.ITALIC
import android.os.Bundle
import android.speech.RecognizerIntent
import android.speech.RecognizerIntent.EXTRA_RESULTS
import android.text.style.StyleSpan
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.ViewStub
import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.core.widget.NestedScrollView
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import kotlinx.android.synthetic.main.fragment_search.*
import kotlinx.android.synthetic.main.fragment_search.view.*
import kotlinx.android.synthetic.main.search_suggestions_hint.view.*
import kotlinx.coroutines.ExperimentalCoroutinesApi
import mozilla.components.browser.toolbar.BrowserToolbar
import mozilla.components.concept.storage.HistoryStorage
import mozilla.components.feature.qr.QrFeature
import mozilla.components.lib.state.ext.consumeFrom
import mozilla.components.support.base.feature.UserInteractionHandler
import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
import mozilla.components.support.ktx.android.content.getColorFromAttr
import mozilla.components.support.ktx.android.content.hasCamera
import mozilla.components.support.ktx.android.content.isPermissionGranted
import mozilla.components.support.ktx.android.content.res.getSpanned
import mozilla.components.support.ktx.android.view.hideKeyboard
import mozilla.components.ui.autocomplete.InlineAutocompleteEditText
import org.mozilla.fenix.BrowserDirection