Skip to content
This repository has been archived by the owner on Feb 20, 2023. It is now read-only.

Commit

Permalink
Closes #875: Adds search shortcuts (#882)
Browse files Browse the repository at this point in the history
* Closes #875: Adds shortcuts

* Refactor and clean up

* Remove TODO

* Removes local

* Fix nits

* Refactors to add ShortcutEngineManager
  • Loading branch information
sblatz authored Mar 29, 2019
1 parent 4f67b7a commit 36af510
Show file tree
Hide file tree
Showing 18 changed files with 438 additions and 72 deletions.
16 changes: 11 additions & 5 deletions app/src/main/java/org/mozilla/fenix/HomeActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import androidx.appcompat.widget.Toolbar
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.NavigationUI
import mozilla.components.browser.search.SearchEngine
import mozilla.components.browser.session.Session
import mozilla.components.concept.engine.EngineView
import mozilla.components.feature.intent.IntentProcessor
Expand Down Expand Up @@ -148,9 +149,14 @@ open class HomeActivity : AppCompatActivity() {
openToBrowser(SafeIntent(intent).getStringExtra(IntentProcessor.ACTIVE_SESSION_ID), BrowserDirection.FromGlobal)
}

fun openToBrowserAndLoad(text: String, sessionId: String? = null, from: BrowserDirection) {
fun openToBrowserAndLoad(
text: String,
sessionId: String? = null,
engine: SearchEngine? = null,
from: BrowserDirection
) {
openToBrowser(sessionId, from)
load(text, sessionId)
load(text, sessionId, engine)
}

fun openToBrowser(sessionId: String?, from: BrowserDirection) {
Expand All @@ -165,7 +171,7 @@ open class HomeActivity : AppCompatActivity() {
navHost.navController.navigate(directions)
}

private fun load(text: String, sessionId: String?) {
private fun load(text: String, sessionId: String?, engine: SearchEngine?) {
val isPrivate = this.browsingModeManager.isPrivate

val loadUrlUseCase = if (sessionId == null) {
Expand All @@ -179,8 +185,8 @@ open class HomeActivity : AppCompatActivity() {
val searchUseCase: (String) -> Unit = { searchTerms ->
if (sessionId == null) {
components.useCases.searchUseCases.newTabSearch
.invoke(searchTerms, Session.Source.USER_ENTERED, true, isPrivate)
} else components.useCases.searchUseCases.defaultSearch.invoke(searchTerms)
.invoke(searchTerms, Session.Source.USER_ENTERED, true, isPrivate, searchEngine = engine)
} else components.useCases.searchUseCases.defaultSearch.invoke(searchTerms, engine)
}

if (text.isUrl()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import androidx.navigation.Navigation
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.component_search.*
import kotlinx.android.synthetic.main.fragment_browser.view.*
import kotlinx.android.synthetic.main.fragment_search.*
import mozilla.components.browser.toolbar.behavior.BrowserToolbarBottomBehavior
import mozilla.components.feature.contextmenu.ContextMenuCandidate
import mozilla.components.feature.contextmenu.ContextMenuFeature
Expand Down Expand Up @@ -90,7 +91,8 @@ class BrowserFragment : Fragment(), BackHandler {
view.browserLayout,
ActionBusFactory.get(this), sessionId,
(activity as HomeActivity).browsingModeManager.isPrivate,
SearchState("", isEditing = false)
SearchState("", isEditing = false),
search_engine_icon
)

toolbarComponent.uiView.view.apply {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
package org.mozilla.fenix.components.toolbar

import android.view.ViewGroup
import android.widget.ImageView
import androidx.core.content.ContextCompat
import kotlinx.android.synthetic.main.component_search.*
import mozilla.components.browser.search.SearchEngine
import mozilla.components.browser.toolbar.BrowserToolbar
import org.mozilla.fenix.DefaultThemeManager
import org.mozilla.fenix.R
Expand All @@ -22,7 +24,8 @@ class ToolbarComponent(
bus: ActionBusFactory,
private val sessionId: String?,
private val isPrivate: Boolean,
override var initialState: SearchState = SearchState("", false)
override var initialState: SearchState = SearchState("", false),
private val engineIconView: ImageView? = null
) :
UIComponent<SearchState, SearchAction, SearchChange>(
bus.getManagedEmitter(SearchAction::class.java),
Expand All @@ -34,10 +37,19 @@ class ToolbarComponent(
override val reducer: Reducer<SearchState, SearchChange> = { state, change ->
when (change) {
is SearchChange.QueryChanged -> state.copy(query = change.query)
is SearchChange.SearchShortcutEngineSelected ->
state.copy(engine = change.engine)
}
}

override fun initView() = ToolbarUIView(sessionId, isPrivate, container, actionEmitter, changesObservable)
override fun initView() = ToolbarUIView(
sessionId,
isPrivate,
container,
actionEmitter,
changesObservable,
engineIconView
)

init {
render(reducer)
Expand All @@ -60,10 +72,14 @@ class ToolbarComponent(
}
}

data class SearchState(val query: String, val isEditing: Boolean) : ViewState
data class SearchState(
val query: String,
val isEditing: Boolean,
val engine: SearchEngine? = null
) : ViewState

sealed class SearchAction : Action {
data class UrlCommitted(val url: String, val session: String?) : SearchAction()
data class UrlCommitted(val url: String, val session: String?, val engine: SearchEngine? = null) : SearchAction()
data class TextChanged(val query: String) : SearchAction()
object ToolbarTapped : SearchAction()
data class ToolbarMenuItemTapped(val item: ToolbarMenu.Item) : SearchAction()
Expand All @@ -72,4 +88,5 @@ sealed class SearchAction : Action {

sealed class SearchChange : Change {
data class QueryChanged(val query: String) : SearchChange()
data class SearchShortcutEngineSelected(val engine: SearchEngine) : SearchChange()
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,18 @@

package org.mozilla.fenix.components.toolbar

import android.graphics.drawable.BitmapDrawable
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.ImageView
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.content.res.pxToDp
import org.jetbrains.anko.backgroundDrawable
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.mvi.UIView
Expand All @@ -22,11 +25,14 @@ class ToolbarUIView(
isPrivate: Boolean,
container: ViewGroup,
actionEmitter: Observer<SearchAction>,
changesObservable: Observable<SearchChange>
changesObservable: Observable<SearchChange>,
private val engineIconView: ImageView? = null
) :
UIView<SearchState, SearchAction, SearchChange>(container, actionEmitter, changesObservable) {

val toolbarIntegration: ToolbarIntegration
var state: SearchState? = null
private set

override val view: BrowserToolbar = LayoutInflater.from(container.context)
.inflate(R.layout.component_search, container, true)
Expand All @@ -38,7 +44,7 @@ class ToolbarUIView(
init {
view.apply {
setOnUrlCommitListener {
actionEmitter.onNext(SearchAction.UrlCommitted(it, sessionId))
actionEmitter.onNext(SearchAction.UrlCommitted(it, sessionId, state?.engine))
false
}
onUrlClicked = {
Expand Down Expand Up @@ -87,14 +93,63 @@ class ToolbarUIView(
}

override fun updateView() = Consumer<SearchState> {
if (it.isEditing) {
view.url = it.query
if (shouldUpdateEngineIcon(it)) {
updateEngineIcon(it)
}

if (shouldClearSearchURL(it)) {
clearSearchURL()
}

if (shouldUpdateEditingState(it)) {
updateEditingState(it)
}

state = it
}

private fun shouldUpdateEngineIcon(newState: SearchState): Boolean {
return newState.isEditing && (engineDidChange(newState) || state == null)
}

private fun updateEngineIcon(newState: SearchState) {
with(view.context) {
val defaultEngineIcon = components.search.searchEngineManager.defaultSearchEngine?.icon
val searchIcon = newState.engine?.icon ?: defaultEngineIcon
val draw = BitmapDrawable(searchIcon)
val iconSize =
containerView?.context!!.resources.getDimension(R.dimen.preference_icon_drawable_size).toInt()
draw.setBounds(0, 0, iconSize, iconSize)
engineIconView?.backgroundDrawable = draw
}
}

private fun shouldClearSearchURL(newState: SearchState): Boolean {
return newState.engine != state?.engine && view.url == newState.query
}

private fun clearSearchURL() {
view.url = ""
view.editMode()
}

private fun shouldUpdateEditingState(newState: SearchState): Boolean {
return !engineDidChange(newState)
}

private fun updateEditingState(newState: SearchState) {
if (newState.isEditing) {
view.url = newState.query
view.editMode()
} else {
view.displayMode()
}
}

private fun engineDidChange(newState: SearchState): Boolean {
return newState.engine != state?.engine
}

companion object {
const val browserActionMarginDp = 8
}
Expand Down
33 changes: 21 additions & 12 deletions app/src/main/java/org/mozilla/fenix/search/SearchFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
package org.mozilla.fenix.search

import android.content.Context
import android.graphics.drawable.BitmapDrawable
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
Expand All @@ -17,13 +16,13 @@ import kotlinx.android.synthetic.main.fragment_search.view.*
import mozilla.components.feature.search.SearchUseCases
import mozilla.components.feature.session.SessionUseCases
import mozilla.components.support.ktx.kotlin.isUrl
import org.jetbrains.anko.backgroundDrawable
import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.utils.ItsNotBrokenSnack
import org.mozilla.fenix.R
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.toolbar.SearchAction
import org.mozilla.fenix.components.toolbar.SearchChange
import org.mozilla.fenix.components.toolbar.SearchState
import org.mozilla.fenix.components.toolbar.ToolbarComponent
import org.mozilla.fenix.components.toolbar.ToolbarUIView
Expand All @@ -35,6 +34,7 @@ import org.mozilla.fenix.mvi.getManagedEmitter
import org.mozilla.fenix.search.awesomebar.AwesomeBarAction
import org.mozilla.fenix.search.awesomebar.AwesomeBarChange
import org.mozilla.fenix.search.awesomebar.AwesomeBarComponent
import org.mozilla.fenix.search.awesomebar.AwesomeBarUIView

class SearchFragment : Fragment() {
private lateinit var toolbarComponent: ToolbarComponent
Expand Down Expand Up @@ -65,7 +65,8 @@ class SearchFragment : Fragment() {
ActionBusFactory.get(this),
sessionId,
isPrivate,
SearchState(url, isEditing = true)
SearchState(url, isEditing = true),
view.search_engine_icon
)

awesomeBarComponent = AwesomeBarComponent(view.search_layout, ActionBusFactory.get(this))
Expand All @@ -82,15 +83,11 @@ class SearchFragment : Fragment() {

view.toolbar_wrapper.clipToOutline = false

val searchIcon = requireComponents.search.searchEngineManager.getDefaultSearchEngine(
requireContext()
).let {
BitmapDrawable(resources, it.icon)
search_shortcuts_button.setOnClickListener {
getManagedEmitter<AwesomeBarChange>().onNext(AwesomeBarChange
.SearchShortcutEnginePicker(!(
(awesomeBarComponent.uiView as AwesomeBarUIView).state?.showShortcutEnginePicker ?: true)))
}

val iconSize = resources.getDimension(R.dimen.preference_icon_drawable_size).toInt()
searchIcon.setBounds(0, 0, iconSize, iconSize)
search_engine_icon.backgroundDrawable = searchIcon
}

override fun onResume() {
Expand All @@ -100,13 +97,17 @@ class SearchFragment : Fragment() {

override fun onStart() {
super.onStart()
subscribeToSearchActions()
subscribeToAwesomeBarActions()
}

private fun subscribeToSearchActions() {
getAutoDisposeObservable<SearchAction>()
.subscribe {
when (it) {
is SearchAction.UrlCommitted -> {
if (it.url.isNotBlank()) {
(activity as HomeActivity).openToBrowserAndLoad(it.url, it.session,
(activity as HomeActivity).openToBrowserAndLoad(it.url, it.session, it.engine,
BrowserDirection.FromSearch)

val event = if (it.url.isUrl()) {
Expand All @@ -126,7 +127,9 @@ class SearchFragment : Fragment() {
}
}
}
}

private fun subscribeToAwesomeBarActions() {
getAutoDisposeObservable<AwesomeBarAction>()
.subscribe {
when (it) {
Expand All @@ -141,6 +144,12 @@ class SearchFragment : Fragment() {
(activity as HomeActivity).openToBrowser(sessionId, BrowserDirection.FromSearch)
requireComponents.analytics.metrics.track(Event.PerformedSearch(true))
}
is AwesomeBarAction.SearchShortcutEngineSelected -> {
getManagedEmitter<AwesomeBarChange>()
.onNext(AwesomeBarChange.SearchShortcutEngineSelected(it.engine))
getManagedEmitter<SearchChange>()
.onNext(SearchChange.SearchShortcutEngineSelected(it.engine))
}
}
}
}
Expand Down
7 changes: 6 additions & 1 deletion app/src/main/java/org/mozilla/fenix/search/SearchLayouts.kt
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,15 @@ internal fun SearchFragment.setOutOfExperimentConstraints(layout: ConstraintLayo
BOTTOM to TOP of UNSET
)
}
search_with_shortcuts {
connect(
TOP to BOTTOM of toolbar_wrapper
)
}
awesomeBar {
connect(
TOP to TOP of UNSET,
TOP to BOTTOM of toolbar_wrapper,
TOP to BOTTOM of search_with_shortcuts,
BOTTOM to TOP of pill_wrapper
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,38 @@ import org.mozilla.fenix.mvi.Reducer
import org.mozilla.fenix.mvi.UIComponent
import org.mozilla.fenix.mvi.ViewState

data class AwesomeBarState(val query: String) : ViewState
data class AwesomeBarState(
val query: String,
val showShortcutEnginePicker: Boolean,
val suggestionEngine: SearchEngine? = null
) : ViewState

sealed class AwesomeBarAction : Action {
data class URLTapped(val url: String) : AwesomeBarAction()
data class SearchTermsTapped(val searchTerms: String, val engine: SearchEngine?) : AwesomeBarAction()
data class SearchTermsTapped(val searchTerms: String, val engine: SearchEngine? = null) : AwesomeBarAction()
data class SearchShortcutEngineSelected(val engine: SearchEngine) : AwesomeBarAction()
}

sealed class AwesomeBarChange : Change {
data class SearchShortcutEngineSelected(val engine: SearchEngine) : AwesomeBarChange()
data class SearchShortcutEnginePicker(val show: Boolean) : AwesomeBarChange()
data class UpdateQuery(val query: String) : AwesomeBarChange()
}

class AwesomeBarComponent(
private val container: ViewGroup,
bus: ActionBusFactory,
override var initialState: AwesomeBarState = AwesomeBarState("")
override var initialState: AwesomeBarState = AwesomeBarState("", false)
) : UIComponent<AwesomeBarState, AwesomeBarAction, AwesomeBarChange>(
bus.getManagedEmitter(AwesomeBarAction::class.java),
bus.getSafeManagedObservable(AwesomeBarChange::class.java)
) {
override val reducer: Reducer<AwesomeBarState, AwesomeBarChange> = { state, change ->
when (change) {
is AwesomeBarChange.SearchShortcutEngineSelected ->
state.copy(suggestionEngine = change.engine, showShortcutEnginePicker = false)
is AwesomeBarChange.SearchShortcutEnginePicker ->
state.copy(showShortcutEnginePicker = change.show)
is AwesomeBarChange.UpdateQuery -> state.copy(query = change.query)
}
}
Expand Down
Loading

0 comments on commit 36af510

Please sign in to comment.