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

Closes #875: Adds shortcuts #882

Merged
merged 6 commits into from
Mar 29, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -87,7 +88,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)) {
sblatz marked this conversation as resolved.
Show resolved Hide resolved
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