mirror of
https://github.com/element-hq/element-android
synced 2024-11-27 11:59:12 +03:00
Use MvRx in room detail
This commit is contained in:
parent
5a75e3db81
commit
7f11c141c7
12 changed files with 191 additions and 64 deletions
|
@ -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'
|
||||
|
||||
|
||||
|
|
|
@ -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) } }
|
||||
}
|
||||
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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) }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
package im.vector.riotredesign.features.home.room.detail
|
||||
|
||||
sealed class RoomDetailActions {
|
||||
|
||||
data class SendMessage(val text: String) : RoomDetailActions()
|
||||
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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)
|
||||
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
||||
}
|
|
@ -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'
|
||||
|
|
|
@ -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)
|
||||
}
|
Loading…
Reference in a new issue