parent
27d8c09def
commit
ccbc14a71f
|
@ -51,4 +51,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
- #1429 - Updated site permissions ui for MVP
|
- #1429 - Updated site permissions ui for MVP
|
||||||
- #1599 - Fixed a crash creating a bookmark for a custom tab
|
- #1599 - Fixed a crash creating a bookmark for a custom tab
|
||||||
- #1414 - Fixed site permissions settings getting reset in Android 6.
|
- #1414 - Fixed site permissions settings getting reset in Android 6.
|
||||||
|
- #1994 - Made app state persist better when rotating the screen
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
|
@ -337,6 +337,7 @@ dependencies {
|
||||||
implementation Deps.androidx_navigation_ui
|
implementation Deps.androidx_navigation_ui
|
||||||
implementation Deps.androidx_recyclerview
|
implementation Deps.androidx_recyclerview
|
||||||
implementation Deps.androidx_lifecycle_viewmodel_ktx
|
implementation Deps.androidx_lifecycle_viewmodel_ktx
|
||||||
|
implementation Deps.androidx_lifecycle_viewmodel_ss
|
||||||
implementation Deps.androidx_core
|
implementation Deps.androidx_core
|
||||||
implementation Deps.androidx_transition
|
implementation Deps.androidx_transition
|
||||||
|
|
||||||
|
|
|
@ -132,6 +132,7 @@ class BrowserFragment : Fragment(), BackHandler, CoroutineScope,
|
||||||
|
|
||||||
toolbarComponent = ToolbarComponent(
|
toolbarComponent = ToolbarComponent(
|
||||||
view.browserLayout,
|
view.browserLayout,
|
||||||
|
this,
|
||||||
ActionBusFactory.get(this), customTabSessionId,
|
ActionBusFactory.get(this), customTabSessionId,
|
||||||
(activity as HomeActivity).browsingModeManager.isPrivate,
|
(activity as HomeActivity).browsingModeManager.isPrivate,
|
||||||
SearchState("", getSessionById()?.searchTerms ?: "", isEditing = false),
|
SearchState("", getSessionById()?.searchTerms ?: "", isEditing = false),
|
||||||
|
@ -156,6 +157,7 @@ class BrowserFragment : Fragment(), BackHandler, CoroutineScope,
|
||||||
|
|
||||||
QuickActionComponent(
|
QuickActionComponent(
|
||||||
view.nestedScrollQuickAction,
|
view.nestedScrollQuickAction,
|
||||||
|
this,
|
||||||
ActionBusFactory.get(this),
|
ActionBusFactory.get(this),
|
||||||
QuickActionState(
|
QuickActionState(
|
||||||
readable = getSessionById()?.readerable ?: false,
|
readable = getSessionById()?.readerable ?: false,
|
||||||
|
|
|
@ -5,6 +5,10 @@ package org.mozilla.fenix.collections
|
||||||
file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import io.reactivex.Observable
|
||||||
import org.mozilla.fenix.home.sessioncontrol.Tab
|
import org.mozilla.fenix.home.sessioncontrol.Tab
|
||||||
import org.mozilla.fenix.home.sessioncontrol.TabCollection
|
import org.mozilla.fenix.home.sessioncontrol.TabCollection
|
||||||
import org.mozilla.fenix.mvi.Action
|
import org.mozilla.fenix.mvi.Action
|
||||||
|
@ -12,6 +16,7 @@ import org.mozilla.fenix.mvi.ActionBusFactory
|
||||||
import org.mozilla.fenix.mvi.Change
|
import org.mozilla.fenix.mvi.Change
|
||||||
import org.mozilla.fenix.mvi.Reducer
|
import org.mozilla.fenix.mvi.Reducer
|
||||||
import org.mozilla.fenix.mvi.UIComponent
|
import org.mozilla.fenix.mvi.UIComponent
|
||||||
|
import org.mozilla.fenix.mvi.UIComponentViewModel
|
||||||
import org.mozilla.fenix.mvi.ViewState
|
import org.mozilla.fenix.mvi.ViewState
|
||||||
|
|
||||||
sealed class SaveCollectionStep {
|
sealed class SaveCollectionStep {
|
||||||
|
@ -51,34 +56,63 @@ sealed class CollectionCreationAction : Action {
|
||||||
|
|
||||||
class CollectionCreationComponent(
|
class CollectionCreationComponent(
|
||||||
private val container: ViewGroup,
|
private val container: ViewGroup,
|
||||||
|
owner: Fragment,
|
||||||
bus: ActionBusFactory,
|
bus: ActionBusFactory,
|
||||||
override var initialState: CollectionCreationState = CollectionCreationState()
|
override var initialState: CollectionCreationState = CollectionCreationState()
|
||||||
) : UIComponent<CollectionCreationState, CollectionCreationAction, CollectionCreationChange>(
|
) : UIComponent<CollectionCreationState, CollectionCreationAction, CollectionCreationChange>(
|
||||||
|
owner,
|
||||||
bus.getManagedEmitter(CollectionCreationAction::class.java),
|
bus.getManagedEmitter(CollectionCreationAction::class.java),
|
||||||
bus.getSafeManagedObservable(CollectionCreationChange::class.java)
|
bus.getSafeManagedObservable(CollectionCreationChange::class.java)
|
||||||
) {
|
) {
|
||||||
override val reducer: Reducer<CollectionCreationState, CollectionCreationChange> =
|
|
||||||
{ state, change ->
|
|
||||||
when (change) {
|
|
||||||
is CollectionCreationChange.AddAllTabs -> state.copy(selectedTabs = state.tabs.toSet())
|
|
||||||
is CollectionCreationChange.TabListChange -> state.copy(tabs = change.tabs)
|
|
||||||
is CollectionCreationChange.TabAdded -> {
|
|
||||||
val selectedTabs = state.selectedTabs + setOf(change.tab)
|
|
||||||
state.copy(selectedTabs = selectedTabs)
|
|
||||||
}
|
|
||||||
is CollectionCreationChange.TabRemoved -> {
|
|
||||||
val selectedTabs = state.selectedTabs - setOf(change.tab)
|
|
||||||
state.copy(selectedTabs = selectedTabs)
|
|
||||||
}
|
|
||||||
is CollectionCreationChange.StepChanged -> {
|
|
||||||
state.copy(saveCollectionStep = change.saveCollectionStep)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun initView() = CollectionCreationUIView(container, actionEmitter, changesObservable)
|
override fun initView() = CollectionCreationUIView(container, actionEmitter, changesObservable)
|
||||||
|
|
||||||
|
override fun render(): Observable<CollectionCreationState> =
|
||||||
|
ViewModelProvider(owner, CollectionCreationViewModel.Factory(initialState, changesObservable)).get(
|
||||||
|
CollectionCreationViewModel::class.java
|
||||||
|
).render(uiView)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
render(reducer)
|
render()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CollectionCreationViewModel(
|
||||||
|
initialState: CollectionCreationState,
|
||||||
|
changesObservable: Observable<CollectionCreationChange>
|
||||||
|
) :
|
||||||
|
UIComponentViewModel<CollectionCreationState, CollectionCreationAction, CollectionCreationChange>(
|
||||||
|
initialState,
|
||||||
|
changesObservable,
|
||||||
|
reducer
|
||||||
|
) {
|
||||||
|
|
||||||
|
class Factory(
|
||||||
|
private val initialState: CollectionCreationState,
|
||||||
|
private val changesObservable: Observable<CollectionCreationChange>
|
||||||
|
) : ViewModelProvider.Factory {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
override fun <T : ViewModel?> create(modelClass: Class<T>): T =
|
||||||
|
CollectionCreationViewModel(initialState, changesObservable) as T
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val reducer: Reducer<CollectionCreationState, CollectionCreationChange> =
|
||||||
|
{ state, change ->
|
||||||
|
when (change) {
|
||||||
|
is CollectionCreationChange.AddAllTabs -> state.copy(selectedTabs = state.tabs.toSet())
|
||||||
|
is CollectionCreationChange.TabListChange -> state.copy(tabs = change.tabs)
|
||||||
|
is CollectionCreationChange.TabAdded -> {
|
||||||
|
val selectedTabs = state.selectedTabs + setOf(change.tab)
|
||||||
|
state.copy(selectedTabs = selectedTabs)
|
||||||
|
}
|
||||||
|
is CollectionCreationChange.TabRemoved -> {
|
||||||
|
val selectedTabs = state.selectedTabs - setOf(change.tab)
|
||||||
|
state.copy(selectedTabs = selectedTabs)
|
||||||
|
}
|
||||||
|
is CollectionCreationChange.StepChanged -> {
|
||||||
|
state.copy(saveCollectionStep = change.saveCollectionStep)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,6 +50,7 @@ class CreateCollectionFragment : DialogFragment() {
|
||||||
|
|
||||||
collectionCreationComponent = CollectionCreationComponent(
|
collectionCreationComponent = CollectionCreationComponent(
|
||||||
view.create_collection_wrapper,
|
view.create_collection_wrapper,
|
||||||
|
this,
|
||||||
ActionBusFactory.get(this),
|
ActionBusFactory.get(this),
|
||||||
CollectionCreationState(
|
CollectionCreationState(
|
||||||
tabs = tabs,
|
tabs = tabs,
|
||||||
|
|
|
@ -7,6 +7,11 @@ package org.mozilla.fenix.components.toolbar
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.lifecycle.ViewModelProviders
|
||||||
|
import io.reactivex.Observable
|
||||||
import kotlinx.android.synthetic.main.component_search.*
|
import kotlinx.android.synthetic.main.component_search.*
|
||||||
import mozilla.components.browser.search.SearchEngine
|
import mozilla.components.browser.search.SearchEngine
|
||||||
import mozilla.components.browser.toolbar.BrowserToolbar
|
import mozilla.components.browser.toolbar.BrowserToolbar
|
||||||
|
@ -17,10 +22,12 @@ import org.mozilla.fenix.mvi.ActionBusFactory
|
||||||
import org.mozilla.fenix.mvi.Change
|
import org.mozilla.fenix.mvi.Change
|
||||||
import org.mozilla.fenix.mvi.Reducer
|
import org.mozilla.fenix.mvi.Reducer
|
||||||
import org.mozilla.fenix.mvi.UIComponent
|
import org.mozilla.fenix.mvi.UIComponent
|
||||||
|
import org.mozilla.fenix.mvi.UIComponentViewModel
|
||||||
import org.mozilla.fenix.mvi.ViewState
|
import org.mozilla.fenix.mvi.ViewState
|
||||||
|
|
||||||
class ToolbarComponent(
|
class ToolbarComponent(
|
||||||
private val container: ViewGroup,
|
private val container: ViewGroup,
|
||||||
|
owner: Fragment,
|
||||||
bus: ActionBusFactory,
|
bus: ActionBusFactory,
|
||||||
private val sessionId: String?,
|
private val sessionId: String?,
|
||||||
private val isPrivate: Boolean,
|
private val isPrivate: Boolean,
|
||||||
|
@ -28,21 +35,13 @@ class ToolbarComponent(
|
||||||
private val engineIconView: ImageView? = null
|
private val engineIconView: ImageView? = null
|
||||||
) :
|
) :
|
||||||
UIComponent<SearchState, SearchAction, SearchChange>(
|
UIComponent<SearchState, SearchAction, SearchChange>(
|
||||||
|
owner,
|
||||||
bus.getManagedEmitter(SearchAction::class.java),
|
bus.getManagedEmitter(SearchAction::class.java),
|
||||||
bus.getSafeManagedObservable(SearchChange::class.java)
|
bus.getSafeManagedObservable(SearchChange::class.java)
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun getView(): BrowserToolbar = uiView.toolbar
|
fun getView(): BrowserToolbar = uiView.toolbar
|
||||||
|
|
||||||
override val reducer: Reducer<SearchState, SearchChange> = { state, change ->
|
|
||||||
when (change) {
|
|
||||||
is SearchChange.ToolbarClearedFocus -> state.copy(focused = false)
|
|
||||||
is SearchChange.ToolbarRequestedFocus -> state.copy(focused = true)
|
|
||||||
is SearchChange.SearchShortcutEngineSelected ->
|
|
||||||
state.copy(engine = change.engine)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun initView() = ToolbarUIView(
|
override fun initView() = ToolbarUIView(
|
||||||
sessionId,
|
sessionId,
|
||||||
isPrivate,
|
isPrivate,
|
||||||
|
@ -52,8 +51,12 @@ class ToolbarComponent(
|
||||||
engineIconView
|
engineIconView
|
||||||
)
|
)
|
||||||
|
|
||||||
|
override fun render(): Observable<SearchState> =
|
||||||
|
ViewModelProviders.of(owner, ToolbarViewModel.Factory(initialState, changesObservable))
|
||||||
|
.get(ToolbarViewModel::class.java).render(uiView)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
render(reducer)
|
render()
|
||||||
applyTheme()
|
applyTheme()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,3 +98,27 @@ sealed class SearchChange : Change {
|
||||||
object ToolbarClearedFocus : SearchChange()
|
object ToolbarClearedFocus : SearchChange()
|
||||||
data class SearchShortcutEngineSelected(val engine: SearchEngine) : SearchChange()
|
data class SearchShortcutEngineSelected(val engine: SearchEngine) : SearchChange()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ToolbarViewModel(initialState: SearchState, changesObservable: Observable<SearchChange>) :
|
||||||
|
UIComponentViewModel<SearchState, SearchAction, SearchChange>(initialState, changesObservable, reducer) {
|
||||||
|
|
||||||
|
class Factory(
|
||||||
|
private val initialState: SearchState,
|
||||||
|
val changesObservable: Observable<SearchChange>
|
||||||
|
) : ViewModelProvider.Factory {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
override fun <T : ViewModel?> create(modelClass: Class<T>): T =
|
||||||
|
ToolbarViewModel(initialState, changesObservable) as T
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val reducer: Reducer<SearchState, SearchChange> = { state, change ->
|
||||||
|
when (change) {
|
||||||
|
is SearchChange.ToolbarClearedFocus -> state.copy(focused = false)
|
||||||
|
is SearchChange.ToolbarRequestedFocus -> state.copy(focused = true)
|
||||||
|
is SearchChange.SearchShortcutEngineSelected ->
|
||||||
|
state.copy(engine = change.engine)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -45,7 +45,7 @@ class ToolbarUIView(
|
||||||
init {
|
init {
|
||||||
val sessionManager = view.context.components.core.sessionManager
|
val sessionManager = view.context.components.core.sessionManager
|
||||||
val session = sessionId?.let { sessionManager.findSessionById(it) }
|
val session = sessionId?.let { sessionManager.findSessionById(it) }
|
||||||
?: sessionManager.selectedSession
|
?: sessionManager.selectedSession
|
||||||
|
|
||||||
view.apply {
|
view.apply {
|
||||||
elevation = resources.pxToDp(TOOLBAR_ELEVATION).toFloat()
|
elevation = resources.pxToDp(TOOLBAR_ELEVATION).toFloat()
|
||||||
|
|
|
@ -4,10 +4,16 @@
|
||||||
package org.mozilla.fenix.exceptions
|
package org.mozilla.fenix.exceptions
|
||||||
|
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.lifecycle.ViewModelProviders
|
||||||
|
import io.reactivex.Observable
|
||||||
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.UIComponentViewModel
|
||||||
import org.mozilla.fenix.mvi.ViewState
|
import org.mozilla.fenix.mvi.ViewState
|
||||||
import org.mozilla.fenix.test.Mockable
|
import org.mozilla.fenix.test.Mockable
|
||||||
|
|
||||||
|
@ -16,24 +22,24 @@ data class ExceptionsItem(val url: String)
|
||||||
@Mockable
|
@Mockable
|
||||||
class ExceptionsComponent(
|
class ExceptionsComponent(
|
||||||
private val container: ViewGroup,
|
private val container: ViewGroup,
|
||||||
|
owner: Fragment,
|
||||||
bus: ActionBusFactory,
|
bus: ActionBusFactory,
|
||||||
override var initialState: ExceptionsState = ExceptionsState(emptyList())
|
override var initialState: ExceptionsState = ExceptionsState(emptyList())
|
||||||
) :
|
) :
|
||||||
UIComponent<ExceptionsState, ExceptionsAction, ExceptionsChange>(
|
UIComponent<ExceptionsState, ExceptionsAction, ExceptionsChange>(
|
||||||
|
owner,
|
||||||
bus.getManagedEmitter(ExceptionsAction::class.java),
|
bus.getManagedEmitter(ExceptionsAction::class.java),
|
||||||
bus.getSafeManagedObservable(ExceptionsChange::class.java)
|
bus.getSafeManagedObservable(ExceptionsChange::class.java)
|
||||||
) {
|
) {
|
||||||
|
|
||||||
override val reducer: (ExceptionsState, ExceptionsChange) -> ExceptionsState = { state, change ->
|
override fun render(): Observable<ExceptionsState> =
|
||||||
when (change) {
|
ViewModelProviders.of(owner, ExceptionsViewModel.Factory(initialState, changesObservable))
|
||||||
is ExceptionsChange.Change -> state.copy(items = change.list)
|
.get(ExceptionsViewModel::class.java).render(uiView)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun initView() = ExceptionsUIView(container, actionEmitter, changesObservable)
|
override fun initView() = ExceptionsUIView(container, actionEmitter, changesObservable)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
render(reducer)
|
render()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,3 +55,28 @@ sealed class ExceptionsAction : Action {
|
||||||
sealed class ExceptionsChange : Change {
|
sealed class ExceptionsChange : Change {
|
||||||
data class Change(val list: List<ExceptionsItem>) : ExceptionsChange()
|
data class Change(val list: List<ExceptionsItem>) : ExceptionsChange()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ExceptionsViewModel(initialState: ExceptionsState, changesObservable: Observable<ExceptionsChange>) :
|
||||||
|
UIComponentViewModel<ExceptionsState, ExceptionsAction, ExceptionsChange>(
|
||||||
|
initialState,
|
||||||
|
changesObservable,
|
||||||
|
reducer
|
||||||
|
) {
|
||||||
|
|
||||||
|
class Factory(
|
||||||
|
private val initialState: ExceptionsState,
|
||||||
|
private val changesObservable: Observable<ExceptionsChange>
|
||||||
|
) : ViewModelProvider.Factory {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
override fun <T : ViewModel?> create(modelClass: Class<T>): T =
|
||||||
|
ExceptionsViewModel(initialState, changesObservable) as T
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val reducer: (ExceptionsState, ExceptionsChange) -> ExceptionsState = { state, change ->
|
||||||
|
when (change) {
|
||||||
|
is ExceptionsChange.Change -> state.copy(items = change.list)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -48,6 +48,7 @@ class ExceptionsFragment : Fragment(), CoroutineScope {
|
||||||
val view = inflater.inflate(R.layout.fragment_exceptions, container, false)
|
val view = inflater.inflate(R.layout.fragment_exceptions, container, false)
|
||||||
exceptionsComponent = ExceptionsComponent(
|
exceptionsComponent = ExceptionsComponent(
|
||||||
view.exceptions_layout,
|
view.exceptions_layout,
|
||||||
|
this,
|
||||||
ActionBusFactory.get(this),
|
ActionBusFactory.get(this),
|
||||||
ExceptionsState(loadAndMapExceptions())
|
ExceptionsState(loadAndMapExceptions())
|
||||||
)
|
)
|
||||||
|
|
|
@ -89,6 +89,7 @@ class HomeFragment : Fragment(), CoroutineScope {
|
||||||
|
|
||||||
sessionControlComponent = SessionControlComponent(
|
sessionControlComponent = SessionControlComponent(
|
||||||
view.homeLayout,
|
view.homeLayout,
|
||||||
|
this,
|
||||||
bus,
|
bus,
|
||||||
SessionControlState(listOf(), listOf(), mode)
|
SessionControlState(listOf(), listOf(), mode)
|
||||||
)
|
)
|
||||||
|
|
|
@ -6,38 +6,42 @@ package org.mozilla.fenix.home.sessioncontrol
|
||||||
|
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.lifecycle.ViewModelProviders
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import io.reactivex.Observable
|
||||||
import io.reactivex.Observer
|
import io.reactivex.Observer
|
||||||
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.UIComponentViewModel
|
||||||
import org.mozilla.fenix.mvi.ViewState
|
import org.mozilla.fenix.mvi.ViewState
|
||||||
|
|
||||||
class SessionControlComponent(
|
class SessionControlComponent(
|
||||||
private val container: ViewGroup,
|
private val container: ViewGroup,
|
||||||
|
owner: Fragment,
|
||||||
bus: ActionBusFactory,
|
bus: ActionBusFactory,
|
||||||
override var initialState: SessionControlState = SessionControlState(emptyList(), emptyList(), Mode.Normal)
|
override var initialState: SessionControlState = SessionControlState(emptyList(), emptyList(), Mode.Normal)
|
||||||
) :
|
) :
|
||||||
UIComponent<SessionControlState, SessionControlAction, SessionControlChange>(
|
UIComponent<SessionControlState, SessionControlAction, SessionControlChange>(
|
||||||
|
owner,
|
||||||
bus.getManagedEmitter(SessionControlAction::class.java),
|
bus.getManagedEmitter(SessionControlAction::class.java),
|
||||||
bus.getSafeManagedObservable(SessionControlChange::class.java)
|
bus.getSafeManagedObservable(SessionControlChange::class.java)
|
||||||
) {
|
) {
|
||||||
|
|
||||||
override val reducer: (SessionControlState, SessionControlChange) -> SessionControlState = { state, change ->
|
|
||||||
when (change) {
|
|
||||||
is SessionControlChange.CollectionsChange -> state.copy(collections = change.collections)
|
|
||||||
is SessionControlChange.TabsChange -> state.copy(tabs = change.tabs)
|
|
||||||
is SessionControlChange.ModeChange -> state.copy(mode = change.mode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun initView() = SessionControlUIView(container, actionEmitter, changesObservable)
|
override fun initView() = SessionControlUIView(container, actionEmitter, changesObservable)
|
||||||
val view: RecyclerView
|
val view: RecyclerView
|
||||||
get() = uiView.view as RecyclerView
|
get() = uiView.view as RecyclerView
|
||||||
|
|
||||||
|
override fun render(): Observable<SessionControlState> =
|
||||||
|
ViewModelProviders.of(owner, SessionControlViewModel.Factory(initialState, changesObservable))
|
||||||
|
.get(SessionControlViewModel::class.java).render(uiView)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
render(reducer)
|
render()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,3 +113,30 @@ sealed class SessionControlChange : Change {
|
||||||
data class ModeChange(val mode: Mode) : SessionControlChange()
|
data class ModeChange(val mode: Mode) : SessionControlChange()
|
||||||
data class CollectionsChange(val collections: List<TabCollection>) : SessionControlChange()
|
data class CollectionsChange(val collections: List<TabCollection>) : SessionControlChange()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class SessionControlViewModel(initialState: SessionControlState, changesObservable: Observable<SessionControlChange>) :
|
||||||
|
UIComponentViewModel<SessionControlState, SessionControlAction, SessionControlChange>(
|
||||||
|
initialState,
|
||||||
|
changesObservable,
|
||||||
|
reducer
|
||||||
|
) {
|
||||||
|
|
||||||
|
class Factory(
|
||||||
|
private val initialState: SessionControlState,
|
||||||
|
private val changesObservable: Observable<SessionControlChange>
|
||||||
|
) : ViewModelProvider.Factory {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
override fun <T : ViewModel?> create(modelClass: Class<T>): T =
|
||||||
|
SessionControlViewModel(initialState, changesObservable) as T
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val reducer: (SessionControlState, SessionControlChange) -> SessionControlState = { state, change ->
|
||||||
|
when (change) {
|
||||||
|
is SessionControlChange.CollectionsChange -> state.copy(collections = change.collections)
|
||||||
|
is SessionControlChange.TabsChange -> state.copy(tabs = change.tabs)
|
||||||
|
is SessionControlChange.ModeChange -> state.copy(mode = change.mode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -5,12 +5,17 @@
|
||||||
package org.mozilla.fenix.library.bookmarks
|
package org.mozilla.fenix.library.bookmarks
|
||||||
|
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import io.reactivex.Observable
|
||||||
import mozilla.components.concept.storage.BookmarkNode
|
import mozilla.components.concept.storage.BookmarkNode
|
||||||
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.Reducer
|
import org.mozilla.fenix.mvi.Reducer
|
||||||
import org.mozilla.fenix.mvi.UIComponent
|
import org.mozilla.fenix.mvi.UIComponent
|
||||||
|
import org.mozilla.fenix.mvi.UIComponentViewModel
|
||||||
import org.mozilla.fenix.mvi.UIView
|
import org.mozilla.fenix.mvi.UIView
|
||||||
import org.mozilla.fenix.mvi.ViewState
|
import org.mozilla.fenix.mvi.ViewState
|
||||||
import org.mozilla.fenix.test.Mockable
|
import org.mozilla.fenix.test.Mockable
|
||||||
|
@ -18,51 +23,28 @@ import org.mozilla.fenix.test.Mockable
|
||||||
@Mockable
|
@Mockable
|
||||||
class BookmarkComponent(
|
class BookmarkComponent(
|
||||||
private val container: ViewGroup,
|
private val container: ViewGroup,
|
||||||
|
owner: Fragment,
|
||||||
bus: ActionBusFactory,
|
bus: ActionBusFactory,
|
||||||
override var initialState: BookmarkState =
|
override var initialState: BookmarkState =
|
||||||
BookmarkState(null, BookmarkState.Mode.Normal)
|
BookmarkState(null, BookmarkState.Mode.Normal)
|
||||||
) :
|
) :
|
||||||
UIComponent<BookmarkState, BookmarkAction, BookmarkChange>(
|
UIComponent<BookmarkState, BookmarkAction, BookmarkChange>(
|
||||||
|
owner,
|
||||||
bus.getManagedEmitter(BookmarkAction::class.java),
|
bus.getManagedEmitter(BookmarkAction::class.java),
|
||||||
bus.getSafeManagedObservable(BookmarkChange::class.java)
|
bus.getSafeManagedObservable(BookmarkChange::class.java)
|
||||||
) {
|
) {
|
||||||
|
|
||||||
override val reducer: Reducer<BookmarkState, BookmarkChange> = { state, change ->
|
|
||||||
when (change) {
|
|
||||||
is BookmarkChange.Change -> {
|
|
||||||
val mode =
|
|
||||||
if (state.mode is BookmarkState.Mode.Selecting) {
|
|
||||||
val items = state.mode.selectedItems.filter {
|
|
||||||
it in change.tree
|
|
||||||
}.toSet()
|
|
||||||
if (items.isEmpty()) BookmarkState.Mode.Normal else BookmarkState.Mode.Selecting(items)
|
|
||||||
} else state.mode
|
|
||||||
state.copy(tree = change.tree, mode = mode)
|
|
||||||
}
|
|
||||||
is BookmarkChange.IsSelected -> {
|
|
||||||
val selectedItems = if (state.mode is BookmarkState.Mode.Selecting) {
|
|
||||||
state.mode.selectedItems + change.newlySelectedItem
|
|
||||||
} else setOf(change.newlySelectedItem)
|
|
||||||
state.copy(mode = BookmarkState.Mode.Selecting(selectedItems))
|
|
||||||
}
|
|
||||||
is BookmarkChange.IsDeselected -> {
|
|
||||||
val selectedItems = if (state.mode is BookmarkState.Mode.Selecting) {
|
|
||||||
state.mode.selectedItems - change.newlyDeselectedItem
|
|
||||||
} else setOf()
|
|
||||||
val mode = if (selectedItems.isEmpty()) BookmarkState.Mode.Normal else BookmarkState.Mode.Selecting(
|
|
||||||
selectedItems
|
|
||||||
)
|
|
||||||
state.copy(mode = mode)
|
|
||||||
}
|
|
||||||
is BookmarkChange.ClearSelection -> state.copy(mode = BookmarkState.Mode.Normal)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun initView(): UIView<BookmarkState, BookmarkAction, BookmarkChange> =
|
override fun initView(): UIView<BookmarkState, BookmarkAction, BookmarkChange> =
|
||||||
BookmarkUIView(container, actionEmitter, changesObservable)
|
BookmarkUIView(container, actionEmitter, changesObservable)
|
||||||
|
|
||||||
|
override fun render(): Observable<BookmarkState> {
|
||||||
|
return ViewModelProvider(
|
||||||
|
owner,
|
||||||
|
BookmarkViewModel.Factory(initialState, changesObservable)
|
||||||
|
).get(BookmarkViewModel::class.java).render(uiView)
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
render(reducer)
|
render()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,3 +80,49 @@ sealed class BookmarkChange : Change {
|
||||||
operator fun BookmarkNode.contains(item: BookmarkNode): Boolean {
|
operator fun BookmarkNode.contains(item: BookmarkNode): Boolean {
|
||||||
return children?.contains(item) ?: false
|
return children?.contains(item) ?: false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class BookmarkViewModel(initialState: BookmarkState, changesObservable: Observable<BookmarkChange>) :
|
||||||
|
UIComponentViewModel<BookmarkState, BookmarkAction, BookmarkChange>(initialState, changesObservable, reducer) {
|
||||||
|
|
||||||
|
class Factory(
|
||||||
|
private val initialState: BookmarkState,
|
||||||
|
private val changesObservable: Observable<BookmarkChange>
|
||||||
|
) : ViewModelProvider.Factory {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
override fun <T : ViewModel?> create(modelClass: Class<T>): T =
|
||||||
|
BookmarkViewModel(initialState, changesObservable) as T
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val reducer: Reducer<BookmarkState, BookmarkChange> = { state, change ->
|
||||||
|
when (change) {
|
||||||
|
is BookmarkChange.Change -> {
|
||||||
|
val mode =
|
||||||
|
if (state.mode is BookmarkState.Mode.Selecting) {
|
||||||
|
val items = state.mode.selectedItems.filter {
|
||||||
|
it in change.tree
|
||||||
|
}.toSet()
|
||||||
|
if (items.isEmpty()) BookmarkState.Mode.Normal else BookmarkState.Mode.Selecting(items)
|
||||||
|
} else state.mode
|
||||||
|
state.copy(tree = change.tree, mode = mode)
|
||||||
|
}
|
||||||
|
is BookmarkChange.IsSelected -> {
|
||||||
|
val selectedItems = if (state.mode is BookmarkState.Mode.Selecting) {
|
||||||
|
state.mode.selectedItems + change.newlySelectedItem
|
||||||
|
} else setOf(change.newlySelectedItem)
|
||||||
|
state.copy(mode = BookmarkState.Mode.Selecting(selectedItems))
|
||||||
|
}
|
||||||
|
is BookmarkChange.IsDeselected -> {
|
||||||
|
val selectedItems = if (state.mode is BookmarkState.Mode.Selecting) {
|
||||||
|
state.mode.selectedItems - change.newlyDeselectedItem
|
||||||
|
} else setOf()
|
||||||
|
val mode = if (selectedItems.isEmpty()) BookmarkState.Mode.Normal else BookmarkState.Mode.Selecting(
|
||||||
|
selectedItems
|
||||||
|
)
|
||||||
|
state.copy(mode = mode)
|
||||||
|
}
|
||||||
|
is BookmarkChange.ClearSelection -> state.copy(mode = BookmarkState.Mode.Normal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -74,8 +74,8 @@ class BookmarkFragment : Fragment(), CoroutineScope, BackHandler, AccountObserve
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
val view = inflater.inflate(R.layout.fragment_bookmark, container, false)
|
val view = inflater.inflate(R.layout.fragment_bookmark, container, false)
|
||||||
bookmarkComponent = BookmarkComponent(view.bookmark_layout, ActionBusFactory.get(this))
|
bookmarkComponent = BookmarkComponent(view.bookmark_layout, this, ActionBusFactory.get(this))
|
||||||
signInComponent = SignInComponent(view.bookmark_layout, ActionBusFactory.get(this))
|
signInComponent = SignInComponent(view.bookmark_layout, this, ActionBusFactory.get(this))
|
||||||
return view
|
return view
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,36 +5,41 @@
|
||||||
package org.mozilla.fenix.library.bookmarks
|
package org.mozilla.fenix.library.bookmarks
|
||||||
|
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import io.reactivex.Observable
|
||||||
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.Reducer
|
import org.mozilla.fenix.mvi.Reducer
|
||||||
import org.mozilla.fenix.mvi.UIComponent
|
import org.mozilla.fenix.mvi.UIComponent
|
||||||
|
import org.mozilla.fenix.mvi.UIComponentViewModel
|
||||||
import org.mozilla.fenix.mvi.UIView
|
import org.mozilla.fenix.mvi.UIView
|
||||||
import org.mozilla.fenix.mvi.ViewState
|
import org.mozilla.fenix.mvi.ViewState
|
||||||
|
|
||||||
class SignInComponent(
|
class SignInComponent(
|
||||||
private val container: ViewGroup,
|
private val container: ViewGroup,
|
||||||
|
owner: Fragment,
|
||||||
bus: ActionBusFactory,
|
bus: ActionBusFactory,
|
||||||
override var initialState: SignInState =
|
override var initialState: SignInState =
|
||||||
SignInState(false)
|
SignInState(false)
|
||||||
) : UIComponent<SignInState, SignInAction, SignInChange>(
|
) : UIComponent<SignInState, SignInAction, SignInChange>(
|
||||||
|
owner,
|
||||||
bus.getManagedEmitter(SignInAction::class.java),
|
bus.getManagedEmitter(SignInAction::class.java),
|
||||||
bus.getSafeManagedObservable(SignInChange::class.java)
|
bus.getSafeManagedObservable(SignInChange::class.java)
|
||||||
) {
|
) {
|
||||||
|
|
||||||
override val reducer: Reducer<SignInState, SignInChange> = { state, change ->
|
|
||||||
when (change) {
|
|
||||||
SignInChange.SignedIn -> state.copy(signedIn = true)
|
|
||||||
SignInChange.SignedOut -> state.copy(signedIn = false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun initView(): UIView<SignInState, SignInAction, SignInChange> =
|
override fun initView(): UIView<SignInState, SignInAction, SignInChange> =
|
||||||
SignInUIView(container, actionEmitter, changesObservable)
|
SignInUIView(container, actionEmitter, changesObservable)
|
||||||
|
|
||||||
|
override fun render(): Observable<SignInState> =
|
||||||
|
ViewModelProvider(
|
||||||
|
owner,
|
||||||
|
SignInViewModel.Factory(initialState, changesObservable)
|
||||||
|
).get(SignInViewModel::class.java).render(uiView)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
render(reducer)
|
render()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,3 +53,29 @@ sealed class SignInChange : Change {
|
||||||
object SignedIn : SignInChange()
|
object SignedIn : SignInChange()
|
||||||
object SignedOut : SignInChange()
|
object SignedOut : SignInChange()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class SignInViewModel(initialState: SignInState, changesObservable: Observable<SignInChange>) :
|
||||||
|
UIComponentViewModel<SignInState, SignInAction, SignInChange>(
|
||||||
|
initialState, changesObservable, reducer
|
||||||
|
) {
|
||||||
|
|
||||||
|
class Factory(
|
||||||
|
private val initialState: SignInState,
|
||||||
|
private val changesObservable: Observable<SignInChange>
|
||||||
|
) : ViewModelProvider.Factory {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
override fun <T : ViewModel?> create(modelClass: Class<T>): T =
|
||||||
|
SignInViewModel(initialState, changesObservable) as T
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val reducer = object : Reducer<SignInState, SignInChange> {
|
||||||
|
override fun invoke(state: SignInState, change: SignInChange): SignInState {
|
||||||
|
return when (change) {
|
||||||
|
SignInChange.SignedIn -> state.copy(signedIn = true)
|
||||||
|
SignInChange.SignedOut -> state.copy(signedIn = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -68,7 +68,7 @@ class SelectBookmarkFolderFragment : Fragment(), CoroutineScope, AccountObserver
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
val view = inflater.inflate(R.layout.fragment_select_bookmark_folder, container, false)
|
val view = inflater.inflate(R.layout.fragment_select_bookmark_folder, container, false)
|
||||||
signInComponent = SignInComponent(view.select_bookmark_layout, ActionBusFactory.get(this))
|
signInComponent = SignInComponent(view.select_bookmark_layout, this, ActionBusFactory.get(this))
|
||||||
return view
|
return view
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,56 +4,42 @@
|
||||||
package org.mozilla.fenix.library.history
|
package org.mozilla.fenix.library.history
|
||||||
|
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import org.mozilla.fenix.test.Mockable
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import io.reactivex.Observable
|
||||||
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.UIComponentViewModel
|
||||||
import org.mozilla.fenix.mvi.ViewState
|
import org.mozilla.fenix.mvi.ViewState
|
||||||
|
import org.mozilla.fenix.test.Mockable
|
||||||
|
|
||||||
data class HistoryItem(val id: Int, val title: String, val url: String, val visitedAt: Long)
|
data class HistoryItem(val id: Int, val title: String, val url: String, val visitedAt: Long)
|
||||||
|
|
||||||
@Mockable
|
@Mockable
|
||||||
class HistoryComponent(
|
class HistoryComponent(
|
||||||
private val container: ViewGroup,
|
private val container: ViewGroup,
|
||||||
|
owner: Fragment,
|
||||||
bus: ActionBusFactory,
|
bus: ActionBusFactory,
|
||||||
override var initialState: HistoryState = HistoryState(emptyList(), HistoryState.Mode.Normal)
|
override var initialState: HistoryState = HistoryState(emptyList(), HistoryState.Mode.Normal)
|
||||||
) :
|
) :
|
||||||
UIComponent<HistoryState, HistoryAction, HistoryChange>(
|
UIComponent<HistoryState, HistoryAction, HistoryChange>(
|
||||||
|
owner,
|
||||||
bus.getManagedEmitter(HistoryAction::class.java),
|
bus.getManagedEmitter(HistoryAction::class.java),
|
||||||
bus.getSafeManagedObservable(HistoryChange::class.java)
|
bus.getSafeManagedObservable(HistoryChange::class.java)
|
||||||
) {
|
) {
|
||||||
|
|
||||||
override val reducer: (HistoryState, HistoryChange) -> HistoryState = { state, change ->
|
|
||||||
when (change) {
|
|
||||||
is HistoryChange.Change -> state.copy(mode = HistoryState.Mode.Normal, items = change.list)
|
|
||||||
is HistoryChange.EnterEditMode -> state.copy(mode = HistoryState.Mode.Editing(listOf(change.item)))
|
|
||||||
is HistoryChange.AddItemForRemoval -> {
|
|
||||||
val mode = state.mode
|
|
||||||
if (mode is HistoryState.Mode.Editing) {
|
|
||||||
val items = mode.selectedItems + listOf(change.item)
|
|
||||||
state.copy(mode = mode.copy(selectedItems = items))
|
|
||||||
} else {
|
|
||||||
state
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is HistoryChange.RemoveItemForRemoval -> {
|
|
||||||
val mode = state.mode
|
|
||||||
if (mode is HistoryState.Mode.Editing) {
|
|
||||||
val items = mode.selectedItems.filter { it.id != change.item.id }
|
|
||||||
state.copy(mode = mode.copy(selectedItems = items))
|
|
||||||
} else {
|
|
||||||
state
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is HistoryChange.ExitEditMode -> state.copy(mode = HistoryState.Mode.Normal)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun initView() = HistoryUIView(container, actionEmitter, changesObservable)
|
override fun initView() = HistoryUIView(container, actionEmitter, changesObservable)
|
||||||
|
|
||||||
|
override fun render(): Observable<HistoryState> =
|
||||||
|
ViewModelProvider(
|
||||||
|
owner,
|
||||||
|
HistoryViewModel.Factory(initialState, changesObservable)
|
||||||
|
).get(HistoryViewModel::class.java).render(uiView)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
render(reducer)
|
render()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,3 +71,44 @@ sealed class HistoryChange : Change {
|
||||||
data class AddItemForRemoval(val item: HistoryItem) : HistoryChange()
|
data class AddItemForRemoval(val item: HistoryItem) : HistoryChange()
|
||||||
data class RemoveItemForRemoval(val item: HistoryItem) : HistoryChange()
|
data class RemoveItemForRemoval(val item: HistoryItem) : HistoryChange()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class HistoryViewModel(initialState: HistoryState, changesObservable: Observable<HistoryChange>) :
|
||||||
|
UIComponentViewModel<HistoryState, HistoryAction, HistoryChange>(initialState, changesObservable, reducer) {
|
||||||
|
|
||||||
|
class Factory(
|
||||||
|
private val initialState: HistoryState,
|
||||||
|
private val changesObservable: Observable<HistoryChange>
|
||||||
|
) : ViewModelProvider.Factory {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
override fun <T : ViewModel?> create(modelClass: Class<T>): T =
|
||||||
|
HistoryViewModel(initialState, changesObservable) as T
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val reducer: (HistoryState, HistoryChange) -> HistoryState = { state, change ->
|
||||||
|
when (change) {
|
||||||
|
is HistoryChange.Change -> state.copy(mode = HistoryState.Mode.Normal, items = change.list)
|
||||||
|
is HistoryChange.EnterEditMode -> state.copy(mode = HistoryState.Mode.Editing(listOf(change.item)))
|
||||||
|
is HistoryChange.AddItemForRemoval -> {
|
||||||
|
val mode = state.mode
|
||||||
|
if (mode is HistoryState.Mode.Editing) {
|
||||||
|
val items = mode.selectedItems + listOf(change.item)
|
||||||
|
state.copy(mode = mode.copy(selectedItems = items))
|
||||||
|
} else {
|
||||||
|
state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is HistoryChange.RemoveItemForRemoval -> {
|
||||||
|
val mode = state.mode
|
||||||
|
if (mode is HistoryState.Mode.Editing) {
|
||||||
|
val items = mode.selectedItems.filter { it.id != change.item.id }
|
||||||
|
state.copy(mode = mode.copy(selectedItems = items))
|
||||||
|
} else {
|
||||||
|
state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is HistoryChange.ExitEditMode -> state.copy(mode = HistoryState.Mode.Normal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -49,7 +49,7 @@ class HistoryFragment : Fragment(), CoroutineScope, BackHandler {
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View? {
|
): View? {
|
||||||
val view = inflater.inflate(R.layout.fragment_history, container, false)
|
val view = inflater.inflate(R.layout.fragment_history, container, false)
|
||||||
historyComponent = HistoryComponent(view.history_layout, ActionBusFactory.get(this))
|
historyComponent = HistoryComponent(view.history_layout, this, ActionBusFactory.get(this))
|
||||||
return view
|
return view
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,16 +5,22 @@
|
||||||
package org.mozilla.fenix.quickactionsheet
|
package org.mozilla.fenix.quickactionsheet
|
||||||
|
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import io.reactivex.Observable
|
||||||
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.Reducer
|
import org.mozilla.fenix.mvi.Reducer
|
||||||
import org.mozilla.fenix.mvi.UIComponent
|
import org.mozilla.fenix.mvi.UIComponent
|
||||||
|
import org.mozilla.fenix.mvi.UIComponentViewModel
|
||||||
import org.mozilla.fenix.mvi.UIView
|
import org.mozilla.fenix.mvi.UIView
|
||||||
import org.mozilla.fenix.mvi.ViewState
|
import org.mozilla.fenix.mvi.ViewState
|
||||||
|
|
||||||
class QuickActionComponent(
|
class QuickActionComponent(
|
||||||
private val container: ViewGroup,
|
private val container: ViewGroup,
|
||||||
|
owner: Fragment,
|
||||||
bus: ActionBusFactory,
|
bus: ActionBusFactory,
|
||||||
override var initialState: QuickActionState = QuickActionState(
|
override var initialState: QuickActionState = QuickActionState(
|
||||||
readable = false,
|
readable = false,
|
||||||
|
@ -22,29 +28,21 @@ class QuickActionComponent(
|
||||||
readerActive = false
|
readerActive = false
|
||||||
)
|
)
|
||||||
) : UIComponent<QuickActionState, QuickActionAction, QuickActionChange>(
|
) : UIComponent<QuickActionState, QuickActionAction, QuickActionChange>(
|
||||||
|
owner,
|
||||||
bus.getManagedEmitter(QuickActionAction::class.java),
|
bus.getManagedEmitter(QuickActionAction::class.java),
|
||||||
bus.getSafeManagedObservable(QuickActionChange::class.java)
|
bus.getSafeManagedObservable(QuickActionChange::class.java)
|
||||||
) {
|
) {
|
||||||
|
|
||||||
override val reducer: Reducer<QuickActionState, QuickActionChange> = { state, change ->
|
|
||||||
when (change) {
|
|
||||||
is QuickActionChange.BookmarkedStateChange -> {
|
|
||||||
state.copy(bookmarked = change.bookmarked)
|
|
||||||
}
|
|
||||||
is QuickActionChange.ReadableStateChange -> {
|
|
||||||
state.copy(readable = change.readable)
|
|
||||||
}
|
|
||||||
is QuickActionChange.ReaderActiveStateChange -> {
|
|
||||||
state.copy(readerActive = change.active)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun initView(): UIView<QuickActionState, QuickActionAction, QuickActionChange> =
|
override fun initView(): UIView<QuickActionState, QuickActionAction, QuickActionChange> =
|
||||||
QuickActionUIView(container, actionEmitter, changesObservable)
|
QuickActionUIView(container, actionEmitter, changesObservable)
|
||||||
|
|
||||||
|
override fun render(): Observable<QuickActionState> =
|
||||||
|
ViewModelProvider(
|
||||||
|
owner,
|
||||||
|
QuickActionViewModel.Factory(initialState, changesObservable)
|
||||||
|
).get(QuickActionViewModel::class.java).render(uiView)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
render(reducer)
|
render()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,3 +63,36 @@ sealed class QuickActionChange : Change {
|
||||||
data class ReadableStateChange(val readable: Boolean) : QuickActionChange()
|
data class ReadableStateChange(val readable: Boolean) : QuickActionChange()
|
||||||
data class ReaderActiveStateChange(val active: Boolean) : QuickActionChange()
|
data class ReaderActiveStateChange(val active: Boolean) : QuickActionChange()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class QuickActionViewModel(initialState: QuickActionState, changesObservable: Observable<QuickActionChange>) :
|
||||||
|
UIComponentViewModel<QuickActionState, QuickActionAction, QuickActionChange>(
|
||||||
|
initialState,
|
||||||
|
changesObservable,
|
||||||
|
reducer
|
||||||
|
) {
|
||||||
|
|
||||||
|
class Factory(
|
||||||
|
private val initialState: QuickActionState,
|
||||||
|
private val changesObservable: Observable<QuickActionChange>
|
||||||
|
) : ViewModelProvider.Factory {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
override fun <T : ViewModel?> create(modelClass: Class<T>): T =
|
||||||
|
QuickActionViewModel(initialState, changesObservable) as T
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val reducer: Reducer<QuickActionState, QuickActionChange> = { state, change ->
|
||||||
|
when (change) {
|
||||||
|
is QuickActionChange.BookmarkedStateChange -> {
|
||||||
|
state.copy(bookmarked = change.bookmarked)
|
||||||
|
}
|
||||||
|
is QuickActionChange.ReadableStateChange -> {
|
||||||
|
state.copy(readable = change.readable)
|
||||||
|
}
|
||||||
|
is QuickActionChange.ReaderActiveStateChange -> {
|
||||||
|
state.copy(readerActive = change.active)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -67,6 +67,7 @@ class SearchFragment : Fragment(), BackHandler {
|
||||||
|
|
||||||
toolbarComponent = ToolbarComponent(
|
toolbarComponent = ToolbarComponent(
|
||||||
view.toolbar_component_wrapper,
|
view.toolbar_component_wrapper,
|
||||||
|
this,
|
||||||
ActionBusFactory.get(this),
|
ActionBusFactory.get(this),
|
||||||
sessionId,
|
sessionId,
|
||||||
isPrivate,
|
isPrivate,
|
||||||
|
@ -74,7 +75,7 @@ class SearchFragment : Fragment(), BackHandler {
|
||||||
view.search_engine_icon
|
view.search_engine_icon
|
||||||
)
|
)
|
||||||
|
|
||||||
awesomeBarComponent = AwesomeBarComponent(view.search_layout, ActionBusFactory.get(this))
|
awesomeBarComponent = AwesomeBarComponent(view.search_layout, this, ActionBusFactory.get(this))
|
||||||
ActionBusFactory.get(this).logMergedObservables()
|
ActionBusFactory.get(this).logMergedObservables()
|
||||||
return view
|
return view
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,23 @@
|
||||||
package org.mozilla.fenix.search.awesomebar
|
package org.mozilla.fenix.search.awesomebar
|
||||||
|
|
||||||
/* 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/. */
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.lifecycle.ViewModelProviders
|
||||||
|
import io.reactivex.Observable
|
||||||
import mozilla.components.browser.search.SearchEngine
|
import mozilla.components.browser.search.SearchEngine
|
||||||
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.Reducer
|
import org.mozilla.fenix.mvi.Reducer
|
||||||
import org.mozilla.fenix.mvi.UIComponent
|
import org.mozilla.fenix.mvi.UIComponent
|
||||||
|
import org.mozilla.fenix.mvi.UIComponentViewModel
|
||||||
import org.mozilla.fenix.mvi.ViewState
|
import org.mozilla.fenix.mvi.ViewState
|
||||||
|
|
||||||
data class AwesomeBarState(
|
data class AwesomeBarState(
|
||||||
|
@ -32,25 +40,51 @@ sealed class AwesomeBarChange : Change {
|
||||||
|
|
||||||
class AwesomeBarComponent(
|
class AwesomeBarComponent(
|
||||||
private val container: ViewGroup,
|
private val container: ViewGroup,
|
||||||
|
owner: Fragment,
|
||||||
bus: ActionBusFactory,
|
bus: ActionBusFactory,
|
||||||
override var initialState: AwesomeBarState = AwesomeBarState("", false)
|
override var initialState: AwesomeBarState = AwesomeBarState("", false)
|
||||||
) : UIComponent<AwesomeBarState, AwesomeBarAction, AwesomeBarChange>(
|
) : UIComponent<AwesomeBarState, AwesomeBarAction, AwesomeBarChange>(
|
||||||
|
owner,
|
||||||
bus.getManagedEmitter(AwesomeBarAction::class.java),
|
bus.getManagedEmitter(AwesomeBarAction::class.java),
|
||||||
bus.getSafeManagedObservable(AwesomeBarChange::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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun initView() = AwesomeBarUIView(container, actionEmitter, changesObservable)
|
override fun initView() = AwesomeBarUIView(container, actionEmitter, changesObservable)
|
||||||
|
|
||||||
|
override fun render(): Observable<AwesomeBarState> =
|
||||||
|
ViewModelProviders.of(owner, AwesomeBarViewModel.Factory(initialState, changesObservable))
|
||||||
|
.get(AwesomeBarViewModel::class.java).render(uiView)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
render(reducer)
|
render()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AwesomeBarViewModel(initialState: AwesomeBarState, changesObservable: Observable<AwesomeBarChange>) :
|
||||||
|
UIComponentViewModel<AwesomeBarState, AwesomeBarAction, AwesomeBarChange>(
|
||||||
|
initialState,
|
||||||
|
changesObservable,
|
||||||
|
reducer
|
||||||
|
) {
|
||||||
|
|
||||||
|
class Factory(
|
||||||
|
private val initialState: AwesomeBarState,
|
||||||
|
private val changesObservable: Observable<AwesomeBarChange>
|
||||||
|
) : ViewModelProvider.Factory {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
override fun <T : ViewModel?> create(modelClass: Class<T>): T =
|
||||||
|
AwesomeBarViewModel(initialState, changesObservable) as T
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val reducer: Reducer<AwesomeBarState, AwesomeBarChange> = { state, change ->
|
||||||
|
Log.d("IN_REDUCER", change.toString())
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,10 @@ package org.mozilla.fenix.settings.quicksettings
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import io.reactivex.Observable
|
||||||
import mozilla.components.feature.sitepermissions.SitePermissions
|
import mozilla.components.feature.sitepermissions.SitePermissions
|
||||||
import mozilla.components.support.ktx.kotlin.toUri
|
import mozilla.components.support.ktx.kotlin.toUri
|
||||||
import org.mozilla.fenix.ext.components
|
import org.mozilla.fenix.ext.components
|
||||||
|
@ -13,6 +17,7 @@ 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.UIComponentViewModel
|
||||||
import org.mozilla.fenix.mvi.UIView
|
import org.mozilla.fenix.mvi.UIView
|
||||||
import org.mozilla.fenix.mvi.ViewState
|
import org.mozilla.fenix.mvi.ViewState
|
||||||
import org.mozilla.fenix.settings.PhoneFeature
|
import org.mozilla.fenix.settings.PhoneFeature
|
||||||
|
@ -22,48 +27,25 @@ import org.mozilla.fenix.utils.Settings
|
||||||
|
|
||||||
class QuickSettingsComponent(
|
class QuickSettingsComponent(
|
||||||
private val container: ViewGroup,
|
private val container: ViewGroup,
|
||||||
|
owner: Fragment,
|
||||||
bus: ActionBusFactory,
|
bus: ActionBusFactory,
|
||||||
override var initialState: QuickSettingsState
|
override var initialState: QuickSettingsState
|
||||||
) : UIComponent<QuickSettingsState, QuickSettingsAction, QuickSettingsChange>(
|
) : UIComponent<QuickSettingsState, QuickSettingsAction, QuickSettingsChange>(
|
||||||
|
owner,
|
||||||
bus.getManagedEmitter(QuickSettingsAction::class.java),
|
bus.getManagedEmitter(QuickSettingsAction::class.java),
|
||||||
bus.getSafeManagedObservable(QuickSettingsChange::class.java)
|
bus.getSafeManagedObservable(QuickSettingsChange::class.java)
|
||||||
) {
|
) {
|
||||||
override val reducer: (QuickSettingsState, QuickSettingsChange) -> QuickSettingsState = { state, change ->
|
|
||||||
when (change) {
|
|
||||||
is QuickSettingsChange.Change -> {
|
|
||||||
state.copy(
|
|
||||||
mode = QuickSettingsState.Mode.Normal(
|
|
||||||
change.url,
|
|
||||||
change.isSecured,
|
|
||||||
change.isTrackingProtectionOn,
|
|
||||||
change.sitePermissions
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
is QuickSettingsChange.PermissionGranted -> {
|
|
||||||
state.copy(
|
|
||||||
mode = QuickSettingsState.Mode.ActionLabelUpdated(change.phoneFeature, change.sitePermissions)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
is QuickSettingsChange.PromptRestarted -> {
|
|
||||||
state.copy(
|
|
||||||
mode = QuickSettingsState.Mode.CheckPendingFeatureBlockedByAndroid(change.sitePermissions)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
is QuickSettingsChange.Stored -> {
|
|
||||||
state.copy(
|
|
||||||
mode = QuickSettingsState.Mode.ActionLabelUpdated(change.phoneFeature, change.sitePermissions)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun initView(): UIView<QuickSettingsState, QuickSettingsAction, QuickSettingsChange> {
|
override fun initView(): UIView<QuickSettingsState, QuickSettingsAction, QuickSettingsChange> {
|
||||||
return QuickSettingsUIView(container, actionEmitter, changesObservable, container)
|
return QuickSettingsUIView(container, actionEmitter, changesObservable, container)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun render(): Observable<QuickSettingsState> =
|
||||||
|
ViewModelProvider(owner, QuickSettingsViewModel.Factory(initialState, changesObservable)).get(
|
||||||
|
QuickSettingsViewModel::class.java
|
||||||
|
).render(uiView)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
render(reducer)
|
render()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toggleSitePermission(
|
fun toggleSitePermission(
|
||||||
|
@ -137,3 +119,52 @@ sealed class QuickSettingsChange : Change {
|
||||||
data class PromptRestarted(val sitePermissions: SitePermissions?) : QuickSettingsChange()
|
data class PromptRestarted(val sitePermissions: SitePermissions?) : QuickSettingsChange()
|
||||||
data class Stored(val phoneFeature: PhoneFeature, val sitePermissions: SitePermissions?) : QuickSettingsChange()
|
data class Stored(val phoneFeature: PhoneFeature, val sitePermissions: SitePermissions?) : QuickSettingsChange()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class QuickSettingsViewModel(initialState: QuickSettingsState, changesObservable: Observable<QuickSettingsChange>) :
|
||||||
|
UIComponentViewModel<QuickSettingsState, QuickSettingsAction, QuickSettingsChange>(
|
||||||
|
initialState,
|
||||||
|
changesObservable,
|
||||||
|
reducer
|
||||||
|
) {
|
||||||
|
|
||||||
|
class Factory(
|
||||||
|
private val initialState: QuickSettingsState,
|
||||||
|
private val changesObservable: Observable<QuickSettingsChange>
|
||||||
|
) : ViewModelProvider.Factory {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
override fun <T : ViewModel?> create(modelClass: Class<T>): T =
|
||||||
|
QuickSettingsViewModel(initialState, changesObservable) as T
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val reducer: (QuickSettingsState, QuickSettingsChange) -> QuickSettingsState = { state, change ->
|
||||||
|
when (change) {
|
||||||
|
is QuickSettingsChange.Change -> {
|
||||||
|
state.copy(
|
||||||
|
mode = QuickSettingsState.Mode.Normal(
|
||||||
|
change.url,
|
||||||
|
change.isSecured,
|
||||||
|
change.isTrackingProtectionOn,
|
||||||
|
change.sitePermissions
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is QuickSettingsChange.PermissionGranted -> {
|
||||||
|
state.copy(
|
||||||
|
mode = QuickSettingsState.Mode.ActionLabelUpdated(change.phoneFeature, change.sitePermissions)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is QuickSettingsChange.PromptRestarted -> {
|
||||||
|
state.copy(
|
||||||
|
mode = QuickSettingsState.Mode.CheckPendingFeatureBlockedByAndroid(change.sitePermissions)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is QuickSettingsChange.Stored -> {
|
||||||
|
state.copy(
|
||||||
|
mode = QuickSettingsState.Mode.ActionLabelUpdated(change.phoneFeature, change.sitePermissions)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -73,7 +73,19 @@ class QuickSettingsSheetDialogFragment : AppCompatDialogFragment(), CoroutineSco
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View {
|
): View {
|
||||||
return inflateRootView(container)
|
val rootView = inflateRootView(container)
|
||||||
|
quickSettingsComponent = QuickSettingsComponent(
|
||||||
|
rootView as NestedScrollView, this, ActionBusFactory.get(this),
|
||||||
|
QuickSettingsState(
|
||||||
|
QuickSettingsState.Mode.Normal(
|
||||||
|
url,
|
||||||
|
isSecured,
|
||||||
|
isTrackingProtectionOn,
|
||||||
|
sitePermissions
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return rootView
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun inflateRootView(container: ViewGroup? = null): View {
|
private fun inflateRootView(container: ViewGroup? = null): View {
|
||||||
|
@ -115,21 +127,6 @@ class QuickSettingsSheetDialogFragment : AppCompatDialogFragment(), CoroutineSco
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(rootView: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(rootView, savedInstanceState)
|
|
||||||
quickSettingsComponent = QuickSettingsComponent(
|
|
||||||
rootView as NestedScrollView, ActionBusFactory.get(this),
|
|
||||||
QuickSettingsState(
|
|
||||||
QuickSettingsState.Mode.Normal(
|
|
||||||
url,
|
|
||||||
isSecured,
|
|
||||||
isTrackingProtectionOn,
|
|
||||||
sitePermissions
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val FRAGMENT_TAG = "QUICK_SETTINGS_FRAGMENT_TAG"
|
const val FRAGMENT_TAG = "QUICK_SETTINGS_FRAGMENT_TAG"
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,7 @@ class BookmarkComponentTest {
|
||||||
TestBookmarkComponent(mockk(), TestUtils.bus),
|
TestBookmarkComponent(mockk(), TestUtils.bus),
|
||||||
recordPrivateCalls = true
|
recordPrivateCalls = true
|
||||||
)
|
)
|
||||||
bookmarkObserver = bookmarkComponent.internalRender(bookmarkComponent.reducer).test()
|
bookmarkObserver = bookmarkComponent.render().test()
|
||||||
emitter = TestUtils.owner.getManagedEmitter()
|
emitter = TestUtils.owner.getManagedEmitter()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,7 +87,7 @@ class BookmarkComponentTest {
|
||||||
|
|
||||||
@Suppress("MemberVisibilityCanBePrivate")
|
@Suppress("MemberVisibilityCanBePrivate")
|
||||||
class TestBookmarkComponent(container: ViewGroup, bus: ActionBusFactory) :
|
class TestBookmarkComponent(container: ViewGroup, bus: ActionBusFactory) :
|
||||||
BookmarkComponent(container, bus) {
|
BookmarkComponent(container, mockk(relaxed = true), bus) {
|
||||||
|
|
||||||
override val uiView: UIView<BookmarkState, BookmarkAction, BookmarkChange>
|
override val uiView: UIView<BookmarkState, BookmarkAction, BookmarkChange>
|
||||||
get() = mockk(relaxed = true)
|
get() = mockk(relaxed = true)
|
||||||
|
|
|
@ -34,7 +34,7 @@ class HistoryComponentTest {
|
||||||
TestHistoryComponent(mockk(), bus),
|
TestHistoryComponent(mockk(), bus),
|
||||||
recordPrivateCalls = true
|
recordPrivateCalls = true
|
||||||
)
|
)
|
||||||
historyObserver = historyComponent.internalRender(historyComponent.reducer).test()
|
historyObserver = historyComponent.render().test()
|
||||||
emitter = owner.getManagedEmitter()
|
emitter = owner.getManagedEmitter()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,7 +82,7 @@ class HistoryComponentTest {
|
||||||
|
|
||||||
@Suppress("MemberVisibilityCanBePrivate")
|
@Suppress("MemberVisibilityCanBePrivate")
|
||||||
class TestHistoryComponent(container: ViewGroup, bus: ActionBusFactory) :
|
class TestHistoryComponent(container: ViewGroup, bus: ActionBusFactory) :
|
||||||
HistoryComponent(container, bus) {
|
HistoryComponent(container, mockk(relaxed = true), bus) {
|
||||||
|
|
||||||
override val uiView: UIView<HistoryState, HistoryAction, HistoryChange>
|
override val uiView: UIView<HistoryState, HistoryAction, HistoryChange>
|
||||||
get() = mockk(relaxed = true)
|
get() = mockk(relaxed = true)
|
||||||
|
|
|
@ -32,8 +32,8 @@ dependencies {
|
||||||
implementation Deps.kotlin_stdlib
|
implementation Deps.kotlin_stdlib
|
||||||
|
|
||||||
implementation Deps.androidx_annotation
|
implementation Deps.androidx_annotation
|
||||||
implementation Deps.androidx_lifecycle_runtime
|
implementation Deps.androidx_lifecycle_extensions
|
||||||
|
implementation Deps.androidx_lifecycle_viewmodel_ss
|
||||||
implementation Deps.mozilla_support_base
|
implementation Deps.mozilla_support_base
|
||||||
|
|
||||||
implementation Deps.rxAndroid
|
implementation Deps.rxAndroid
|
||||||
|
|
|
@ -90,6 +90,7 @@ class ActionBusFactory private constructor(val owner: LifecycleOwner) {
|
||||||
* @param clazz is the Event Class
|
* @param clazz is the Event Class
|
||||||
* @param event is the instance of the Event to be sent
|
* @param event is the instance of the Event to be sent
|
||||||
*/
|
*/
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
fun <T : Action> emit(clazz: Class<T>, event: T) {
|
fun <T : Action> emit(clazz: Class<T>, event: T) {
|
||||||
val subject = if (map[clazz] != null) map[clazz] else create(clazz)
|
val subject = if (map[clazz] != null) map[clazz] else create(clazz)
|
||||||
(subject as Subject<T>).onNext(event)
|
(subject as Subject<T>).onNext(event)
|
||||||
|
@ -102,6 +103,7 @@ class ActionBusFactory private constructor(val owner: LifecycleOwner) {
|
||||||
*
|
*
|
||||||
* @param clazz is the class of the event type used by this observable
|
* @param clazz is the class of the event type used by this observable
|
||||||
*/
|
*/
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
fun <T : Action> getSafeManagedObservable(clazz: Class<T>): Observable<T> {
|
fun <T : Action> getSafeManagedObservable(clazz: Class<T>): Observable<T> {
|
||||||
return if (map[clazz] != null) map[clazz] as Observable<T> else create(clazz)
|
return if (map[clazz] != null) map[clazz] as Observable<T> else create(clazz)
|
||||||
}
|
}
|
||||||
|
@ -111,6 +113,7 @@ class ActionBusFactory private constructor(val owner: LifecycleOwner) {
|
||||||
.`as`(autoDisposable(AndroidLifecycleScopeProvider.from(owner)))
|
.`as`(autoDisposable(AndroidLifecycleScopeProvider.from(owner)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
fun <T : Action> getManagedEmitter(clazz: Class<T>): Observer<T> {
|
fun <T : Action> getManagedEmitter(clazz: Class<T>): Observer<T> {
|
||||||
return if (map[clazz] != null) map[clazz] as Observer<T> else create(clazz)
|
return if (map[clazz] != null) map[clazz] as Observer<T> else create(clazz)
|
||||||
}
|
}
|
||||||
|
@ -132,7 +135,7 @@ class ActionBusFactory private constructor(val owner: LifecycleOwner) {
|
||||||
* Extension on [LifecycleOwner] used to emit an event.
|
* Extension on [LifecycleOwner] used to emit an event.
|
||||||
*/
|
*/
|
||||||
inline fun <reified T : Action> LifecycleOwner.emit(event: T) =
|
inline fun <reified T : Action> LifecycleOwner.emit(event: T) =
|
||||||
kotlin.with(ActionBusFactory.get(this)) {
|
with(ActionBusFactory.get(this)) {
|
||||||
getSafeManagedObservable(T::class.java)
|
getSafeManagedObservable(T::class.java)
|
||||||
emit(T::class.java, event)
|
emit(T::class.java, event)
|
||||||
}
|
}
|
||||||
|
@ -152,10 +155,10 @@ inline fun <reified T : Action> LifecycleOwner.getManagedEmitter(): Observer<T>
|
||||||
/**
|
/**
|
||||||
* This method returns a destroy observable that can be passed to [org.mozilla.fenix.mvi.UIView]s as needed.
|
* This method returns a destroy observable that can be passed to [org.mozilla.fenix.mvi.UIView]s as needed.
|
||||||
*/
|
*/
|
||||||
inline fun LifecycleOwner?.createDestroyObservable(): Observable<Unit> {
|
fun LifecycleOwner?.createDestroyObservable(): Observable<Unit> {
|
||||||
return Observable.create { emitter ->
|
return Observable.create { emitter ->
|
||||||
if (this == null || this.lifecycle.currentState == Lifecycle.State.DESTROYED) {
|
if (this == null || this.lifecycle.currentState == Lifecycle.State.DESTROYED) {
|
||||||
emitter.onNext(kotlin.Unit)
|
emitter.onNext(Unit)
|
||||||
emitter.onComplete()
|
emitter.onComplete()
|
||||||
return@create
|
return@create
|
||||||
}
|
}
|
||||||
|
@ -163,7 +166,7 @@ inline fun LifecycleOwner?.createDestroyObservable(): Observable<Unit> {
|
||||||
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
|
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
|
||||||
fun emitDestroy() {
|
fun emitDestroy() {
|
||||||
if (emitter.isDisposed) {
|
if (emitter.isDisposed) {
|
||||||
emitter.onNext(kotlin.Unit)
|
emitter.onNext(Unit)
|
||||||
emitter.onComplete()
|
emitter.onComplete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
|
|
||||||
package org.mozilla.fenix.mvi
|
package org.mozilla.fenix.mvi
|
||||||
|
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import io.reactivex.Observer
|
import io.reactivex.Observer
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
|
@ -11,28 +13,50 @@ import io.reactivex.disposables.Disposable
|
||||||
import io.reactivex.schedulers.Schedulers
|
import io.reactivex.schedulers.Schedulers
|
||||||
|
|
||||||
abstract class UIComponent<S : ViewState, A : Action, C : Change>(
|
abstract class UIComponent<S : ViewState, A : Action, C : Change>(
|
||||||
|
protected val owner: Fragment,
|
||||||
protected val actionEmitter: Observer<A>,
|
protected val actionEmitter: Observer<A>,
|
||||||
protected val changesObservable: Observable<C>
|
protected val changesObservable: Observable<C>
|
||||||
) {
|
) {
|
||||||
|
|
||||||
abstract var initialState: S
|
abstract var initialState: S
|
||||||
abstract val reducer: Reducer<S, C>
|
|
||||||
|
|
||||||
open val uiView: UIView<S, A, C> by lazy { initView() }
|
open val uiView: UIView<S, A, C> by lazy { initView() }
|
||||||
|
|
||||||
abstract fun initView(): UIView<S, A, C>
|
abstract fun initView(): UIView<S, A, C>
|
||||||
open fun getContainerId() = uiView.containerId
|
open fun getContainerId() = uiView.containerId
|
||||||
|
abstract fun render(): Observable<S>
|
||||||
|
}
|
||||||
|
|
||||||
|
open class UIComponentViewModel<S : ViewState, A : Action, C : Change>(
|
||||||
|
private val initialState: S,
|
||||||
|
val changesObservable: Observable<C>,
|
||||||
|
reducer: Reducer<S, C>
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
private val statesObservable: Observable<S> = internalRender(reducer)
|
||||||
|
private var statesDisposable: Disposable? = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render the ViewState to the View through the Reducer
|
* Render the ViewState to the View through the Reducer
|
||||||
*/
|
*/
|
||||||
fun render(reducer: Reducer<S, C>): Disposable =
|
fun render(uiView: UIView<S, A, C>): Observable<S> {
|
||||||
internalRender(reducer)
|
statesDisposable = statesObservable
|
||||||
.subscribe(uiView.updateView())
|
.subscribe(uiView.updateView())
|
||||||
|
return statesObservable
|
||||||
|
}
|
||||||
|
|
||||||
fun internalRender(reducer: Reducer<S, C>): Observable<S> =
|
@Suppress("MemberVisibilityCanBePrivate")
|
||||||
|
protected fun internalRender(reducer: Reducer<S, C>): Observable<S> =
|
||||||
changesObservable
|
changesObservable
|
||||||
.scan(initialState, reducer)
|
.scan(initialState, reducer)
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.replay(1)
|
||||||
|
.autoConnect(0)
|
||||||
|
|
||||||
|
override fun onCleared() {
|
||||||
|
super.onCleared()
|
||||||
|
statesDisposable?.dispose()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,12 +36,16 @@ abstract class UIView<S : ViewState, A : Action, C : Change>(
|
||||||
/**
|
/**
|
||||||
* Show the UIView
|
* Show the UIView
|
||||||
*/
|
*/
|
||||||
open fun show() { view.visibility = View.VISIBLE }
|
open fun show() {
|
||||||
|
view.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hide the UIView
|
* Hide the UIView
|
||||||
*/
|
*/
|
||||||
open fun hide() { view.visibility = View.GONE }
|
open fun hide() {
|
||||||
|
view.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the view from the ViewState
|
* Update the view from the ViewState
|
||||||
|
|
|
@ -23,6 +23,7 @@ private object Versions {
|
||||||
const val androidx_fragment = "1.1.0-alpha08"
|
const val androidx_fragment = "1.1.0-alpha08"
|
||||||
const val androidx_navigation = "2.1.0-alpha03"
|
const val androidx_navigation = "2.1.0-alpha03"
|
||||||
const val androidx_recyclerview = "1.1.0-alpha05"
|
const val androidx_recyclerview = "1.1.0-alpha05"
|
||||||
|
const val androidx_lifecycle_savedstate = "1.0.0-alpha01"
|
||||||
const val androidx_testing = "1.1.0-alpha08"
|
const val androidx_testing = "1.1.0-alpha08"
|
||||||
const val androidx_core = "1.2.0-alpha01"
|
const val androidx_core = "1.2.0-alpha01"
|
||||||
const val androidx_transition = "1.1.0-rc01"
|
const val androidx_transition = "1.1.0-rc01"
|
||||||
|
@ -134,8 +135,10 @@ object Deps {
|
||||||
const val androidx_appcompat = "androidx.appcompat:appcompat:${Versions.androidx_appcompat}"
|
const val androidx_appcompat = "androidx.appcompat:appcompat:${Versions.androidx_appcompat}"
|
||||||
const val androidx_constraintlayout = "androidx.constraintlayout:constraintlayout:${Versions.androidx_constraint_layout}"
|
const val androidx_constraintlayout = "androidx.constraintlayout:constraintlayout:${Versions.androidx_constraint_layout}"
|
||||||
const val androidx_legacy = "androidx.legacy:legacy-support-v4:${Versions.androidx_legacy}"
|
const val androidx_legacy = "androidx.legacy:legacy-support-v4:${Versions.androidx_legacy}"
|
||||||
|
const val androidx_lifecycle_extensions = "androidx.lifecycle:lifecycle-extensions:${Versions.androidx_lifecycle}"
|
||||||
const val androidx_lifecycle_viewmodel_ktx = "androidx.lifecycle:lifecycle-viewmodel-ktx:${Versions.androidx_lifecycle}"
|
const val androidx_lifecycle_viewmodel_ktx = "androidx.lifecycle:lifecycle-viewmodel-ktx:${Versions.androidx_lifecycle}"
|
||||||
const val androidx_lifecycle_runtime = "androidx.lifecycle:lifecycle-runtime:${Versions.androidx_lifecycle}"
|
const val androidx_lifecycle_runtime = "androidx.lifecycle:lifecycle-runtime:${Versions.androidx_lifecycle}"
|
||||||
|
const val androidx_lifecycle_viewmodel_ss = "androidx.lifecycle:lifecycle-viewmodel-savedstate:${Versions.androidx_lifecycle_savedstate}"
|
||||||
const val androidx_preference = "androidx.preference:preference-ktx:${Versions.androidx_preference}"
|
const val androidx_preference = "androidx.preference:preference-ktx:${Versions.androidx_preference}"
|
||||||
const val androidx_safeargs = "androidx.navigation:navigation-safe-args-gradle-plugin:${Versions.androidx_navigation}"
|
const val androidx_safeargs = "androidx.navigation:navigation-safe-args-gradle-plugin:${Versions.androidx_navigation}"
|
||||||
const val androidx_navigation_fragment = "androidx.navigation:navigation-fragment:${Versions.androidx_navigation}"
|
const val androidx_navigation_fragment = "androidx.navigation:navigation-fragment:${Versions.androidx_navigation}"
|
||||||
|
|
Loading…
Reference in New Issue