Add a HomeActivityViewModel, VisibleRoomHandler and Event classes to handle some navigation. Not perfect but ok for now

This commit is contained in:
ganfra 2019-01-11 12:07:38 +01:00
parent 922609cb57
commit 2b66b4c4b7
11 changed files with 184 additions and 19 deletions

View File

@ -0,0 +1,19 @@
package im.vector.riotredesign.core.extensions

import android.arch.lifecycle.LifecycleOwner
import android.arch.lifecycle.LiveData
import android.arch.lifecycle.Observer
import im.vector.riotredesign.core.utils.Event
import im.vector.riotredesign.core.utils.EventObserver

inline fun <T> LiveData<T>.observeK(owner: LifecycleOwner, crossinline observer: (T?) -> Unit) {
this.observe(owner, Observer { observer(it) })
}

inline fun <T> LiveData<T>.observeNotNull(owner: LifecycleOwner, crossinline observer: (T) -> Unit) {
this.observe(owner, Observer { it?.run(observer) })
}

inline fun <T> LiveData<Event<T>>.observeEvent(owner: LifecycleOwner, crossinline observer: (T) -> Unit) {
this.observe(owner, EventObserver { it.run(observer) })
}

View File

@ -0,0 +1,40 @@
package im.vector.riotredesign.core.utils

import android.arch.lifecycle.Observer

open class Event<out T>(private val content: T) {

var hasBeenHandled = false
private set // Allow external read but not write

/**
* Returns the content and prevents its use again.
*/
fun getContentIfNotHandled(): T? {
return if (hasBeenHandled) {
null
} else {
hasBeenHandled = true
content
}
}

/**
* Returns the content, even if it's already been handled.
*/
fun peekContent(): T = content
}

/**
* An [Observer] for [Event]s, simplifying the pattern of checking if the [Event]'s content has
* already been handled.
*
* [onEventUnhandledContent] is *only* called if the [Event]'s contents has not been handled.
*/
class EventObserver<T>(private val onEventUnhandledContent: (T) -> Unit) : Observer<Event<T>> {
override fun onChanged(event: Event<T>?) {
event?.getContentIfNotHandled()?.let { value ->
onEventUnhandledContent(value)
}
}
}

View File

@ -9,7 +9,9 @@ import android.support.v7.app.ActionBarDrawerToggle
import android.support.v7.widget.Toolbar
import android.view.Gravity
import android.view.MenuItem
import com.airbnb.mvrx.viewModel
import im.vector.riotredesign.R
import im.vector.riotredesign.core.extensions.observeEvent
import im.vector.riotredesign.core.extensions.replaceFragment
import im.vector.riotredesign.core.platform.OnBackPressed
import im.vector.riotredesign.core.platform.RiotActivity
@ -22,6 +24,8 @@ import org.koin.standalone.StandAloneContext.loadKoinModules

