Use MvRx in room detail

This commit is contained in:
ganfra 2018-12-29 17:54:03 +01:00 committed by ganfra
parent 5a75e3db81
commit 7f11c141c7
12 changed files with 191 additions and 64 deletions

View File

@ -7,6 +7,10 @@ kapt {
correctErrorTypes = true
}

androidExtensions {
experimental = true
}

android {
compileSdkVersion 28
defaultConfig {
@ -64,7 +68,6 @@ dependencies {

implementation 'com.github.bumptech.glide:glide:4.8.0'
kapt 'com.github.bumptech.glide:compiler:4.8.0'
//todo remove that
implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'



View File

@ -1,6 +1,9 @@
package im.vector.riotredesign.core.platform

import android.os.Bundle
import android.os.Parcelable
import com.airbnb.mvrx.BaseMvRxFragment
import com.airbnb.mvrx.MvRx

abstract class RiotFragment : BaseMvRxFragment(), OnBackPressed {

@ -16,5 +19,8 @@ abstract class RiotFragment : BaseMvRxFragment(), OnBackPressed {
//no-ops by default
}

protected fun setArguments(args: Parcelable? = null) {
arguments = args?.let { Bundle().apply { putParcelable(MvRx.KEY_ARG, it) } }
}

}

View File

@ -17,6 +17,7 @@ import im.vector.riotredesign.core.platform.OnBackPressed
import im.vector.riotredesign.core.platform.RiotActivity
import im.vector.riotredesign.core.platform.ToolbarConfigurable
import im.vector.riotredesign.features.home.room.detail.LoadingRoomDetailFragment
import im.vector.riotredesign.features.home.room.detail.RoomDetailArgs
import im.vector.riotredesign.features.home.room.detail.RoomDetailFragment
import kotlinx.android.synthetic.main.activity_home.*
import org.koin.standalone.StandAloneContext.loadKoinModules
@ -88,7 +89,8 @@ class HomeActivity : RiotActivity(), HomeNavigator, ToolbarConfigurable {
// HomeNavigator *******************************************************************************

override fun openRoomDetail(roomId: String, eventId: String?) {
val roomDetailFragment = RoomDetailFragment.newInstance(roomId)
val args = RoomDetailArgs(roomId, eventId)
val roomDetailFragment = RoomDetailFragment.newInstance(args)
if (drawerLayout.isDrawerOpen(Gravity.LEFT)) {
closeDrawerLayout(Gravity.LEFT) { replaceFragment(roomDetailFragment, R.id.homeDetailFragmentContainer) }
} else {

View File

@ -40,44 +40,38 @@ class HomeViewModel(initialState: HomeViewState,

// PRIVATE METHODS *****************************************************************************

private fun handlePermalinkClicked(action: HomeActions.PermalinkClicked) {
withState { state ->
when (action.permalinkData) {
is PermalinkData.EventLink -> {
private fun handlePermalinkClicked(action: HomeActions.PermalinkClicked) = withState { state ->
when (action.permalinkData) {
is PermalinkData.EventLink -> {

}
is PermalinkData.RoomLink -> {
}
is PermalinkData.RoomLink -> {

}
is PermalinkData.GroupLink -> {
}
is PermalinkData.GroupLink -> {

}
is PermalinkData.UserLink -> {
}
is PermalinkData.UserLink -> {

}
is PermalinkData.FallbackLink -> {
}
is PermalinkData.FallbackLink -> {

}
}
}
}

private fun handleSelectRoom(action: HomeActions.SelectRoom) {
withState { state ->
if (state.selectedRoomId != action.roomSummary.roomId) {
roomSelectionRepository.saveLastSelectedRoom(action.roomSummary.roomId)
setState { copy(selectedRoomId = action.roomSummary.roomId, shouldOpenRoomDetail = true) }
}
private fun handleSelectRoom(action: HomeActions.SelectRoom) = withState { state ->
if (state.selectedRoomId != action.roomSummary.roomId) {
roomSelectionRepository.saveLastSelectedRoom(action.roomSummary.roomId)
setState { copy(selectedRoomId = action.roomSummary.roomId, shouldOpenRoomDetail = true) }
}
}

private fun handleSelectGroup(action: HomeActions.SelectGroup) {
withState { state ->
if (state.selectedGroup?.groupId != action.groupSummary.groupId) {
setState { copy(selectedGroup = action.groupSummary) }
} else {
setState { copy(selectedGroup = null) }
}
private fun handleSelectGroup(action: HomeActions.SelectGroup) = withState { state ->
if (state.selectedGroup?.groupId != action.groupSummary.groupId) {
setState { copy(selectedGroup = action.groupSummary) }
} else {
setState { copy(selectedGroup = null) }
}
}


View File

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

sealed class RoomDetailActions {

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

}

View File

@ -1,51 +1,51 @@
package im.vector.riotredesign.features.home.room.detail

import android.arch.lifecycle.Observer
import android.arch.paging.PagedList
import android.os.Bundle
import android.os.Parcelable
import android.support.v7.widget.LinearLayoutManager
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.activityViewModel
import im.vector.matrix.android.api.Matrix
import im.vector.matrix.android.api.MatrixCallback
import com.airbnb.mvrx.args
import com.airbnb.mvrx.fragmentViewModel
import im.vector.matrix.android.api.permalinks.PermalinkParser
import im.vector.matrix.android.api.session.events.model.EnrichedEvent
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.Room
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.riotredesign.R
import im.vector.riotredesign.core.platform.RiotFragment
import im.vector.riotredesign.core.platform.ToolbarConfigurable
import im.vector.riotredesign.core.utils.FragmentArgumentDelegate
import im.vector.riotredesign.core.utils.UnsafeFragmentArgumentDelegate
import im.vector.riotredesign.features.home.AvatarRenderer
import im.vector.riotredesign.features.home.HomeActions
import im.vector.riotredesign.features.home.HomeViewModel
import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController
import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.fragment_room_detail.*
import org.koin.android.ext.android.inject
import org.koin.core.parameter.parametersOf

@Parcelize
data class RoomDetailArgs(
val roomId: String,
val eventId: String? = null
) : Parcelable

class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback {

companion object {

fun newInstance(roomId: String, eventId: String? = null): RoomDetailFragment {
fun newInstance(args: RoomDetailArgs): RoomDetailFragment {
return RoomDetailFragment().apply {
this.roomId = roomId
this.eventId = eventId
setArguments(args)
}
}
}

private val viewModel: HomeViewModel by activityViewModel()
private val currentSession = Matrix.getInstance().currentSession
private var roomId: String by UnsafeFragmentArgumentDelegate()
private var eventId: String? by FragmentArgumentDelegate()
private val timelineEventController by inject<TimelineEventController> { parametersOf(roomId) }
private lateinit var room: Room
private val homeViewModel: HomeViewModel by activityViewModel()
private val roomDetailViewModel: RoomDetailViewModel by fragmentViewModel()
private val roomDetailArgs: RoomDetailArgs by args()

private val timelineEventController by inject<TimelineEventController> { parametersOf(roomDetailArgs.roomId) }
private lateinit var scrollOnNewMessageCallback: ScrollOnNewMessageCallback

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
@ -54,21 +54,16 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback {

override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
room = currentSession.getRoom(roomId)!!
setupRecyclerView()
setupToolbar()
room.loadRoomMembersIfNeeded()
room.timeline(eventId).observe(this, Observer { renderEvents(it) })
room.roomSummary.observe(this, Observer { renderRoomSummary(it) })
sendButton.setOnClickListener {
val textMessage = composerEditText.text.toString()
if (textMessage.isNotBlank()) {
composerEditText.text = null
room.sendTextMessage(textMessage, object : MatrixCallback<Event> {

})
roomDetailViewModel.accept(RoomDetailActions.SendMessage(textMessage))
}
}
roomDetailViewModel.subscribe { renderState(it) }
}

private fun setupToolbar() {
@ -87,6 +82,15 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback {
timelineEventController.callback = this
}

private fun renderState(state: RoomDetailViewState) {
when (state.asyncTimeline) {
is Success -> renderTimeline(state.asyncTimeline())
}
when (state.asyncRoomSummary) {
is Success -> renderRoomSummary(state.asyncRoomSummary())
}
}

private fun renderRoomSummary(roomSummary: RoomSummary?) {
roomSummary?.let {
toolbarTitleView.text = it.displayName
@ -100,16 +104,16 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback {
}
}

private fun renderEvents(events: PagedList<EnrichedEvent>?) {
private fun renderTimeline(timeline: Timeline?) {
scrollOnNewMessageCallback.hasBeenUpdated.set(true)
timelineEventController.timeline = events
timelineEventController.timeline = timeline
}

// TimelineEventController.Callback ************************************************************

override fun onUrlClicked(url: String) {
val permalinkData = PermalinkParser.parse(url)
viewModel.accept(HomeActions.PermalinkClicked(permalinkData))
homeViewModel.accept(HomeActions.PermalinkClicked(permalinkData))
}

}

View File

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

import android.support.v4.app.FragmentActivity
import com.airbnb.mvrx.BaseMvRxViewModel
import com.airbnb.mvrx.MvRxViewModelFactory
import im.vector.matrix.android.api.Matrix
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.rx.rx

class RoomDetailViewModel(initialState: RoomDetailViewState,
session: Session
) : BaseMvRxViewModel<RoomDetailViewState>(initialState) {

private val room = session.getRoom(initialState.roomId)!!
private val roomId = initialState.roomId
private val eventId = initialState.eventId

companion object : MvRxViewModelFactory<RoomDetailViewState> {

@JvmStatic
override fun create(activity: FragmentActivity, state: RoomDetailViewState): RoomDetailViewModel {
val currentSession = Matrix.getInstance().currentSession
return RoomDetailViewModel(state, currentSession)
}
}

init {
observeRoomSummary()
observeTimeline()
room.loadRoomMembersIfNeeded()
}

fun accept(action: RoomDetailActions) {
when (action) {
is RoomDetailActions.SendMessage -> handleSendMessage(action)
}
}

// PRIVATE METHODS *****************************************************************************

private fun handleSendMessage(action: RoomDetailActions.SendMessage) {
room.sendTextMessage(action.text, callback = object : MatrixCallback<Event> {})
}

private fun observeRoomSummary() {
room.rx().liveRoomSummary()
.execute { async ->
copy(asyncRoomSummary = async)
}
}

private fun observeTimeline() {
room.rx().timeline(eventId)
.execute { async ->
copy(asyncTimeline = async)
}
}


}

View File

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

import android.arch.paging.PagedList
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.Uninitialized
import im.vector.matrix.android.api.session.events.model.EnrichedEvent
import im.vector.matrix.android.api.session.room.model.RoomSummary

typealias Timeline = PagedList<EnrichedEvent>

data class RoomDetailViewState(
val roomId: String,
val eventId: String?,
val asyncRoomSummary: Async<RoomSummary> = Uninitialized,
val asyncTimeline: Async<Timeline> = Uninitialized
) : MvRxState {

constructor(args: RoomDetailArgs) : this(roomId = args.roomId, eventId = args.eventId)

}

View File

@ -7,6 +7,7 @@ import im.vector.matrix.android.api.session.events.model.EnrichedEvent
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.riotredesign.core.extensions.localDateTime
import im.vector.riotredesign.features.home.LoadingItemModel_
import im.vector.riotredesign.features.home.room.detail.Timeline

class TimelineEventController(private val roomId: String,
private val messageItemFactory: MessageItemFactory,
@ -36,7 +37,7 @@ class TimelineEventController(private val roomId: String,
}

private var snapshotList: List<EnrichedEvent>? = emptyList()
var timeline: PagedList<EnrichedEvent>? = null
var timeline: Timeline? = null
set(value) {
field?.removeWeakCallback(pagedListCallback)
field = value

View File

@ -29,7 +29,7 @@ class RoomListFragment : RiotFragment(), RoomSummaryController.Callback {
}

private val homeNavigator by inject<HomeNavigator>()
private val viewModel: HomeViewModel by activityViewModel()
private val homeViewModel: HomeViewModel by activityViewModel()
private lateinit var roomController: RoomSummaryController

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
@ -41,18 +41,18 @@ class RoomListFragment : RiotFragment(), RoomSummaryController.Callback {
roomController = RoomSummaryController(this)
stateView.contentView = epoxyRecyclerView
epoxyRecyclerView.setController(roomController)
viewModel.subscribe { renderState(it) }
homeViewModel.subscribe { renderState(it) }
}

private fun renderState(state: HomeViewState) {
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)
}
if (state.shouldOpenRoomDetail && state.selectedRoomId != null) {
homeNavigator.openRoomDetail(state.selectedRoomId, null)
viewModel.accept(HomeActions.RoomDisplayed)
homeViewModel.accept(HomeActions.RoomDisplayed)
}
}

@ -72,13 +72,13 @@ 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)
}

override fun onRoomSelected(room: RoomSummary) {
viewModel.accept(HomeActions.SelectRoom(room))
homeViewModel.accept(HomeActions.SelectRoom(room))
}

}

View File

@ -39,6 +39,9 @@ dependencies {
implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'

// Paging
api "android.arch.paging:runtime:1.0.1"


testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'

View File

@ -0,0 +1,24 @@
package im.vector.matrix.rx

import android.arch.paging.PagedList
import im.vector.matrix.android.api.session.events.model.EnrichedEvent
import im.vector.matrix.android.api.session.group.model.GroupSummary
import im.vector.matrix.android.api.session.room.Room
import im.vector.matrix.android.api.session.room.model.RoomSummary
import io.reactivex.Observable

class RxRoom(private val room: Room) {

fun liveRoomSummary(): Observable<RoomSummary> {
return room.roomSummary.asObservable()
}

fun timeline(eventId: String? = null): Observable<PagedList<EnrichedEvent>> {
return room.timeline(eventId).asObservable()
}

}

fun Room.rx(): RxRoom {
return RxRoom(this)
}