Add a HomeActivityViewModel, VisibleRoomHandler and Event classes to handle some navigation. Not perfect but ok for now
This commit is contained in:
parent
922609cb57
commit
2b66b4c4b7
@ -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) })
|
||||
}
|
40
app/src/main/java/im/vector/riotredesign/core/utils/Event.kt
Normal file
40
app/src/main/java/im/vector/riotredesign/core/utils/Event.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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() {
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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())
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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()
|
||||
|
||||
}
|
@ -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) {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user