class HomeActivity : RiotActivity(), ToolbarConfigurable {


private val homeActivityViewModel: HomeActivityViewModel by viewModel()
private val homeNavigator by inject<HomeNavigator>()

override fun onCreate(savedInstanceState: Bundle?) {
@ -35,6 +39,9 @@ class HomeActivity : RiotActivity(), ToolbarConfigurable {
replaceFragment(loadingDetail, R.id.homeDetailFragmentContainer)
replaceFragment(homeDrawerFragment, R.id.homeDrawerFragmentContainer)
}
homeActivityViewModel.openRoomLiveData.observeEvent(this) {
homeNavigator.openRoomDetail(it, null)
}
}

override fun onDestroy() {

View File

@ -0,0 +1,61 @@
package im.vector.riotredesign.features.home

import android.arch.lifecycle.LiveData
import android.arch.lifecycle.MutableLiveData
import android.support.v4.app.FragmentActivity
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.MvRxViewModelFactory
import im.vector.matrix.android.api.Matrix
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.rx.rx
import im.vector.riotredesign.core.platform.RiotViewModel
import im.vector.riotredesign.core.utils.Event
import im.vector.riotredesign.features.home.room.list.RoomSelectionRepository
import io.reactivex.rxkotlin.subscribeBy
import org.koin.android.ext.android.get

class EmptyState : MvRxState

class HomeActivityViewModel(state: EmptyState,
private val session: Session,
roomSelectionRepository: RoomSelectionRepository
) : RiotViewModel<EmptyState>(state) {

companion object : MvRxViewModelFactory<EmptyState> {

@JvmStatic
override fun create(activity: FragmentActivity, state: EmptyState): HomeActivityViewModel {
val session = Matrix.getInstance().currentSession
val roomSelectionRepository = activity.get<RoomSelectionRepository>()
return HomeActivityViewModel(state, session, roomSelectionRepository)
}
}

private val _openRoomLiveData = MutableLiveData<Event<String>>()
val openRoomLiveData: LiveData<Event<String>>
get() = _openRoomLiveData

init {
val lastSelectedRoom = roomSelectionRepository.lastSelectedRoom()
if (lastSelectedRoom == null) {
getTheFirstRoomWhenAvailable()
} else {
_openRoomLiveData.postValue(Event(lastSelectedRoom))
}
}

private fun getTheFirstRoomWhenAvailable() {
session.rx().liveRoomSummaries()
.filter { it.isNotEmpty() }
.first(emptyList())
.subscribeBy {
val firstRoom = it.firstOrNull()
if (firstRoom != null) {
_openRoomLiveData.postValue(Event(firstRoom.roomId))
}
}
.disposeOnClear()
}


}

View File

@ -1,6 +1,7 @@
package im.vector.riotredesign.features.home

import im.vector.riotredesign.features.home.group.SelectedGroupHolder
import im.vector.riotredesign.features.home.room.VisibleRoomHolder
import im.vector.riotredesign.features.home.room.detail.timeline.MessageItemFactory
import im.vector.riotredesign.features.home.room.detail.timeline.TextItemFactory
import im.vector.riotredesign.features.home.room.detail.timeline.TimelineDateFormatter
@ -35,9 +36,14 @@ class HomeModule(private val homeActivity: HomeActivity) {
SelectedGroupHolder()
}

single {
VisibleRoomHolder()
}

single {
HomePermalinkHandler(get())
}


}
}

View File

@ -0,0 +1,19 @@
package im.vector.riotredesign.features.home.room

import io.reactivex.Observable
import io.reactivex.subjects.BehaviorSubject

class VisibleRoomHolder {

private val visibleRoomStream = BehaviorSubject.create<String>()

fun setVisibleRoom(roomId: String) {
visibleRoomStream.onNext(roomId)
}

fun visibleRoom(): Observable<String> {
return visibleRoomStream.hide()
}


}

View File

@ -3,5 +3,6 @@ package im.vector.riotredesign.features.home.room.detail
sealed class RoomDetailActions {

data class SendMessage(val text: String) : RoomDetailActions()
object IsDisplayed : RoomDetailActions()

}

View File

@ -62,6 +62,11 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback {
roomDetailViewModel.subscribe { renderState(it) }
}

override fun onResume() {
super.onResume()
roomDetailViewModel.accept(RoomDetailActions.IsDisplayed)
}

