Fixes #290: Integrate new Fenix architecture
parent
8af55652be
commit
273f33b244
|
@ -104,6 +104,7 @@ dependencies {
|
||||||
implementation Deps.mozilla_concept_toolbar
|
implementation Deps.mozilla_concept_toolbar
|
||||||
|
|
||||||
implementation Deps.mozilla_browser_awesomebar
|
implementation Deps.mozilla_browser_awesomebar
|
||||||
|
implementation Deps.mozilla_feature_downloads
|
||||||
implementation Deps.mozilla_browser_domains
|
implementation Deps.mozilla_browser_domains
|
||||||
implementation Deps.mozilla_browser_engine_gecko_nightly
|
implementation Deps.mozilla_browser_engine_gecko_nightly
|
||||||
implementation Deps.mozilla_browser_session
|
implementation Deps.mozilla_browser_session
|
||||||
|
@ -116,6 +117,7 @@ dependencies {
|
||||||
implementation Deps.mozilla_feature_session
|
implementation Deps.mozilla_feature_session
|
||||||
implementation Deps.mozilla_feature_toolbar
|
implementation Deps.mozilla_feature_toolbar
|
||||||
implementation Deps.mozilla_feature_tabs
|
implementation Deps.mozilla_feature_tabs
|
||||||
|
implementation Deps.mozilla_service_fretboard
|
||||||
|
|
||||||
implementation Deps.mozilla_support_ktx
|
implementation Deps.mozilla_support_ktx
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
/* 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
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import mozilla.components.service.fretboard.ExperimentDescriptor
|
||||||
|
|
||||||
|
const val EXPERIMENTS_JSON_FILENAME = "experiments.json"
|
||||||
|
const val EXPERIMENTS_BASE_URL = "https://settings.prod.mozaws.net/v1"
|
||||||
|
const val EXPERIMENTS_BUCKET_NAME = "main"
|
||||||
|
// TODO Change this after fenix-experiments is created
|
||||||
|
const val EXPERIMENTS_COLLECTION_NAME = "focus-experiments"
|
||||||
|
|
||||||
|
object Experiments {
|
||||||
|
val AATestDescriptor = ExperimentDescriptor("AAtest")
|
||||||
|
}
|
||||||
|
|
||||||
|
val Context.app: FenixApplication
|
||||||
|
get() = applicationContext as FenixApplication
|
||||||
|
|
||||||
|
fun Context.isInExperiment(descriptor: ExperimentDescriptor): Boolean =
|
||||||
|
app.fretboard.isInExperiment(this, descriptor)
|
|
@ -5,15 +5,50 @@
|
||||||
package org.mozilla.fenix
|
package org.mozilla.fenix
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import android.content.Context
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import mozilla.components.service.fretboard.Fretboard
|
||||||
|
import mozilla.components.service.fretboard.ValuesProvider
|
||||||
|
import mozilla.components.service.fretboard.source.kinto.KintoExperimentSource
|
||||||
|
import mozilla.components.service.fretboard.storage.flatfile.FlatFileExperimentStorage
|
||||||
|
import mozilla.components.support.base.log.Log
|
||||||
|
import mozilla.components.support.base.log.logger.Logger
|
||||||
|
import mozilla.components.support.base.log.sink.AndroidLogSink
|
||||||
import org.mozilla.fenix.components.Components
|
import org.mozilla.fenix.components.Components
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
class FenixApplication : Application() {
|
class FenixApplication : Application() {
|
||||||
|
lateinit var fretboard: Fretboard
|
||||||
|
|
||||||
val components by lazy { Components(this) }
|
val components by lazy { Components(this) }
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
Log.addSink(AndroidLogSink())
|
||||||
|
|
||||||
setupCrashReporting()
|
setupCrashReporting()
|
||||||
|
loadExperiments()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadExperiments() {
|
||||||
|
val experimentsFile = File(filesDir, EXPERIMENTS_JSON_FILENAME)
|
||||||
|
val experimentSource = KintoExperimentSource(
|
||||||
|
EXPERIMENTS_BASE_URL, EXPERIMENTS_BUCKET_NAME, EXPERIMENTS_COLLECTION_NAME
|
||||||
|
)
|
||||||
|
fretboard = Fretboard(experimentSource, FlatFileExperimentStorage(experimentsFile),
|
||||||
|
object : ValuesProvider() {
|
||||||
|
override fun getClientId(context: Context): String {
|
||||||
|
return "10" // hardcode clientId to determine in or out of experiment
|
||||||
|
}
|
||||||
|
})
|
||||||
|
fretboard.loadExperiments()
|
||||||
|
Logger.debug("Bucket is ${fretboard.getUserBucket(this@FenixApplication)}")
|
||||||
|
Logger.debug("Experiments active: ${fretboard.getExperimentsMap(this@FenixApplication)}")
|
||||||
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
|
fretboard.updateExperiments()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupCrashReporting() {
|
private fun setupCrashReporting() {
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
/* 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
|
package org.mozilla.fenix
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/* 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
|
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/. */
|
file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
package org.mozilla.fenix.browser
|
package org.mozilla.fenix.browser
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.transition.TransitionInflater
|
import android.transition.TransitionInflater
|
||||||
|
@ -10,6 +12,7 @@ import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.accessibility.AccessibilityManager
|
import android.view.accessibility.AccessibilityManager
|
||||||
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import kotlinx.android.synthetic.main.fragment_browser.*
|
import kotlinx.android.synthetic.main.fragment_browser.*
|
||||||
import mozilla.components.feature.downloads.DownloadsFeature
|
import mozilla.components.feature.downloads.DownloadsFeature
|
||||||
|
@ -19,7 +22,6 @@ import mozilla.components.support.ktx.android.arch.lifecycle.addObservers
|
||||||
import org.mozilla.fenix.R
|
import org.mozilla.fenix.R
|
||||||
import org.mozilla.fenix.components.toolbar.ToolbarIntegration
|
import org.mozilla.fenix.components.toolbar.ToolbarIntegration
|
||||||
import org.mozilla.fenix.ext.requireComponents
|
import org.mozilla.fenix.ext.requireComponents
|
||||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
|
||||||
|
|
||||||
class BrowserFragment : Fragment() {
|
class BrowserFragment : Fragment() {
|
||||||
|
|
||||||
|
@ -40,6 +42,7 @@ class BrowserFragment : Fragment() {
|
||||||
sharedElementEnterTransition = TransitionInflater.from(context).inflateTransition(android.R.transition.move)
|
sharedElementEnterTransition = TransitionInflater.from(context).inflateTransition(android.R.transition.move)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("CheckResult")
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
@ -60,17 +63,6 @@ class BrowserFragment : Fragment() {
|
||||||
engineView
|
engineView
|
||||||
)
|
)
|
||||||
|
|
||||||
lifecycle.addObserver(
|
|
||||||
ToolbarIntegration(
|
|
||||||
requireContext(),
|
|
||||||
toolbar,
|
|
||||||
requireComponents.toolbar.shippedDomainsProvider,
|
|
||||||
requireComponents.core.historyStorage
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
lifecycle.addObservers(sessionFeature)
|
|
||||||
|
|
||||||
// Stop toolbar from collapsing if TalkBack is enabled
|
// Stop toolbar from collapsing if TalkBack is enabled
|
||||||
val accessibilityManager = context?.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager
|
val accessibilityManager = context?.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager
|
||||||
if (accessibilityManager.isEnabled) {
|
if (accessibilityManager.isEnabled) {
|
||||||
|
@ -80,7 +72,11 @@ class BrowserFragment : Fragment() {
|
||||||
|
|
||||||
lifecycle.addObservers(
|
lifecycle.addObservers(
|
||||||
downloadsFeature,
|
downloadsFeature,
|
||||||
sessionFeature
|
sessionFeature,
|
||||||
|
ToolbarIntegration(requireContext(),
|
||||||
|
toolbar,
|
||||||
|
requireComponents.toolbar.shippedDomainsProvider,
|
||||||
|
requireComponents.core.historyStorage)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
/* 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.components
|
package org.mozilla.fenix.components
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
|
|
@ -5,20 +5,15 @@
|
||||||
package org.mozilla.fenix.components.toolbar
|
package org.mozilla.fenix.components.toolbar
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.view.LayoutInflater
|
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.core.view.ViewCompat
|
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.LifecycleObserver
|
import androidx.lifecycle.LifecycleObserver
|
||||||
import androidx.lifecycle.OnLifecycleEvent
|
import androidx.lifecycle.OnLifecycleEvent
|
||||||
import androidx.navigation.Navigation
|
import androidx.navigation.Navigation
|
||||||
import androidx.navigation.fragment.FragmentNavigator
|
|
||||||
import mozilla.components.browser.domains.autocomplete.DomainAutocompleteProvider
|
import mozilla.components.browser.domains.autocomplete.DomainAutocompleteProvider
|
||||||
import mozilla.components.browser.toolbar.BrowserToolbar
|
import mozilla.components.browser.toolbar.BrowserToolbar
|
||||||
import mozilla.components.concept.storage.HistoryStorage
|
import mozilla.components.concept.storage.HistoryStorage
|
||||||
import mozilla.components.feature.toolbar.ToolbarAutocompleteFeature
|
import mozilla.components.feature.toolbar.ToolbarAutocompleteFeature
|
||||||
import mozilla.components.feature.toolbar.ToolbarFeature
|
import mozilla.components.feature.toolbar.ToolbarFeature
|
||||||
import mozilla.components.support.ktx.android.content.res.pxToDp
|
|
||||||
import org.mozilla.fenix.R
|
import org.mozilla.fenix.R
|
||||||
import org.mozilla.fenix.ext.application
|
import org.mozilla.fenix.ext.application
|
||||||
import org.mozilla.fenix.ext.components
|
import org.mozilla.fenix.ext.components
|
||||||
|
@ -33,11 +28,6 @@ class ToolbarIntegration(
|
||||||
init {
|
init {
|
||||||
toolbar.setMenuBuilder(context.components.toolbar.menuBuilder)
|
toolbar.setMenuBuilder(context.components.toolbar.menuBuilder)
|
||||||
|
|
||||||
toolbar.browserActionMargin = toolbar.resources.pxToDp(browserActionMarginDp)
|
|
||||||
toolbar.textColor = ContextCompat.getColor(context, R.color.searchText)
|
|
||||||
toolbar.urlBoxView = LayoutInflater.from(context).inflate(R.layout.layout_url_backround, null)
|
|
||||||
toolbar.urlBoxMargin = toolbar.resources.pxToDp(urlBoxMargin)
|
|
||||||
|
|
||||||
val home = BrowserToolbar.Button(
|
val home = BrowserToolbar.Button(
|
||||||
context.resources.getDrawable(
|
context.resources.getDrawable(
|
||||||
R.drawable.ic_home,
|
R.drawable.ic_home,
|
||||||
|
@ -49,15 +39,6 @@ class ToolbarIntegration(
|
||||||
|
|
||||||
toolbar.addBrowserAction(home)
|
toolbar.addBrowserAction(home)
|
||||||
|
|
||||||
toolbar.onUrlClicked = {
|
|
||||||
val extras = FragmentNavigator.Extras.Builder().addSharedElement(
|
|
||||||
toolbar, ViewCompat.getTransitionName(toolbar)!!
|
|
||||||
).build()
|
|
||||||
Navigation.findNavController(toolbar)
|
|
||||||
.navigate(R.id.action_browserFragment_to_searchFragment, null, null, extras)
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
ToolbarAutocompleteFeature(toolbar).apply {
|
ToolbarAutocompleteFeature(toolbar).apply {
|
||||||
addDomainProvider(domainAutocompleteProvider)
|
addDomainProvider(domainAutocompleteProvider)
|
||||||
addHistoryStorageProvider(historyStorage)
|
addHistoryStorageProvider(historyStorage)
|
||||||
|
|
|
@ -28,13 +28,16 @@ class HomeFragment : Fragment() {
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View? {
|
): View? {
|
||||||
val view = inflater.inflate(R.layout.fragment_home, container, false)
|
val view = inflater.inflate(R.layout.fragment_home, container, false)
|
||||||
SessionsComponent(view.homeLayout, ActionBusFactory.get(this)).setup()
|
SessionsComponent(view.homeLayout, ActionBusFactory.get(this))
|
||||||
|
ActionBusFactory.get(this).logMergedObservables()
|
||||||
return view
|
return view
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
layoutComponents(view.homeLayout)
|
||||||
|
|
||||||
val searchIcon = requireComponents.search.searchEngineManager.getDefaultSearchEngine(requireContext()).let {
|
val searchIcon = requireComponents.search.searchEngineManager.getDefaultSearchEngine(requireContext()).let {
|
||||||
BitmapDrawable(resources, it.icon)
|
BitmapDrawable(resources, it.icon)
|
||||||
}
|
}
|
||||||
|
@ -45,7 +48,6 @@ class HomeFragment : Fragment() {
|
||||||
toolbar.setOnClickListener { it ->
|
toolbar.setOnClickListener { it ->
|
||||||
Navigation.findNavController(it).navigate(R.id.action_homeFragment_to_searchFragment, null, null)
|
Navigation.findNavController(it).navigate(R.id.action_homeFragment_to_searchFragment, null, null)
|
||||||
}
|
}
|
||||||
layoutComponents(homeLayout)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
@ -7,16 +7,19 @@ package org.mozilla.fenix.home.sessions
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import mozilla.components.browser.session.Session
|
import mozilla.components.browser.session.Session
|
||||||
|
import mozilla.components.support.base.log.logger.Logger
|
||||||
import org.mozilla.fenix.mvi.Action
|
import org.mozilla.fenix.mvi.Action
|
||||||
import org.mozilla.fenix.mvi.ActionBusFactory
|
import org.mozilla.fenix.mvi.ActionBusFactory
|
||||||
import org.mozilla.fenix.mvi.Change
|
import org.mozilla.fenix.mvi.Change
|
||||||
import org.mozilla.fenix.mvi.UIComponent
|
import org.mozilla.fenix.mvi.UIComponent
|
||||||
import org.mozilla.fenix.mvi.ViewState
|
import org.mozilla.fenix.mvi.ViewState
|
||||||
|
|
||||||
class SessionsComponent(private val container: ViewGroup, override val bus: ActionBusFactory) :
|
class SessionsComponent(
|
||||||
UIComponent<SessionsState, SessionsAction, SessionsChange>(bus) {
|
private val container: ViewGroup,
|
||||||
|
override val bus: ActionBusFactory,
|
||||||
override var initialState: SessionsState = SessionsState(emptyList())
|
override var initialState: SessionsState = SessionsState(emptyList())
|
||||||
|
) :
|
||||||
|
UIComponent<SessionsState, SessionsAction, SessionsChange>(bus) {
|
||||||
|
|
||||||
override val reducer: (SessionsState, SessionsChange) -> SessionsState = { state, change ->
|
override val reducer: (SessionsState, SessionsChange) -> SessionsState = { state, change ->
|
||||||
when (change) {
|
when (change) {
|
||||||
|
@ -26,9 +29,17 @@ class SessionsComponent(private val container: ViewGroup, override val bus: Acti
|
||||||
|
|
||||||
override fun initView() = SessionsUIView(container, bus)
|
override fun initView() = SessionsUIView(container, bus)
|
||||||
|
|
||||||
|
init {
|
||||||
|
setup()
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("CheckResult")
|
@SuppressLint("CheckResult")
|
||||||
fun setup(): SessionsComponent {
|
fun setup(): SessionsComponent {
|
||||||
render(reducer)
|
render(reducer)
|
||||||
|
getUserInteractionEvents<SessionsAction>()
|
||||||
|
.subscribe {
|
||||||
|
Logger("SessionsComponent").debug(it.toString())
|
||||||
|
}
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,17 @@
|
||||||
|
/* 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.home.sessions
|
package org.mozilla.fenix.home.sessions
|
||||||
|
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout.LayoutParams.PARENT_ID
|
import androidx.constraintlayout.widget.ConstraintLayout.LayoutParams.PARENT_ID
|
||||||
import kotlinx.android.synthetic.main.component_sessions.*
|
import kotlinx.android.synthetic.main.component_sessions.*
|
||||||
import kotlinx.android.synthetic.main.fragment_home.*
|
import kotlinx.android.synthetic.main.fragment_home.*
|
||||||
import org.jetbrains.anko.constraint.layout.ConstraintSetBuilder.Side.TOP
|
|
||||||
import org.jetbrains.anko.constraint.layout.ConstraintSetBuilder.Side.BOTTOM
|
import org.jetbrains.anko.constraint.layout.ConstraintSetBuilder.Side.BOTTOM
|
||||||
import org.jetbrains.anko.constraint.layout.ConstraintSetBuilder.Side.START
|
|
||||||
import org.jetbrains.anko.constraint.layout.ConstraintSetBuilder.Side.END
|
import org.jetbrains.anko.constraint.layout.ConstraintSetBuilder.Side.END
|
||||||
|
import org.jetbrains.anko.constraint.layout.ConstraintSetBuilder.Side.START
|
||||||
|
import org.jetbrains.anko.constraint.layout.ConstraintSetBuilder.Side.TOP
|
||||||
import org.jetbrains.anko.constraint.layout.applyConstraintSet
|
import org.jetbrains.anko.constraint.layout.applyConstraintSet
|
||||||
import org.mozilla.fenix.home.HomeFragment
|
import org.mozilla.fenix.home.HomeFragment
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ class SessionsUIView(container: ViewGroup, bus: ActionBusFactory) :
|
||||||
.findViewById(R.id.session_list)
|
.findViewById(R.id.session_list)
|
||||||
|
|
||||||
private val sessionsAdapter = SessionsAdapter()
|
private val sessionsAdapter = SessionsAdapter()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
view.apply {
|
view.apply {
|
||||||
layoutManager = LinearLayoutManager(container.context)
|
layoutManager = LinearLayoutManager(container.context)
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
/* 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.annotation.SuppressLint
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import kotlinx.android.synthetic.main.fragment_browser.*
|
||||||
|
import mozilla.components.browser.toolbar.BrowserToolbar
|
||||||
|
import mozilla.components.support.base.log.logger.Logger
|
||||||
|
import org.mozilla.fenix.mvi.Action
|
||||||
|
import org.mozilla.fenix.mvi.ActionBusFactory
|
||||||
|
import org.mozilla.fenix.mvi.Change
|
||||||
|
import org.mozilla.fenix.mvi.Reducer
|
||||||
|
import org.mozilla.fenix.mvi.UIComponent
|
||||||
|
import org.mozilla.fenix.mvi.ViewState
|
||||||
|
|
||||||
|
class SearchComponent(
|
||||||
|
private val container: ViewGroup,
|
||||||
|
override val bus: ActionBusFactory,
|
||||||
|
private val onEditComplete: (View) -> Unit,
|
||||||
|
override var initialState: SearchState = SearchState("")
|
||||||
|
) :
|
||||||
|
UIComponent<SearchState, SearchAction, SearchChange>(bus) {
|
||||||
|
|
||||||
|
override val reducer: Reducer<SearchState, SearchChange> = { state, change ->
|
||||||
|
when (change) {
|
||||||
|
is SearchChange.Changed -> state // TODO handle state changes here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun initView() = SearchUIView(container, bus)
|
||||||
|
init {
|
||||||
|
setup()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getView(): BrowserToolbar = uiView.toolbar
|
||||||
|
fun editMode() = getView().editMode()
|
||||||
|
|
||||||
|
@SuppressLint("CheckResult")
|
||||||
|
fun setup(): SearchComponent {
|
||||||
|
render(reducer)
|
||||||
|
getUserInteractionEvents<SearchAction>()
|
||||||
|
.subscribe {
|
||||||
|
Logger("SearchComponent").debug(it.toString())
|
||||||
|
when (it) {
|
||||||
|
is SearchAction.EditComplete -> {
|
||||||
|
onEditComplete.invoke(getView())
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class SearchState(val term: String) : ViewState
|
||||||
|
sealed class SearchAction : Action {
|
||||||
|
object UrlClicked : SearchAction()
|
||||||
|
object EditComplete : SearchAction()
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class SearchChange : Change {
|
||||||
|
object Changed : SearchChange()
|
||||||
|
}
|
|
@ -1,72 +1,64 @@
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/* 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
|
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/. */
|
file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
package org.mozilla.fenix.search
|
package org.mozilla.fenix.search
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.navigation.Navigation
|
import androidx.navigation.Navigation
|
||||||
import kotlinx.android.synthetic.main.fragment_search.*
|
import kotlinx.android.synthetic.main.fragment_search.*
|
||||||
|
import kotlinx.android.synthetic.main.fragment_search.view.*
|
||||||
import mozilla.components.browser.domains.autocomplete.ShippedDomainsProvider
|
import mozilla.components.browser.domains.autocomplete.ShippedDomainsProvider
|
||||||
import mozilla.components.feature.awesomebar.AwesomeBarFeature
|
|
||||||
import mozilla.components.feature.awesomebar.provider.SearchSuggestionProvider
|
|
||||||
import org.mozilla.fenix.R
|
import org.mozilla.fenix.R
|
||||||
import org.mozilla.fenix.components.toolbar.ToolbarIntegration
|
import org.mozilla.fenix.components.toolbar.ToolbarIntegration
|
||||||
import org.mozilla.fenix.ext.requireComponents
|
import org.mozilla.fenix.ext.requireComponents
|
||||||
|
import org.mozilla.fenix.mvi.ActionBusFactory
|
||||||
|
|
||||||
class SearchFragment : Fragment() {
|
class SearchFragment : Fragment() {
|
||||||
private lateinit var awesomeBarFeature: AwesomeBarFeature
|
private lateinit var searchComponent: SearchComponent
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View? {
|
): View? {
|
||||||
return inflater.inflate(R.layout.fragment_search, container, false)
|
val view = inflater.inflate(R.layout.fragment_search, container, false)
|
||||||
|
searchComponent = SearchComponent(view.toolbar_wrapper, ActionBusFactory.get(this),
|
||||||
|
{ v -> transitionToBrowser(v) })
|
||||||
|
return view
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
toolbar.editMode()
|
searchComponent.editMode()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("CheckResult")
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
layoutComponents(view.search_layout)
|
||||||
|
|
||||||
lifecycle.addObserver(
|
lifecycle.addObserver(
|
||||||
ToolbarIntegration(
|
ToolbarIntegration(
|
||||||
requireContext(),
|
requireContext(),
|
||||||
toolbar,
|
searchComponent.getView(),
|
||||||
ShippedDomainsProvider().also { it.initialize(requireContext()) },
|
ShippedDomainsProvider().also { it.initialize(requireContext()) },
|
||||||
requireComponents.core.historyStorage
|
requireComponents.core.historyStorage
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
awesomeBarFeature = AwesomeBarFeature(awesomeBar, toolbar, null, onEditComplete = ::userDidSearch)
|
|
||||||
.addClipboardProvider(requireContext(), requireComponents.useCases.sessionUseCases.loadUrl)
|
|
||||||
.addSearchProvider(
|
|
||||||
requireComponents.search.searchEngineManager.getDefaultSearchEngine(requireContext()),
|
|
||||||
requireComponents.useCases.searchUseCases.defaultSearch,
|
|
||||||
SearchSuggestionProvider.Mode.MULTIPLE_SUGGESTIONS)
|
|
||||||
.addSessionProvider(
|
|
||||||
requireComponents.core.sessionManager,
|
|
||||||
requireComponents.useCases.tabsUseCases.selectTab)
|
|
||||||
|
|
||||||
toolbar_wrapper.clipToOutline = false
|
toolbar_wrapper.clipToOutline = false
|
||||||
toolbar.apply {
|
|
||||||
textColor = ContextCompat.getColor(context, R.color.searchText)
|
|
||||||
textSize = toolbarTextSizeSp
|
|
||||||
hint = context.getString(R.string.search_hint)
|
|
||||||
hintColor = ContextCompat.getColor(context, R.color.searchText)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun userDidSearch() {
|
private fun transitionToBrowser(toolbar: View) {
|
||||||
Navigation.findNavController(toolbar).navigate(R.id.action_searchFragment_to_browserFragment, null, null)
|
Navigation.findNavController(toolbar)
|
||||||
|
.navigate(R.id.action_searchFragment_to_browserFragment, null, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
/* 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 androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
import androidx.constraintlayout.widget.ConstraintLayout.LayoutParams.PARENT_ID
|
||||||
|
import androidx.constraintlayout.widget.ConstraintLayout.LayoutParams.UNSET
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import kotlinx.android.synthetic.main.fragment_search.*
|
||||||
|
import mozilla.components.support.base.log.logger.Logger
|
||||||
|
import org.jetbrains.anko.constraint.layout.ConstraintSetBuilder.Side.BOTTOM
|
||||||
|
import org.jetbrains.anko.constraint.layout.ConstraintSetBuilder.Side.TOP
|
||||||
|
import org.jetbrains.anko.constraint.layout.applyConstraintSet
|
||||||
|
import org.mozilla.fenix.Experiments.AATestDescriptor
|
||||||
|
import org.mozilla.fenix.isInExperiment
|
||||||
|
|
||||||
|
internal fun SearchFragment.layoutComponents(layout: ConstraintLayout) {
|
||||||
|
context?.let {
|
||||||
|
when {
|
||||||
|
it.isInExperiment(AATestDescriptor) -> {
|
||||||
|
setInExperimentConstraints(layout)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
setOutOfExperimentConstraints(layout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // we're unattached if context is null
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun SearchFragment.setInExperimentConstraints(layout: ConstraintLayout) {
|
||||||
|
Logger.debug("Loading in experiment constraints")
|
||||||
|
layout.applyConstraintSet {
|
||||||
|
toolbar_wrapper {
|
||||||
|
connect(
|
||||||
|
TOP to TOP of UNSET,
|
||||||
|
BOTTOM to TOP of pill_wrapper
|
||||||
|
)
|
||||||
|
}
|
||||||
|
awesomeBar {
|
||||||
|
connect(
|
||||||
|
TOP to TOP of PARENT_ID,
|
||||||
|
BOTTOM to TOP of toolbar_wrapper
|
||||||
|
)
|
||||||
|
}
|
||||||
|
(awesomeBar.layoutManager as? LinearLayoutManager)?.reverseLayout = true
|
||||||
|
pill_wrapper {
|
||||||
|
connect(
|
||||||
|
BOTTOM to BOTTOM of PARENT_ID
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun SearchFragment.setOutOfExperimentConstraints(layout: ConstraintLayout) {
|
||||||
|
Logger.debug("Loading out of experiment constraints")
|
||||||
|
layout.applyConstraintSet {
|
||||||
|
toolbar_wrapper {
|
||||||
|
connect(
|
||||||
|
TOP to TOP of PARENT_ID,
|
||||||
|
BOTTOM to TOP of UNSET
|
||||||
|
)
|
||||||
|
}
|
||||||
|
awesomeBar {
|
||||||
|
connect(
|
||||||
|
TOP to TOP of UNSET,
|
||||||
|
TOP to BOTTOM of toolbar_wrapper,
|
||||||
|
BOTTOM to TOP of pill_wrapper
|
||||||
|
)
|
||||||
|
}
|
||||||
|
(awesomeBar.layoutManager as? LinearLayoutManager)?.reverseLayout = false
|
||||||
|
pill_wrapper {
|
||||||
|
connect(
|
||||||
|
BOTTOM to BOTTOM of PARENT_ID
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
/* 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.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import io.reactivex.functions.Consumer
|
||||||
|
import kotlinx.android.synthetic.main.fragment_search.view.*
|
||||||
|
import mozilla.components.browser.toolbar.BrowserToolbar
|
||||||
|
import mozilla.components.feature.awesomebar.AwesomeBarFeature
|
||||||
|
import mozilla.components.feature.awesomebar.provider.SearchSuggestionProvider
|
||||||
|
import mozilla.components.support.ktx.android.content.res.pxToDp
|
||||||
|
import org.mozilla.fenix.R
|
||||||
|
import org.mozilla.fenix.ext.components
|
||||||
|
import org.mozilla.fenix.mvi.ActionBusFactory
|
||||||
|
import org.mozilla.fenix.mvi.UIView
|
||||||
|
|
||||||
|
class SearchUIView(container: ViewGroup, bus: ActionBusFactory) :
|
||||||
|
UIView<SearchState>(container, bus) {
|
||||||
|
|
||||||
|
override val view: BrowserToolbar = LayoutInflater.from(container.context)
|
||||||
|
.inflate(R.layout.component_search, container, true)
|
||||||
|
.findViewById(R.id.toolbar)
|
||||||
|
|
||||||
|
private val urlBackground = LayoutInflater.from(container.context)
|
||||||
|
.inflate(R.layout.layout_url_backround, container, false)
|
||||||
|
|
||||||
|
init {
|
||||||
|
view.apply {
|
||||||
|
onUrlClicked = {
|
||||||
|
bus.emit(SearchAction::class.java, SearchAction.UrlClicked)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
browserActionMargin = resources.pxToDp(browserActionMarginDp)
|
||||||
|
urlBoxView = urlBackground
|
||||||
|
urlBoxMargin = this.resources.pxToDp(urlBoxMarginDp)
|
||||||
|
|
||||||
|
textColor = ContextCompat.getColor(context, R.color.searchText)
|
||||||
|
textSize = toolbarTextSizeSp
|
||||||
|
hint = context.getString(R.string.search_hint)
|
||||||
|
hintColor = ContextCompat.getColor(context, R.color.searchText)
|
||||||
|
}
|
||||||
|
|
||||||
|
with(container.context) {
|
||||||
|
AwesomeBarFeature(container.rootView.awesomeBar, view, null,
|
||||||
|
onEditComplete = { bus.emit(SearchAction::class.java, SearchAction.EditComplete) })
|
||||||
|
.addClipboardProvider(this, components.useCases.sessionUseCases.loadUrl)
|
||||||
|
.addSearchProvider(
|
||||||
|
components.search.searchEngineManager.getDefaultSearchEngine(this),
|
||||||
|
components.useCases.searchUseCases.defaultSearch,
|
||||||
|
SearchSuggestionProvider.Mode.MULTIPLE_SUGGESTIONS
|
||||||
|
)
|
||||||
|
.addSessionProvider(
|
||||||
|
components.core.sessionManager,
|
||||||
|
components.useCases.tabsUseCases.selectTab
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updateView() = Consumer<SearchState> {
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val toolbarTextSizeSp = 14f
|
||||||
|
const val browserActionMarginDp = 8
|
||||||
|
const val urlBoxMarginDp = 8
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<mozilla.components.browser.toolbar.BrowserToolbar
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:layout_margin="8dp"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:focusableInTouchMode="true"/>
|
|
@ -1,6 +1,7 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:id="@+id/session_list"
|
android:id="@+id/session_list"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
|
|
|
@ -8,9 +8,10 @@
|
||||||
xmlns:mozac="http://schemas.android.com/apk/res-auto"
|
xmlns:mozac="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:context=".search.SearchFragment">
|
tools:context=".search.SearchFragment"
|
||||||
|
android:id="@+id/search_layout">
|
||||||
|
|
||||||
<FrameLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:id="@+id/toolbar_wrapper"
|
android:id="@+id/toolbar_wrapper"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
@ -21,17 +22,7 @@
|
||||||
android:outlineProvider="paddedBounds"
|
android:outlineProvider="paddedBounds"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent">
|
app:layout_constraintTop_toTopOf="parent"/>
|
||||||
|
|
||||||
<mozilla.components.browser.toolbar.BrowserToolbar
|
|
||||||
android:id="@+id/toolbar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="40dp"
|
|
||||||
android:layout_margin="8dp"
|
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true"
|
|
||||||
android:focusableInTouchMode="true" />
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
<mozilla.components.browser.awesomebar.BrowserAwesomeBar
|
<mozilla.components.browser.awesomebar.BrowserAwesomeBar
|
||||||
android:id="@+id/awesomeBar"
|
android:id="@+id/awesomeBar"
|
||||||
|
|
|
@ -13,7 +13,8 @@
|
||||||
|
|
||||||
<fragment android:id="@+id/searchFragment" android:name="org.mozilla.fenix.search.SearchFragment"
|
<fragment android:id="@+id/searchFragment" android:name="org.mozilla.fenix.search.SearchFragment"
|
||||||
android:label="fragment_search" tools:layout="@layout/fragment_search">
|
android:label="fragment_search" tools:layout="@layout/fragment_search">
|
||||||
<action android:id="@+id/action_searchFragment_to_browserFragment" app:destination="@id/browserFragment"/>
|
<action android:id="@+id/action_searchFragment_to_browserFragment" app:destination="@id/browserFragment"
|
||||||
|
app:popUpTo="@id/homeFragment"/>
|
||||||
</fragment>
|
</fragment>
|
||||||
|
|
||||||
<fragment android:id="@+id/browserFragment" android:name="org.mozilla.fenix.browser.BrowserFragment"
|
<fragment android:id="@+id/browserFragment" android:name="org.mozilla.fenix.browser.BrowserFragment"
|
||||||
|
|
|
@ -73,6 +73,8 @@ object Deps {
|
||||||
const val mozilla_feature_prompts = "org.mozilla.components:feature-prompts:${Versions.mozilla_android_components}"
|
const val mozilla_feature_prompts = "org.mozilla.components:feature-prompts:${Versions.mozilla_android_components}"
|
||||||
const val mozilla_feature_toolbar = "org.mozilla.components:feature-toolbar:${Versions.mozilla_android_components}"
|
const val mozilla_feature_toolbar = "org.mozilla.components:feature-toolbar:${Versions.mozilla_android_components}"
|
||||||
|
|
||||||
|
const val mozilla_service_fretboard = "org.mozilla.components:service-fretboard:${Versions.mozilla_android_components}"
|
||||||
|
|
||||||
const val mozilla_lib_crash = "org.mozilla.components:lib-crash:${Versions.mozilla_android_components}"
|
const val mozilla_lib_crash = "org.mozilla.components:lib-crash:${Versions.mozilla_android_components}"
|
||||||
|
|
||||||
const val mozilla_support_ktx = "org.mozilla.components:support-ktx:${Versions.mozilla_android_components}"
|
const val mozilla_support_ktx = "org.mozilla.components:support-ktx:${Versions.mozilla_android_components}"
|
||||||
|
|
Loading…
Reference in New Issue