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
|
||||
- #1599 - Fixed a crash creating a bookmark for a custom tab
|
||||
- #1414 - Fixed site permissions settings getting reset in Android 6.
|
||||
- #1994 - Made app state persist better when rotating the screen
|
||||
|
||||
### Removed
|
||||
|
|
|
@ -337,6 +337,7 @@ dependencies {
|
|||
implementation Deps.androidx_navigation_ui
|
||||
implementation Deps.androidx_recyclerview
|
||||
implementation Deps.androidx_lifecycle_viewmodel_ktx
|
||||
implementation Deps.androidx_lifecycle_viewmodel_ss
|
||||
implementation Deps.androidx_core
|
||||
implementation Deps.androidx_transition
|
||||
|
||||
|
|
|
@ -132,6 +132,7 @@ class BrowserFragment : Fragment(), BackHandler, CoroutineScope,
|
|||
|
||||
toolbarComponent = ToolbarComponent(
|
||||
view.browserLayout,
|
||||
this,
|
||||
ActionBusFactory.get(this), customTabSessionId,
|
||||
(activity as HomeActivity).browsingModeManager.isPrivate,
|
||||
SearchState("", getSessionById()?.searchTerms ?: "", isEditing = false),
|
||||
|
@ -156,6 +157,7 @@ class BrowserFragment : Fragment(), BackHandler, CoroutineScope,
|
|||
|
||||
QuickActionComponent(
|
||||
view.nestedScrollQuickAction,
|
||||
this,
|
||||
ActionBusFactory.get(this),
|
||||
QuickActionState(
|
||||
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/. */
|
||||
|
||||
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.TabCollection
|
||||
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.Reducer
|
||||
import org.mozilla.fenix.mvi.UIComponent
|
||||
import org.mozilla.fenix.mvi.UIComponentViewModel
|
||||
import org.mozilla.fenix.mvi.ViewState
|
||||
|
||||
sealed class SaveCollectionStep {
|
||||
|
@ -51,34 +56,63 @@ sealed class CollectionCreationAction : Action {
|
|||
|
||||
class CollectionCreationComponent(
|
||||
private val container: ViewGroup,
|
||||
owner: Fragment,
|
||||
bus: ActionBusFactory,
|
||||
override var initialState: CollectionCreationState = CollectionCreationState()
|
||||
) : UIComponent<CollectionCreationState, CollectionCreationAction, CollectionCreationChange>(
|
||||
owner,
|
||||
bus.getManagedEmitter(CollectionCreationAction::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 render(): Observable<CollectionCreationState> =
|
||||
ViewModelProvider(owner, CollectionCreationViewModel.Factory(initialState, changesObservable)).get(
|
||||
CollectionCreationViewModel::class.java
|
||||
).render(uiView)
|
||||
|
||||
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(
|
||||
view.create_collection_wrapper,
|
||||
this,
|
||||
ActionBusFactory.get(this),
|
||||
CollectionCreationState(
|
||||
tabs = tabs,
|
||||
|
|
|
@ -7,6 +7,11 @@ package org.mozilla.fenix.components.toolbar
|
|||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
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 mozilla.components.browser.search.SearchEngine
|
||||
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.Reducer
|
||||
import org.mozilla.fenix.mvi.UIComponent
|
||||
import org.mozilla.fenix.mvi.UIComponentViewModel
|
||||
import org.mozilla.fenix.mvi.ViewState
|
||||
|
||||
class ToolbarComponent(
|
||||
private val container: ViewGroup,
|
||||
owner: Fragment,
|
||||
bus: ActionBusFactory,
|
||||
private val sessionId: String?,
|
||||
private val isPrivate: Boolean,
|
||||
|
@ -28,21 +35,13 @@ class ToolbarComponent(
|
|||
private val engineIconView: ImageView? = null
|
||||
) :
|
||||
UIComponent<SearchState, SearchAction, SearchChange>(
|
||||
owner,
|
||||
bus.getManagedEmitter(SearchAction::class.java),
|
||||
bus.getSafeManagedObservable(SearchChange::class.java)
|
||||
) {
|
||||
|
||||
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(
|
||||
sessionId,
|
||||
isPrivate,
|
||||
|
@ -52,8 +51,12 @@ class ToolbarComponent(
|
|||
engineIconView
|
||||
)
|
||||
|
||||
override fun render(): Observable<SearchState> =
|
||||
ViewModelProviders.of(owner, ToolbarViewModel.Factory(initialState, changesObservable))
|
||||
.get(ToolbarViewModel::class.java).render(uiView)
|
||||
|
||||
init {
|
||||
render(reducer)
|
||||
render()
|
||||
applyTheme()
|
||||
}
|
||||
|
||||
|
@ -95,3 +98,27 @@ sealed class SearchChange : Change {
|
|||
object ToolbarClearedFocus : 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 {
|
||||
val sessionManager = view.context.components.core.sessionManager
|
||||
val session = sessionId?.let { sessionManager.findSessionById(it) }
|
||||
?: sessionManager.selectedSession
|
||||
?: sessionManager.selectedSession
|
||||
|
||||
view.apply {
|
||||
elevation = resources.pxToDp(TOOLBAR_ELEVATION).toFloat()
|
||||
|
|
|
@ -4,10 +4,16 @@
|
|||
package org.mozilla.fenix.exceptions
|
||||
|
||||
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.ActionBusFactory
|
||||
import org.mozilla.fenix.mvi.Change
|
||||
import org.mozilla.fenix.mvi.UIComponent
|
||||
import org.mozilla.fenix.mvi.UIComponentViewModel
|
||||
import org.mozilla.fenix.mvi.ViewState
|
||||
import org.mozilla.fenix.test.Mockable
|
||||
|
||||
|
@ -16,24 +22,24 @@ data class ExceptionsItem(val url: String)
|
|||
@Mockable
|
||||
class ExceptionsComponent(
|
||||
private val container: ViewGroup,
|
||||
owner: Fragment,
|
||||
bus: ActionBusFactory,
|
||||
override var initialState: ExceptionsState = ExceptionsState(emptyList())
|
||||
) :
|
||||
UIComponent<ExceptionsState, ExceptionsAction, ExceptionsChange>(
|
||||
owner,
|
||||
bus.getManagedEmitter(ExceptionsAction::class.java),
|
||||
bus.getSafeManagedObservable(ExceptionsChange::class.java)
|
||||
) {
|
||||
|
||||
override val reducer: (ExceptionsState, ExceptionsChange) -> ExceptionsState = { state, change ->
|
||||
when (change) {
|
||||
is ExceptionsChange.Change -> state.copy(items = change.list)
|
||||
}
|
||||
}
|
||||
override fun render(): Observable<ExceptionsState> =
|
||||
ViewModelProviders.of(owner, ExceptionsViewModel.Factory(initialState, changesObservable))
|
||||
.get(ExceptionsViewModel::class.java).render(uiView)
|
||||
|
||||
override fun initView() = ExceptionsUIView(container, actionEmitter, changesObservable)
|
||||
|
||||
init {
|
||||
render(reducer)
|
||||
render()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -49,3 +55,28 @@ sealed class ExceptionsAction : Action {
|
|||
sealed class ExceptionsChange : Change {
|
||||
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)
|
||||
exceptionsComponent = ExceptionsComponent(
|
||||
view.exceptions_layout,
|
||||
this,
|
||||
ActionBusFactory.get(this),
|
||||
ExceptionsState(loadAndMapExceptions())
|
||||
)
|
||||
|
|
|
@ -89,6 +89,7 @@ class HomeFragment : Fragment(), CoroutineScope {
|
|||
|
||||
sessionControlComponent = SessionControlComponent(
|
||||
view.homeLayout,
|
||||
this,
|
||||
bus,
|
||||
SessionControlState(listOf(), listOf(), mode)
|
||||
)
|
||||
|
|
|
@ -6,38 +6,42 @@ package org.mozilla.fenix.home.sessioncontrol
|
|||
|
||||
import android.graphics.Bitmap
|
||||
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 io.reactivex.Observable
|
||||
import io.reactivex.Observer
|
||||
import org.mozilla.fenix.mvi.Action
|
||||
import org.mozilla.fenix.mvi.ActionBusFactory
|
||||
import org.mozilla.fenix.mvi.Change
|
||||
import org.mozilla.fenix.mvi.UIComponent
|
||||
import org.mozilla.fenix.mvi.UIComponentViewModel
|
||||
import org.mozilla.fenix.mvi.ViewState
|
||||
|
||||
class SessionControlComponent(
|
||||
private val container: ViewGroup,
|
||||
owner: Fragment,
|
||||
bus: ActionBusFactory,
|
||||
override var initialState: SessionControlState = SessionControlState(emptyList(), emptyList(), Mode.Normal)
|
||||
) :
|
||||
UIComponent<SessionControlState, SessionControlAction, SessionControlChange>(
|
||||
owner,
|
||||
bus.getManagedEmitter(SessionControlAction::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)
|
||||
val view: 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 {
|
||||
render(reducer)
|
||||
render()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -109,3 +113,30 @@ sealed class SessionControlChange : Change {
|
|||
data class ModeChange(val mode: Mode) : 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
|
||||
|
||||
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 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.UIComponentViewModel
|
||||
import org.mozilla.fenix.mvi.UIView
|
||||
import org.mozilla.fenix.mvi.ViewState
|
||||
import org.mozilla.fenix.test.Mockable
|
||||
|
@ -18,51 +23,28 @@ import org.mozilla.fenix.test.Mockable
|
|||
@Mockable
|
||||
class BookmarkComponent(
|
||||
private val container: ViewGroup,
|
||||
owner: Fragment,
|
||||
bus: ActionBusFactory,
|
||||
override var initialState: BookmarkState =
|
||||
BookmarkState(null, BookmarkState.Mode.Normal)
|
||||
) :
|
||||
UIComponent<BookmarkState, BookmarkAction, BookmarkChange>(
|
||||
owner,
|
||||
bus.getManagedEmitter(BookmarkAction::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> =
|
||||
BookmarkUIView(container, actionEmitter, changesObservable)
|
||||
|
||||
override fun render(): Observable<BookmarkState> {
|
||||
return ViewModelProvider(
|
||||
owner,
|
||||
BookmarkViewModel.Factory(initialState, changesObservable)
|
||||
).get(BookmarkViewModel::class.java).render(uiView)
|
||||
}
|
||||
|
||||
init {
|
||||
render(reducer)
|
||||
render()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -98,3 +80,49 @@ sealed class BookmarkChange : Change {
|
|||
operator fun BookmarkNode.contains(item: BookmarkNode): Boolean {
|
||||
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? {
|
||||
val view = inflater.inflate(R.layout.fragment_bookmark, container, false)
|
||||
bookmarkComponent = BookmarkComponent(view.bookmark_layout, ActionBusFactory.get(this))
|
||||
signInComponent = SignInComponent(view.bookmark_layout, ActionBusFactory.get(this))
|
||||
bookmarkComponent = BookmarkComponent(view.bookmark_layout, this, ActionBusFactory.get(this))
|
||||
signInComponent = SignInComponent(view.bookmark_layout, this, ActionBusFactory.get(this))
|
||||
return view
|
||||
}
|
||||
|
||||
|
|
|
@ -5,36 +5,41 @@
|
|||
package org.mozilla.fenix.library.bookmarks
|
||||
|
||||
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.ActionBusFactory
|
||||
import org.mozilla.fenix.mvi.Change
|
||||
import org.mozilla.fenix.mvi.Reducer
|
||||
import org.mozilla.fenix.mvi.UIComponent
|
||||
import org.mozilla.fenix.mvi.UIComponentViewModel
|
||||
import org.mozilla.fenix.mvi.UIView
|
||||
import org.mozilla.fenix.mvi.ViewState
|
||||
|
||||
class SignInComponent(
|
||||
private val container: ViewGroup,
|
||||
owner: Fragment,
|
||||
bus: ActionBusFactory,
|
||||
override var initialState: SignInState =
|
||||
SignInState(false)
|
||||
) : UIComponent<SignInState, SignInAction, SignInChange>(
|
||||
owner,
|
||||
bus.getManagedEmitter(SignInAction::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> =
|
||||
SignInUIView(container, actionEmitter, changesObservable)
|
||||
|
||||
override fun render(): Observable<SignInState> =
|
||||
ViewModelProvider(
|
||||
owner,
|
||||
SignInViewModel.Factory(initialState, changesObservable)
|
||||
).get(SignInViewModel::class.java).render(uiView)
|
||||
|
||||
init {
|
||||
render(reducer)
|
||||
render()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -48,3 +53,29 @@ sealed class SignInChange : Change {
|
|||
object SignedIn : 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? {
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -4,56 +4,42 @@
|
|||
package org.mozilla.fenix.library.history
|
||||
|
||||
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.ActionBusFactory
|
||||
import org.mozilla.fenix.mvi.Change
|
||||
import org.mozilla.fenix.mvi.UIComponent
|
||||
import org.mozilla.fenix.mvi.UIComponentViewModel
|
||||
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)
|
||||
|
||||
@Mockable
|
||||
class HistoryComponent(
|
||||
private val container: ViewGroup,
|
||||
owner: Fragment,
|
||||
bus: ActionBusFactory,
|
||||
override var initialState: HistoryState = HistoryState(emptyList(), HistoryState.Mode.Normal)
|
||||
) :
|
||||
UIComponent<HistoryState, HistoryAction, HistoryChange>(
|
||||
owner,
|
||||
bus.getManagedEmitter(HistoryAction::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 render(): Observable<HistoryState> =
|
||||
ViewModelProvider(
|
||||
owner,
|
||||
HistoryViewModel.Factory(initialState, changesObservable)
|
||||
).get(HistoryViewModel::class.java).render(uiView)
|
||||
|
||||
init {
|
||||
render(reducer)
|
||||
render()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -85,3 +71,44 @@ sealed class HistoryChange : Change {
|
|||
data class AddItemForRemoval(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?
|
||||
): View? {
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -5,16 +5,22 @@
|
|||
package org.mozilla.fenix.quickactionsheet
|
||||
|
||||
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.ActionBusFactory
|
||||
import org.mozilla.fenix.mvi.Change
|
||||
import org.mozilla.fenix.mvi.Reducer
|
||||
import org.mozilla.fenix.mvi.UIComponent
|
||||
import org.mozilla.fenix.mvi.UIComponentViewModel
|
||||
import org.mozilla.fenix.mvi.UIView
|
||||
import org.mozilla.fenix.mvi.ViewState
|
||||
|
||||
class QuickActionComponent(
|
||||
private val container: ViewGroup,
|
||||
owner: Fragment,
|
||||
bus: ActionBusFactory,
|
||||
override var initialState: QuickActionState = QuickActionState(
|
||||
readable = false,
|
||||
|
@ -22,29 +28,21 @@ class QuickActionComponent(
|
|||
readerActive = false
|
||||
)
|
||||
) : UIComponent<QuickActionState, QuickActionAction, QuickActionChange>(
|
||||
owner,
|
||||
bus.getManagedEmitter(QuickActionAction::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> =
|
||||
QuickActionUIView(container, actionEmitter, changesObservable)
|
||||
|
||||
override fun render(): Observable<QuickActionState> =
|
||||
ViewModelProvider(
|
||||
owner,
|
||||
QuickActionViewModel.Factory(initialState, changesObservable)
|
||||
).get(QuickActionViewModel::class.java).render(uiView)
|
||||
|
||||
init {
|
||||
render(reducer)
|
||||
render()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -65,3 +63,36 @@ sealed class QuickActionChange : Change {
|
|||
data class ReadableStateChange(val readable: 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(
|
||||
view.toolbar_component_wrapper,
|
||||
this,
|
||||
ActionBusFactory.get(this),
|
||||
sessionId,
|
||||
isPrivate,
|
||||
|
@ -74,7 +75,7 @@ class SearchFragment : Fragment(), BackHandler {
|
|||
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()
|
||||
return view
|
||||
}
|
||||
|
|
|
@ -1,15 +1,23 @@
|
|||
package org.mozilla.fenix.search.awesomebar
|
||||
|
||||
/* 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/. */
|
||||
|
||||
import android.util.Log
|
||||
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 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.UIComponentViewModel
|
||||
import org.mozilla.fenix.mvi.ViewState
|
||||
|
||||
data class AwesomeBarState(
|
||||
|
@ -32,25 +40,51 @@ sealed class AwesomeBarChange : Change {
|
|||
|
||||
class AwesomeBarComponent(
|
||||
private val container: ViewGroup,
|
||||
owner: Fragment,
|
||||
bus: ActionBusFactory,
|
||||
override var initialState: AwesomeBarState = AwesomeBarState("", false)
|
||||
) : UIComponent<AwesomeBarState, AwesomeBarAction, AwesomeBarChange>(
|
||||
owner,
|
||||
bus.getManagedEmitter(AwesomeBarAction::class.java),
|
||||
bus.getSafeManagedObservable(AwesomeBarChange::class.java)
|
||||
) {
|
||||
override val reducer: Reducer<AwesomeBarState, AwesomeBarChange> = { state, change ->
|
||||
when (change) {
|
||||
is AwesomeBarChange.SearchShortcutEngineSelected ->
|
||||
state.copy(suggestionEngine = change.engine, showShortcutEnginePicker = false)
|
||||
is AwesomeBarChange.SearchShortcutEnginePicker ->
|
||||
state.copy(showShortcutEnginePicker = change.show)
|
||||
is AwesomeBarChange.UpdateQuery -> state.copy(query = change.query)
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
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.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.support.ktx.kotlin.toUri
|
||||
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.Change
|
||||
import org.mozilla.fenix.mvi.UIComponent
|
||||
import org.mozilla.fenix.mvi.UIComponentViewModel
|
||||
import org.mozilla.fenix.mvi.UIView
|
||||
import org.mozilla.fenix.mvi.ViewState
|
||||
import org.mozilla.fenix.settings.PhoneFeature
|
||||
|
@ -22,48 +27,25 @@ import org.mozilla.fenix.utils.Settings
|
|||
|
||||
class QuickSettingsComponent(
|
||||
private val container: ViewGroup,
|
||||
owner: Fragment,
|
||||
bus: ActionBusFactory,
|
||||
override var initialState: QuickSettingsState
|
||||
) : UIComponent<QuickSettingsState, QuickSettingsAction, QuickSettingsChange>(
|
||||
owner,
|
||||
bus.getManagedEmitter(QuickSettingsAction::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> {
|
||||
return QuickSettingsUIView(container, actionEmitter, changesObservable, container)
|
||||
}
|
||||
|
||||
override fun render(): Observable<QuickSettingsState> =
|
||||
ViewModelProvider(owner, QuickSettingsViewModel.Factory(initialState, changesObservable)).get(
|
||||
QuickSettingsViewModel::class.java
|
||||
).render(uiView)
|
||||
|
||||
init {
|
||||
render(reducer)
|
||||
render()
|
||||
}
|
||||
|
||||
fun toggleSitePermission(
|
||||
|
@ -137,3 +119,52 @@ sealed class QuickSettingsChange : Change {
|
|||
data class PromptRestarted(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?,
|
||||
savedInstanceState: Bundle?
|
||||
): 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 {
|
||||
|
@ -115,21 +127,6 @@ class QuickSettingsSheetDialogFragment : AppCompatDialogFragment(), CoroutineSco
|
|||
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 {
|
||||
const val FRAGMENT_TAG = "QUICK_SETTINGS_FRAGMENT_TAG"
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ class BookmarkComponentTest {
|
|||
TestBookmarkComponent(mockk(), TestUtils.bus),
|
||||
recordPrivateCalls = true
|
||||
)
|
||||
bookmarkObserver = bookmarkComponent.internalRender(bookmarkComponent.reducer).test()
|
||||
bookmarkObserver = bookmarkComponent.render().test()
|
||||
emitter = TestUtils.owner.getManagedEmitter()
|
||||
}
|
||||
|
||||
|
@ -87,7 +87,7 @@ class BookmarkComponentTest {
|
|||
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
class TestBookmarkComponent(container: ViewGroup, bus: ActionBusFactory) :
|
||||
BookmarkComponent(container, bus) {
|
||||
BookmarkComponent(container, mockk(relaxed = true), bus) {
|
||||
|
||||
override val uiView: UIView<BookmarkState, BookmarkAction, BookmarkChange>
|
||||
get() = mockk(relaxed = true)
|
||||
|
|
|
@ -34,7 +34,7 @@ class HistoryComponentTest {
|
|||
TestHistoryComponent(mockk(), bus),
|
||||
recordPrivateCalls = true
|
||||
)
|
||||
historyObserver = historyComponent.internalRender(historyComponent.reducer).test()
|
||||
historyObserver = historyComponent.render().test()
|
||||
emitter = owner.getManagedEmitter()
|
||||
}
|
||||
|
||||
|
@ -82,7 +82,7 @@ class HistoryComponentTest {
|
|||
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
class TestHistoryComponent(container: ViewGroup, bus: ActionBusFactory) :
|
||||
HistoryComponent(container, bus) {
|
||||
HistoryComponent(container, mockk(relaxed = true), bus) {
|
||||
|
||||
override val uiView: UIView<HistoryState, HistoryAction, HistoryChange>
|
||||
get() = mockk(relaxed = true)
|
||||
|
|
|
@ -32,8 +32,8 @@ dependencies {
|
|||
implementation Deps.kotlin_stdlib
|
||||
|
||||
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.rxAndroid
|
||||
|
|
|
@ -90,6 +90,7 @@ class ActionBusFactory private constructor(val owner: LifecycleOwner) {
|
|||
* @param clazz is the Event Class
|
||||
* @param event is the instance of the Event to be sent
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <T : Action> emit(clazz: Class<T>, event: T) {
|
||||
val subject = if (map[clazz] != null) map[clazz] else create(clazz)
|
||||
(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
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <T : Action> getSafeManagedObservable(clazz: Class<T>): Observable<T> {
|
||||
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)))
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <T : Action> getManagedEmitter(clazz: Class<T>): Observer<T> {
|
||||
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.
|
||||
*/
|
||||
inline fun <reified T : Action> LifecycleOwner.emit(event: T) =
|
||||
kotlin.with(ActionBusFactory.get(this)) {
|
||||
with(ActionBusFactory.get(this)) {
|
||||
getSafeManagedObservable(T::class.java)
|
||||
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.
|
||||
*/
|
||||
inline fun LifecycleOwner?.createDestroyObservable(): Observable<Unit> {
|
||||
fun LifecycleOwner?.createDestroyObservable(): Observable<Unit> {
|
||||
return Observable.create { emitter ->
|
||||
if (this == null || this.lifecycle.currentState == Lifecycle.State.DESTROYED) {
|
||||
emitter.onNext(kotlin.Unit)
|
||||
emitter.onNext(Unit)
|
||||
emitter.onComplete()
|
||||
return@create
|
||||
}
|
||||
|
@ -163,7 +166,7 @@ inline fun LifecycleOwner?.createDestroyObservable(): Observable<Unit> {
|
|||
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
|
||||
fun emitDestroy() {
|
||||
if (emitter.isDisposed) {
|
||||
emitter.onNext(kotlin.Unit)
|
||||
emitter.onNext(Unit)
|
||||
emitter.onComplete()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
package org.mozilla.fenix.mvi
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModel
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Observer
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
|
@ -11,28 +13,50 @@ import io.reactivex.disposables.Disposable
|
|||
import io.reactivex.schedulers.Schedulers
|
||||
|
||||
abstract class UIComponent<S : ViewState, A : Action, C : Change>(
|
||||
protected val owner: Fragment,
|
||||
protected val actionEmitter: Observer<A>,
|
||||
protected val changesObservable: Observable<C>
|
||||
) {
|
||||
|
||||
abstract var initialState: S
|
||||
abstract val reducer: Reducer<S, C>
|
||||
|
||||
open val uiView: UIView<S, A, C> by lazy { initView() }
|
||||
|
||||
abstract fun initView(): UIView<S, A, C>
|
||||
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
|
||||
*/
|
||||
fun render(reducer: Reducer<S, C>): Disposable =
|
||||
internalRender(reducer)
|
||||
fun render(uiView: UIView<S, A, C>): Observable<S> {
|
||||
statesDisposable = statesObservable
|
||||
.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
|
||||
.scan(initialState, reducer)
|
||||
.distinctUntilChanged()
|
||||
.subscribeOn(Schedulers.io())
|
||||
.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
|
||||
*/
|
||||
open fun show() { view.visibility = View.VISIBLE }
|
||||
open fun show() {
|
||||
view.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the UIView
|
||||
*/
|
||||
open fun hide() { view.visibility = View.GONE }
|
||||
open fun hide() {
|
||||
view.visibility = View.GONE
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the view from the ViewState
|
||||
|
|
|
@ -23,6 +23,7 @@ private object Versions {
|
|||
const val androidx_fragment = "1.1.0-alpha08"
|
||||
const val androidx_navigation = "2.1.0-alpha03"
|
||||
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_core = "1.2.0-alpha01"
|
||||
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_constraintlayout = "androidx.constraintlayout:constraintlayout:${Versions.androidx_constraint_layout}"
|
||||
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_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_safeargs = "androidx.navigation:navigation-safe-args-gradle-plugin:${Versions.androidx_navigation}"
|
||||
const val androidx_navigation_fragment = "androidx.navigation:navigation-fragment:${Versions.androidx_navigation}"
|
||||
|
|
Loading…
Reference in New Issue