private fun setupToolbar() {
val parentActivity = riotActivity
if (parentActivity is ToolbarConfigurable) {

View File

@ -8,9 +8,12 @@ import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.rx.rx
import im.vector.riotredesign.core.platform.RiotViewModel
import im.vector.riotredesign.features.home.room.VisibleRoomHolder
import org.koin.android.ext.android.get

class RoomDetailViewModel(initialState: RoomDetailViewState,
session: Session
private val session: Session,
private val visibleRoomHolder: VisibleRoomHolder
) : RiotViewModel<RoomDetailViewState>(initialState) {

private val room = session.getRoom(initialState.roomId)!!
@ -22,7 +25,8 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
@JvmStatic
override fun create(activity: FragmentActivity, state: RoomDetailViewState): RoomDetailViewModel {
val currentSession = Matrix.getInstance().currentSession
return RoomDetailViewModel(state, currentSession)
val visibleRoomHolder = activity.get<VisibleRoomHolder>()
return RoomDetailViewModel(state, currentSession, visibleRoomHolder)
}
}

@ -35,6 +39,7 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
fun accept(action: RoomDetailActions) {
when (action) {
is RoomDetailActions.SendMessage -> handleSendMessage(action)
is RoomDetailActions.IsDisplayed -> visibleRoomHolder.setVisibleRoom(roomId)
}
}

@ -54,7 +59,7 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
private fun observeTimeline() {
room.rx().timeline(eventId)
.execute { timelineData ->
copy(asyncTimelineData= timelineData)
copy(asyncTimelineData = timelineData)
}
}


View File

@ -44,15 +44,12 @@ class RoomListFragment : RiotFragment(), RoomSummaryController.Callback {
private fun renderState(state: RoomListViewState) {
when (state.asyncRooms) {
is Incomplete -> renderLoading()
is Success -> renderSuccess(state)
is Fail -> renderFailure(state.asyncRooms.error)
is Success -> renderSuccess(state)
is Fail -> renderFailure(state.asyncRooms.error)
}
}

private fun renderSuccess(state: RoomListViewState) {
if (state.selectedRoomId != null) {
homeNavigator.openRoomDetail(state.selectedRoomId, null)
}
if (state.asyncRooms().isNullOrEmpty()) {
stateView.state = StateView.State.Empty(getString(R.string.room_list_empty))
} else {
@ -68,7 +65,7 @@ class RoomListFragment : RiotFragment(), RoomSummaryController.Callback {
private fun renderFailure(error: Throwable) {
val message = when (error) {
is Failure.NetworkConnection -> getString(R.string.error_no_network)
else -> getString(R.string.error_common)
else -> getString(R.string.error_common)
}
stateView.state = StateView.State.Error(message)
}

View File

@ -10,13 +10,16 @@ import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.rx.rx
import im.vector.riotredesign.core.platform.RiotViewModel
import im.vector.riotredesign.features.home.group.SelectedGroupHolder
import im.vector.riotredesign.features.home.room.VisibleRoomHolder
import io.reactivex.Observable
import io.reactivex.functions.BiFunction
import io.reactivex.rxkotlin.subscribeBy
import org.koin.android.ext.android.get

class RoomListViewModel(initialState: RoomListViewState,
private val session: Session,
private val selectedGroupHolder: SelectedGroupHolder,
private val visibleRoomHolder: VisibleRoomHolder,
private val roomSelectionRepository: RoomSelectionRepository)
: RiotViewModel<RoomListViewState>(initialState) {

@ -27,12 +30,14 @@ class RoomListViewModel(initialState: RoomListViewState,
val currentSession = Matrix.getInstance().currentSession
val roomSelectionRepository = activity.get<RoomSelectionRepository>()
val selectedGroupHolder = activity.get<SelectedGroupHolder>()
return RoomListViewModel(state, currentSession, selectedGroupHolder, roomSelectionRepository)
val visibleRoomHolder = activity.get<VisibleRoomHolder>()
return RoomListViewModel(state, currentSession, selectedGroupHolder, visibleRoomHolder, roomSelectionRepository)
}
}

init {
observeRoomSummaries()
observeVisibleRoom()
}

fun accept(action: RoomListActions) {
@ -46,10 +51,17 @@ class RoomListViewModel(initialState: RoomListViewState,
private fun handleSelectRoom(action: RoomListActions.SelectRoom) = withState { state ->
if (state.selectedRoomId != action.roomSummary.roomId) {
roomSelectionRepository.saveLastSelectedRoom(action.roomSummary.roomId)
setState { copy(selectedRoomId = action.roomSummary.roomId) }
}
}

private fun observeVisibleRoom() {
visibleRoomHolder.visibleRoom()
.subscribeBy {
setState { copy(selectedRoomId = it) }
}
.disposeOnClear()
}

private fun observeRoomSummaries() {
Observable.combineLatest<List<RoomSummary>, Option<GroupSummary>, RoomSummaries>(
session.rx().liveRoomSummaries(),
@ -78,15 +90,8 @@ class RoomListViewModel(initialState: RoomListViewState,
}
)
.execute { async ->
val summaries = async()
val selectedRoomId = selectedRoomId
?: roomSelectionRepository.lastSelectedRoom()
?: summaries?.directRooms?.firstOrNull()?.roomId
?: summaries?.groupRooms?.firstOrNull()?.roomId

copy(
asyncRooms = async,
selectedRoomId = selectedRoomId
asyncRooms = async
)
}
}