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.support.v7.widget.Toolbar
|
||||||
import android.view.Gravity
|
import android.view.Gravity
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
|
import com.airbnb.mvrx.viewModel
|
||||||
import im.vector.riotredesign.R
|
import im.vector.riotredesign.R
|
||||||
|
import im.vector.riotredesign.core.extensions.observeEvent
|
||||||
import im.vector.riotredesign.core.extensions.replaceFragment
|
import im.vector.riotredesign.core.extensions.replaceFragment
|
||||||
import im.vector.riotredesign.core.platform.OnBackPressed
|
import im.vector.riotredesign.core.platform.OnBackPressed
|
||||||
import im.vector.riotredesign.core.platform.RiotActivity
|
import im.vector.riotredesign.core.platform.RiotActivity
|
||||||
@ -22,6 +24,8 @@ import org.koin.standalone.StandAloneContext.loadKoinModules
|
|||||||
|
|
||||||
class HomeActivity : RiotActivity(), ToolbarConfigurable {
|
class HomeActivity : RiotActivity(), ToolbarConfigurable {
|
||||||
|
|
||||||
|
|
||||||
|
private val homeActivityViewModel: HomeActivityViewModel by viewModel()
|
||||||
private val homeNavigator by inject<HomeNavigator>()
|
private val homeNavigator by inject<HomeNavigator>()
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
@ -35,6 +39,9 @@ class HomeActivity : RiotActivity(), ToolbarConfigurable {
|
|||||||
replaceFragment(loadingDetail, R.id.homeDetailFragmentContainer)
|
replaceFragment(loadingDetail, R.id.homeDetailFragmentContainer)
|
||||||
replaceFragment(homeDrawerFragment, R.id.homeDrawerFragmentContainer)
|
replaceFragment(homeDrawerFragment, R.id.homeDrawerFragmentContainer)
|
||||||
}
|
}
|
||||||
|
homeActivityViewModel.openRoomLiveData.observeEvent(this) {
|
||||||
|
homeNavigator.openRoomDetail(it, null)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
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
|
package im.vector.riotredesign.features.home
|
||||||
|
|
||||||
import im.vector.riotredesign.features.home.group.SelectedGroupHolder
|
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.MessageItemFactory
|
||||||
import im.vector.riotredesign.features.home.room.detail.timeline.TextItemFactory
|
import im.vector.riotredesign.features.home.room.detail.timeline.TextItemFactory
|
||||||
import im.vector.riotredesign.features.home.room.detail.timeline.TimelineDateFormatter
|
import im.vector.riotredesign.features.home.room.detail.timeline.TimelineDateFormatter
|
||||||
@ -35,9 +36,14 @@ class HomeModule(private val homeActivity: HomeActivity) {
|
|||||||
SelectedGroupHolder()
|
SelectedGroupHolder()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
single {
|
||||||
|
VisibleRoomHolder()
|
||||||
|
}
|
||||||
|
|
||||||
single {
|
single {
|
||||||
HomePermalinkHandler(get())
|
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 {
|
sealed class RoomDetailActions {
|
||||||
|
|
||||||
data class SendMessage(val text: String) : RoomDetailActions()
|
data class SendMessage(val text: String) : RoomDetailActions()
|
||||||
|
object IsDisplayed : RoomDetailActions()
|
||||||
|
|
||||||
}
|
}
|
@ -62,6 +62,11 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback {
|
|||||||
roomDetailViewModel.subscribe { renderState(it) }
|
roomDetailViewModel.subscribe { renderState(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
roomDetailViewModel.accept(RoomDetailActions.IsDisplayed)
|
||||||
|
}
|
||||||
|
|
||||||
private fun setupToolbar() {
|
private fun setupToolbar() {
|
||||||
val parentActivity = riotActivity
|
val parentActivity = riotActivity
|
||||||
if (parentActivity is ToolbarConfigurable) {
|
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.android.api.session.events.model.Event
|
||||||
import im.vector.matrix.rx.rx
|
import im.vector.matrix.rx.rx
|
||||||
import im.vector.riotredesign.core.platform.RiotViewModel
|
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,
|
class RoomDetailViewModel(initialState: RoomDetailViewState,
|
||||||
session: Session
|
private val session: Session,
|
||||||
|
private val visibleRoomHolder: VisibleRoomHolder
|
||||||
) : RiotViewModel<RoomDetailViewState>(initialState) {
|
) : RiotViewModel<RoomDetailViewState>(initialState) {
|
||||||
|
|
||||||
private val room = session.getRoom(initialState.roomId)!!
|
private val room = session.getRoom(initialState.roomId)!!
|
||||||
@ -22,7 +25,8 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
|
|||||||
@JvmStatic
|
@JvmStatic
|
||||||
override fun create(activity: FragmentActivity, state: RoomDetailViewState): RoomDetailViewModel {
|
override fun create(activity: FragmentActivity, state: RoomDetailViewState): RoomDetailViewModel {
|
||||||
val currentSession = Matrix.getInstance().currentSession
|
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) {
|
fun accept(action: RoomDetailActions) {
|
||||||
when (action) {
|
when (action) {
|
||||||
is RoomDetailActions.SendMessage -> handleSendMessage(action)
|
is RoomDetailActions.SendMessage -> handleSendMessage(action)
|
||||||
|
is RoomDetailActions.IsDisplayed -> visibleRoomHolder.setVisibleRoom(roomId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,7 +59,7 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
|
|||||||
private fun observeTimeline() {
|
private fun observeTimeline() {
|
||||||
room.rx().timeline(eventId)
|
room.rx().timeline(eventId)
|
||||||
.execute { timelineData ->
|
.execute { timelineData ->
|
||||||
copy(asyncTimelineData= timelineData)
|
copy(asyncTimelineData = timelineData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,15 +44,12 @@ class RoomListFragment : RiotFragment(), RoomSummaryController.Callback {
|
|||||||
private fun renderState(state: RoomListViewState) {
|
private fun renderState(state: RoomListViewState) {
|
||||||
when (state.asyncRooms) {
|
when (state.asyncRooms) {
|
||||||
is Incomplete -> renderLoading()
|
is Incomplete -> renderLoading()
|
||||||
is Success -> renderSuccess(state)
|
is Success -> renderSuccess(state)
|
||||||
is Fail -> renderFailure(state.asyncRooms.error)
|
is Fail -> renderFailure(state.asyncRooms.error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renderSuccess(state: RoomListViewState) {
|
private fun renderSuccess(state: RoomListViewState) {
|
||||||
if (state.selectedRoomId != null) {
|
|
||||||
homeNavigator.openRoomDetail(state.selectedRoomId, null)
|
|
||||||
}
|
|
||||||
if (state.asyncRooms().isNullOrEmpty()) {
|
if (state.asyncRooms().isNullOrEmpty()) {
|
||||||
stateView.state = StateView.State.Empty(getString(R.string.room_list_empty))
|
stateView.state = StateView.State.Empty(getString(R.string.room_list_empty))
|
||||||
} else {
|
} else {
|
||||||
@ -68,7 +65,7 @@ class RoomListFragment : RiotFragment(), RoomSummaryController.Callback {
|
|||||||
private fun renderFailure(error: Throwable) {
|
private fun renderFailure(error: Throwable) {
|
||||||
val message = when (error) {
|
val message = when (error) {
|
||||||
is Failure.NetworkConnection -> getString(R.string.error_no_network)
|
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)
|
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.matrix.rx.rx
|
||||||
import im.vector.riotredesign.core.platform.RiotViewModel
|
import im.vector.riotredesign.core.platform.RiotViewModel
|
||||||
import im.vector.riotredesign.features.home.group.SelectedGroupHolder
|
import im.vector.riotredesign.features.home.group.SelectedGroupHolder
|
||||||
|
import im.vector.riotredesign.features.home.room.VisibleRoomHolder
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import io.reactivex.functions.BiFunction
|
import io.reactivex.functions.BiFunction
|
||||||
|
import io.reactivex.rxkotlin.subscribeBy
|
||||||
import org.koin.android.ext.android.get
|
import org.koin.android.ext.android.get
|
||||||
|
|
||||||
class RoomListViewModel(initialState: RoomListViewState,
|
class RoomListViewModel(initialState: RoomListViewState,
|
||||||
private val session: Session,
|
private val session: Session,
|
||||||
private val selectedGroupHolder: SelectedGroupHolder,
|
private val selectedGroupHolder: SelectedGroupHolder,
|
||||||
|
private val visibleRoomHolder: VisibleRoomHolder,
|
||||||
private val roomSelectionRepository: RoomSelectionRepository)
|
private val roomSelectionRepository: RoomSelectionRepository)
|
||||||
: RiotViewModel<RoomListViewState>(initialState) {
|
: RiotViewModel<RoomListViewState>(initialState) {
|
||||||
|
|
||||||
@ -27,12 +30,14 @@ class RoomListViewModel(initialState: RoomListViewState,
|
|||||||
val currentSession = Matrix.getInstance().currentSession
|
val currentSession = Matrix.getInstance().currentSession
|
||||||
val roomSelectionRepository = activity.get<RoomSelectionRepository>()
|
val roomSelectionRepository = activity.get<RoomSelectionRepository>()
|
||||||
val selectedGroupHolder = activity.get<SelectedGroupHolder>()
|
val selectedGroupHolder = activity.get<SelectedGroupHolder>()
|
||||||
return RoomListViewModel(state, currentSession, selectedGroupHolder, roomSelectionRepository)
|
val visibleRoomHolder = activity.get<VisibleRoomHolder>()
|
||||||
|
return RoomListViewModel(state, currentSession, selectedGroupHolder, visibleRoomHolder, roomSelectionRepository)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
observeRoomSummaries()
|
observeRoomSummaries()
|
||||||
|
observeVisibleRoom()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun accept(action: RoomListActions) {
|
fun accept(action: RoomListActions) {
|
||||||
@ -46,10 +51,17 @@ class RoomListViewModel(initialState: RoomListViewState,
|
|||||||
private fun handleSelectRoom(action: RoomListActions.SelectRoom) = withState { state ->
|
private fun handleSelectRoom(action: RoomListActions.SelectRoom) = withState { state ->
|
||||||
if (state.selectedRoomId != action.roomSummary.roomId) {
|
if (state.selectedRoomId != action.roomSummary.roomId) {
|
||||||
roomSelectionRepository.saveLastSelectedRoom(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() {
|
private fun observeRoomSummaries() {
|
||||||
Observable.combineLatest<List<RoomSummary>, Option<GroupSummary>, RoomSummaries>(
|
Observable.combineLatest<List<RoomSummary>, Option<GroupSummary>, RoomSummaries>(
|
||||||
session.rx().liveRoomSummaries(),
|
session.rx().liveRoomSummaries(),
|
||||||
@ -78,15 +90,8 @@ class RoomListViewModel(initialState: RoomListViewState,
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
.execute { async ->
|
.execute { async ->
|
||||||
val summaries = async()
|
|
||||||
val selectedRoomId = selectedRoomId
|
|
||||||
?: roomSelectionRepository.lastSelectedRoom()
|
|
||||||
?: summaries?.directRooms?.firstOrNull()?.roomId
|
|
||||||
?: summaries?.groupRooms?.firstOrNull()?.roomId
|
|
||||||
|
|
||||||
copy(
|
copy(
|
||||||
asyncRooms = async,
|
asyncRooms = async
|
||||||
selectedRoomId = selectedRoomId
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user