From 7f11c141c720c5875a5d8ba7451356d5e17d97ae Mon Sep 17 00:00:00 2001 From: ganfra Date: Sat, 29 Dec 2018 17:54:03 +0100 Subject: [PATCH] Use MvRx in room detail --- app/build.gradle | 5 +- .../core/platform/RiotFragment.kt | 6 ++ .../features/home/HomeActivity.kt | 4 +- .../features/home/HomeViewModel.kt | 46 ++++++-------- .../home/room/detail/RoomDetailActions.kt | 7 +++ .../home/room/detail/RoomDetailFragment.kt | 60 +++++++++--------- .../home/room/detail/RoomDetailViewModel.kt | 62 +++++++++++++++++++ .../home/room/detail/RoomDetailViewState.kt | 21 +++++++ .../timeline/TimelineEventController.kt | 3 +- .../home/room/list/RoomListFragment.kt | 14 ++--- matrix-sdk-android-rx/build.gradle | 3 + .../main/java/im/vector/matrix/rx/RxRoom.kt | 24 +++++++ 12 files changed, 191 insertions(+), 64 deletions(-) create mode 100644 app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailActions.kt create mode 100644 app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt create mode 100644 app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewState.kt create mode 100644 matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt diff --git a/app/build.gradle b/app/build.gradle index 98e9b05aca..cae6db8673 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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' diff --git a/app/src/main/java/im/vector/riotredesign/core/platform/RiotFragment.kt b/app/src/main/java/im/vector/riotredesign/core/platform/RiotFragment.kt index 79d39066ac..178996982a 100644 --- a/app/src/main/java/im/vector/riotredesign/core/platform/RiotFragment.kt +++ b/app/src/main/java/im/vector/riotredesign/core/platform/RiotFragment.kt @@ -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) } } + } } \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt b/app/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt index b930574249..262f040c66 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt @@ -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 { diff --git a/app/src/main/java/im/vector/riotredesign/features/home/HomeViewModel.kt b/app/src/main/java/im/vector/riotredesign/features/home/HomeViewModel.kt index e6487c14cb..9fe633a628 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/HomeViewModel.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/HomeViewModel.kt @@ -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) } } } diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailActions.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailActions.kt new file mode 100644 index 0000000000..032caa8a80 --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailActions.kt @@ -0,0 +1,7 @@ +package im.vector.riotredesign.features.home.room.detail + +sealed class RoomDetailActions { + + data class SendMessage(val text: String) : RoomDetailActions() + +} \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt index a9acc87747..4ed0272ed5 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt @@ -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 { 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 { 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 { - - }) + 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?) { + 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)) } } diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt new file mode 100644 index 0000000000..89c70a0cf0 --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt @@ -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(initialState) { + + private val room = session.getRoom(initialState.roomId)!! + private val roomId = initialState.roomId + private val eventId = initialState.eventId + + companion object : MvRxViewModelFactory { + + @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 {}) + } + + private fun observeRoomSummary() { + room.rx().liveRoomSummary() + .execute { async -> + copy(asyncRoomSummary = async) + } + } + + private fun observeTimeline() { + room.rx().timeline(eventId) + .execute { async -> + copy(asyncTimeline = async) + } + } + + +} \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewState.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewState.kt new file mode 100644 index 0000000000..83f048513c --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewState.kt @@ -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 + +data class RoomDetailViewState( + val roomId: String, + val eventId: String?, + val asyncRoomSummary: Async = Uninitialized, + val asyncTimeline: Async = Uninitialized +) : MvRxState { + + constructor(args: RoomDetailArgs) : this(roomId = args.roomId, eventId = args.eventId) + +} \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt index 73888167a1..ecda3c0255 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt @@ -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? = emptyList() - var timeline: PagedList? = null + var timeline: Timeline? = null set(value) { field?.removeWeakCallback(pagedListCallback) field = value diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt index 4830ba8d4f..eeab837493 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt @@ -29,7 +29,7 @@ class RoomListFragment : RiotFragment(), RoomSummaryController.Callback { } private val homeNavigator by inject() - 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)) } } \ No newline at end of file diff --git a/matrix-sdk-android-rx/build.gradle b/matrix-sdk-android-rx/build.gradle index 8f51158f89..c05a36e837 100644 --- a/matrix-sdk-android-rx/build.gradle +++ b/matrix-sdk-android-rx/build.gradle @@ -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' diff --git a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt new file mode 100644 index 0000000000..e588d078e4 --- /dev/null +++ b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt @@ -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 { + return room.roomSummary.asObservable() + } + + fun timeline(eventId: String? = null): Observable> { + return room.timeline(eventId).asObservable() + } + +} + +fun Room.rx(): RxRoom { + return RxRoom(this) +} \ No newline at end of file