Remove HomeViewModel and dispatch in multiple view models (one for each fragment)

This commit is contained in:
ganfra 2018-12-29 19:57:38 +01:00 committed by ganfra
parent 7f11c141c7
commit 7c0df91a58
22 changed files with 282 additions and 222 deletions

View File

@ -47,7 +47,7 @@ configurations.all { strategy ->
dependencies { dependencies {


def epoxy_version = "2.19.0" def epoxy_version = "2.19.0"

def arrow_version = "0.8.0"


implementation fileTree(dir: 'libs', include: ['*.jar']) implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation project(":matrix-sdk-android") implementation project(":matrix-sdk-android")
@ -58,22 +58,30 @@ dependencies {
implementation 'com.android.support.constraint:constraint-layout:1.1.3' implementation 'com.android.support.constraint:constraint-layout:1.1.3'


implementation 'com.jakewharton.threetenabp:threetenabp:1.1.1' implementation 'com.jakewharton.threetenabp:threetenabp:1.1.1'

implementation 'com.jakewharton.timber:timber:4.7.1' implementation 'com.jakewharton.timber:timber:4.7.1'


// rx
implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'

implementation("com.airbnb.android:epoxy:$epoxy_version") implementation("com.airbnb.android:epoxy:$epoxy_version")
kapt "com.airbnb.android:epoxy-processor:$epoxy_version" kapt "com.airbnb.android:epoxy-processor:$epoxy_version"
implementation "com.airbnb.android:epoxy-paging:$epoxy_version" implementation "com.airbnb.android:epoxy-paging:$epoxy_version"
implementation 'com.airbnb.android:mvrx:0.6.0' implementation 'com.airbnb.android:mvrx:0.6.0'


// FP
implementation "io.arrow-kt:arrow-core:$arrow_version"

// UI
implementation 'com.github.bumptech.glide:glide:4.8.0' implementation 'com.github.bumptech.glide:glide:4.8.0'
kapt 'com.github.bumptech.glide:compiler:4.8.0' kapt 'com.github.bumptech.glide:compiler:4.8.0'
implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1' implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'



// DI
implementation "org.koin:koin-android:$koin_version" implementation "org.koin:koin-android:$koin_version"
implementation "org.koin:koin-android-scope:$koin_version" implementation "org.koin:koin-android-scope:$koin_version"


// TESTS
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'

View File

@ -0,0 +1,7 @@
package im.vector.riotredesign.core.platform

import com.airbnb.mvrx.BaseMvRxViewModel
import com.airbnb.mvrx.MvRxState

abstract class RiotViewModel<S : MvRxState>(initialState: S)
: BaseMvRxViewModel<S>(initialState, debugMode = false)

View File

@ -1,17 +0,0 @@
package im.vector.riotredesign.features.home

import im.vector.matrix.android.api.permalinks.PermalinkData
import im.vector.matrix.android.api.session.group.model.GroupSummary
import im.vector.matrix.android.api.session.room.model.RoomSummary

sealed class HomeActions {

data class SelectRoom(val roomSummary: RoomSummary) : HomeActions()

data class SelectGroup(val groupSummary: GroupSummary) : HomeActions()

data class PermalinkClicked(val permalinkData: PermalinkData) : HomeActions()

object RoomDisplayed : HomeActions()

}

View File

@ -5,12 +5,10 @@ import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.support.v4.app.FragmentManager import android.support.v4.app.FragmentManager
import android.support.v4.view.GravityCompat import android.support.v4.view.GravityCompat
import android.support.v4.widget.DrawerLayout
import android.support.v7.app.ActionBarDrawerToggle 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 android.view.View
import im.vector.riotredesign.R import im.vector.riotredesign.R
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
@ -91,11 +89,8 @@ class HomeActivity : RiotActivity(), HomeNavigator, ToolbarConfigurable {
override fun openRoomDetail(roomId: String, eventId: String?) { override fun openRoomDetail(roomId: String, eventId: String?) {
val args = RoomDetailArgs(roomId, eventId) val args = RoomDetailArgs(roomId, eventId)
val roomDetailFragment = RoomDetailFragment.newInstance(args) val roomDetailFragment = RoomDetailFragment.newInstance(args)
if (drawerLayout.isDrawerOpen(Gravity.LEFT)) { drawerLayout.closeDrawer(Gravity.LEFT)
closeDrawerLayout(Gravity.LEFT) { replaceFragment(roomDetailFragment, R.id.homeDetailFragmentContainer) } replaceFragment(roomDetailFragment, R.id.homeDetailFragmentContainer)
} else {
replaceFragment(roomDetailFragment, R.id.homeDetailFragmentContainer)
}
} }


override fun openGroupDetail(groupId: String) { override fun openGroupDetail(groupId: String) {
@ -106,16 +101,6 @@ class HomeActivity : RiotActivity(), HomeNavigator, ToolbarConfigurable {
Timber.v("Open user detail $userId") Timber.v("Open user detail $userId")
} }


private fun closeDrawerLayout(gravity: Int, actionOnClose: () -> Unit) {
drawerLayout.addDrawerListener(object : DrawerLayout.SimpleDrawerListener() {
override fun onDrawerClosed(p0: View) {
drawerLayout.removeDrawerListener(this)
actionOnClose()
}
})
drawerLayout.closeDrawer(gravity)
}

companion object { companion object {
fun newIntent(context: Context): Intent { fun newIntent(context: Context): Intent {
return Intent(context, HomeActivity::class.java) return Intent(context, HomeActivity::class.java)

View File

@ -1,5 +1,6 @@
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.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
@ -30,5 +31,9 @@ class HomeModule(private val homeActivity: HomeActivity) {
TimelineEventController(roomId, get(), get(), get()) TimelineEventController(roomId, get(), get(), get())
} }


single {
SelectedGroupHolder()
}

} }
} }

View File

@ -1,107 +0,0 @@
package im.vector.riotredesign.features.home

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.permalinks.PermalinkData
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.rx.rx
import im.vector.riotredesign.features.home.room.list.RoomSelectionRepository
import org.koin.android.ext.android.get

class HomeViewModel(initialState: HomeViewState,
private val session: Session,
private val roomSelectionRepository: RoomSelectionRepository) : BaseMvRxViewModel<HomeViewState>(initialState) {

companion object : MvRxViewModelFactory<HomeViewState> {

@JvmStatic
override fun create(activity: FragmentActivity, state: HomeViewState): HomeViewModel {
val currentSession = Matrix.getInstance().currentSession
val roomSelectionRepository = activity.get<RoomSelectionRepository>()
return HomeViewModel(state, currentSession, roomSelectionRepository)
}
}

init {
observeRoomSummaries()
observeGroupSummaries()
}

fun accept(action: HomeActions) {
when (action) {
is HomeActions.SelectRoom -> handleSelectRoom(action)
is HomeActions.SelectGroup -> handleSelectGroup(action)
is HomeActions.RoomDisplayed -> setState { copy(shouldOpenRoomDetail = false) }
is HomeActions.PermalinkClicked -> handlePermalinkClicked(action)
}
}

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

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

}
is PermalinkData.RoomLink -> {

}
is PermalinkData.GroupLink -> {

}
is PermalinkData.UserLink -> {

}
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 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 observeRoomSummaries() {
session
.rx().liveRoomSummaries()
.execute { async ->
val summaries = async()
val directRooms = summaries?.filter { it.isDirect } ?: emptyList()
val groupRooms = summaries?.filter { !it.isDirect } ?: emptyList()

val selectedRoomId = selectedRoomId
?: roomSelectionRepository.lastSelectedRoom()
?: directRooms.firstOrNull()?.roomId
?: groupRooms.firstOrNull()?.roomId

copy(
asyncRooms = async,
directRooms = directRooms,
groupRooms = groupRooms,
selectedRoomId = selectedRoomId
)
}
}

private fun observeGroupSummaries() {
session
.rx().liveGroupSummaries()
.execute { async ->
copy(asyncGroups = async)
}
}
}

View File

@ -1,18 +0,0 @@
package im.vector.riotredesign.features.home

import com.airbnb.mvrx.Async
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.Uninitialized
import im.vector.matrix.android.api.session.group.model.GroupSummary
import im.vector.matrix.android.api.session.room.model.RoomSummary

data class HomeViewState(
val asyncRooms: Async<List<RoomSummary>> = Uninitialized,
val directRooms: List<RoomSummary> = emptyList(),
val groupRooms: List<RoomSummary> = emptyList(),
val selectedRoomId: String? = null,
val selectedEventId: String? = null,
val shouldOpenRoomDetail: Boolean = true,
val asyncGroups: Async<List<GroupSummary>> = Uninitialized,
val selectedGroup: GroupSummary? = null
) : MvRxState

View File

@ -0,0 +1,9 @@
package im.vector.riotredesign.features.home.group

import im.vector.matrix.android.api.session.group.model.GroupSummary

sealed class GroupListActions {

data class SelectGroup(val groupSummary: GroupSummary) : GroupListActions()

}

View File

@ -6,14 +6,11 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import com.airbnb.mvrx.Incomplete import com.airbnb.mvrx.Incomplete
import com.airbnb.mvrx.Success import com.airbnb.mvrx.Success
import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.fragmentViewModel
import im.vector.matrix.android.api.session.group.model.GroupSummary import im.vector.matrix.android.api.session.group.model.GroupSummary
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.platform.RiotFragment import im.vector.riotredesign.core.platform.RiotFragment
import im.vector.riotredesign.core.platform.StateView import im.vector.riotredesign.core.platform.StateView
import im.vector.riotredesign.features.home.HomeActions
import im.vector.riotredesign.features.home.HomeViewModel
import im.vector.riotredesign.features.home.HomeViewState
import kotlinx.android.synthetic.main.fragment_group_list.* import kotlinx.android.synthetic.main.fragment_group_list.*


class GroupListFragment : RiotFragment(), GroupSummaryController.Callback { class GroupListFragment : RiotFragment(), GroupSummaryController.Callback {
@ -24,7 +21,7 @@ class GroupListFragment : RiotFragment(), GroupSummaryController.Callback {
} }
} }


private val viewModel: HomeViewModel by activityViewModel() private val viewModel: GroupListViewModel by fragmentViewModel()


private lateinit var groupController: GroupSummaryController private lateinit var groupController: GroupSummaryController


@ -40,14 +37,14 @@ class GroupListFragment : RiotFragment(), GroupSummaryController.Callback {
viewModel.subscribe { renderState(it) } viewModel.subscribe { renderState(it) }
} }


private fun renderState(state: HomeViewState) { private fun renderState(state: GroupListViewState) {
when (state.asyncGroups) { when (state.asyncGroups) {
is Incomplete -> renderLoading() is Incomplete -> renderLoading()
is Success -> renderSuccess(state) is Success -> renderSuccess(state)
} }
} }


private fun renderSuccess(state: HomeViewState) { private fun renderSuccess(state: GroupListViewState) {
stateView.state = StateView.State.Content stateView.state = StateView.State.Content
groupController.setData(state) groupController.setData(state)
} }
@ -57,7 +54,7 @@ class GroupListFragment : RiotFragment(), GroupSummaryController.Callback {
} }


override fun onGroupSelected(groupSummary: GroupSummary) { override fun onGroupSelected(groupSummary: GroupSummary) {
viewModel.accept(HomeActions.SelectGroup(groupSummary)) viewModel.accept(GroupListActions.SelectGroup(groupSummary))
} }


} }

View File

@ -0,0 +1,64 @@
package im.vector.riotredesign.features.home.group

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.session.Session
import im.vector.matrix.rx.rx
import im.vector.riotredesign.core.platform.RiotViewModel
import org.koin.android.ext.android.get

class GroupListViewModel(initialState: GroupListViewState,
private val selectedGroupHolder: SelectedGroupHolder,
private val session: Session
) : RiotViewModel<GroupListViewState>(initialState) {

companion object : MvRxViewModelFactory<GroupListViewState> {

@JvmStatic
override fun create(activity: FragmentActivity, state: GroupListViewState): GroupListViewModel {
val currentSession = Matrix.getInstance().currentSession
val selectedGroupHolder = activity.get<SelectedGroupHolder>()
return GroupListViewModel(state, selectedGroupHolder, currentSession)
}
}

init {
observeGroupSummaries()
observeState()
}

private fun observeState() {
subscribe {
selectedGroupHolder.setSelectedGroup(it.selectedGroup)
}
}

fun accept(action: GroupListActions) {
when (action) {
is GroupListActions.SelectGroup -> handleSelectGroup(action)
}
}

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

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


private fun observeGroupSummaries() {
session
.rx().liveGroupSummaries()
.execute { async ->
copy(asyncGroups = async)
}
}


}

View File

@ -0,0 +1,11 @@
package im.vector.riotredesign.features.home.group

import com.airbnb.mvrx.Async
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.Uninitialized
import im.vector.matrix.android.api.session.group.model.GroupSummary

data class GroupListViewState(
val asyncGroups: Async<List<GroupSummary>> = Uninitialized,
val selectedGroup: GroupSummary? = null
) : MvRxState

View File

@ -2,12 +2,11 @@ package im.vector.riotredesign.features.home.group


import com.airbnb.epoxy.TypedEpoxyController import com.airbnb.epoxy.TypedEpoxyController
import im.vector.matrix.android.api.session.group.model.GroupSummary import im.vector.matrix.android.api.session.group.model.GroupSummary
import im.vector.riotredesign.features.home.HomeViewState


class GroupSummaryController(private val callback: Callback? = null class GroupSummaryController(private val callback: Callback? = null
) : TypedEpoxyController<HomeViewState>() { ) : TypedEpoxyController<GroupListViewState>() {


override fun buildModels(viewState: HomeViewState) { override fun buildModels(viewState: GroupListViewState) {
buildGroupModels(viewState.asyncGroups(), viewState.selectedGroup) buildGroupModels(viewState.asyncGroups(), viewState.selectedGroup)
} }



View File

@ -0,0 +1,22 @@
package im.vector.riotredesign.features.home.group

import arrow.core.Option
import im.vector.matrix.android.api.session.group.model.GroupSummary
import io.reactivex.Observable
import io.reactivex.subjects.BehaviorSubject

class SelectedGroupHolder {

private val selectedGroupStream = BehaviorSubject.createDefault<Option<GroupSummary>>(Option.empty())

fun setSelectedGroup(group: GroupSummary?) {
val optionValue = Option.fromNullable(group)
selectedGroupStream.onNext(optionValue)
}

fun selectedGroup(): Observable<Option<GroupSummary>> {
return selectedGroupStream.hide()
}


}

View File

@ -6,8 +6,6 @@ import android.support.v7.widget.LinearLayoutManager
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.args import com.airbnb.mvrx.args
import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.fragmentViewModel
import im.vector.matrix.android.api.permalinks.PermalinkParser import im.vector.matrix.android.api.permalinks.PermalinkParser
@ -16,8 +14,6 @@ import im.vector.riotredesign.R
import im.vector.riotredesign.core.platform.RiotFragment import im.vector.riotredesign.core.platform.RiotFragment
import im.vector.riotredesign.core.platform.ToolbarConfigurable import im.vector.riotredesign.core.platform.ToolbarConfigurable
import im.vector.riotredesign.features.home.AvatarRenderer 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 im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.fragment_room_detail.* import kotlinx.android.synthetic.main.fragment_room_detail.*
@ -41,7 +37,6 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback {
} }
} }


private val homeViewModel: HomeViewModel by activityViewModel()
private val roomDetailViewModel: RoomDetailViewModel by fragmentViewModel() private val roomDetailViewModel: RoomDetailViewModel by fragmentViewModel()
private val roomDetailArgs: RoomDetailArgs by args() private val roomDetailArgs: RoomDetailArgs by args()


@ -83,12 +78,8 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback {
} }


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


private fun renderRoomSummary(roomSummary: RoomSummary?) { private fun renderRoomSummary(roomSummary: RoomSummary?) {
@ -113,7 +104,7 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback {


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


} }

View File

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


import android.support.v4.app.FragmentActivity import android.support.v4.app.FragmentActivity
import com.airbnb.mvrx.BaseMvRxViewModel
import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.MvRxViewModelFactory
import im.vector.matrix.android.api.Matrix import im.vector.matrix.android.api.Matrix
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.Session 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


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


private val room = session.getRoom(initialState.roomId)!! private val room = session.getRoom(initialState.roomId)!!
private val roomId = initialState.roomId private val roomId = initialState.roomId
@ -53,10 +53,9 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,


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



} }

View File

@ -0,0 +1,11 @@
package im.vector.riotredesign.features.home.room.list

import im.vector.matrix.android.api.session.room.model.RoomSummary

sealed class RoomListActions {

data class SelectRoom(val roomSummary: RoomSummary) : RoomListActions()

object RoomDisplayed : RoomListActions()

}

View File

@ -13,10 +13,7 @@ import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.platform.RiotFragment import im.vector.riotredesign.core.platform.RiotFragment
import im.vector.riotredesign.core.platform.StateView import im.vector.riotredesign.core.platform.StateView
import im.vector.riotredesign.features.home.HomeActions
import im.vector.riotredesign.features.home.HomeNavigator import im.vector.riotredesign.features.home.HomeNavigator
import im.vector.riotredesign.features.home.HomeViewModel
import im.vector.riotredesign.features.home.HomeViewState
import kotlinx.android.synthetic.main.fragment_room_list.* import kotlinx.android.synthetic.main.fragment_room_list.*
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject


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


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


override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
@ -44,19 +41,15 @@ class RoomListFragment : RiotFragment(), RoomSummaryController.Callback {
homeViewModel.subscribe { renderState(it) } homeViewModel.subscribe { renderState(it) }
} }


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


private fun renderSuccess(state: HomeViewState) { private fun renderSuccess(state: RoomListViewState) {
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 {
@ -78,7 +71,8 @@ class RoomListFragment : RiotFragment(), RoomSummaryController.Callback {
} }


override fun onRoomSelected(room: RoomSummary) { override fun onRoomSelected(room: RoomSummary) {
homeViewModel.accept(HomeActions.SelectRoom(room)) homeViewModel.accept(RoomListActions.SelectRoom(room))
homeNavigator.openRoomDetail(room.roomId, null)
} }


} }

View File

@ -0,0 +1,93 @@
package im.vector.riotredesign.features.home.room.list

import android.support.v4.app.FragmentActivity
import arrow.core.Option
import com.airbnb.mvrx.MvRxViewModelFactory
import im.vector.matrix.android.api.Matrix
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.group.model.GroupSummary
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 io.reactivex.Observable
import io.reactivex.functions.BiFunction
import org.koin.android.ext.android.get

class RoomListViewModel(initialState: RoomListViewState,
private val session: Session,
private val selectedGroupHolder: SelectedGroupHolder,
private val roomSelectionRepository: RoomSelectionRepository)
: RiotViewModel<RoomListViewState>(initialState) {

companion object : MvRxViewModelFactory<RoomListViewState> {

@JvmStatic
override fun create(activity: FragmentActivity, state: RoomListViewState): RoomListViewModel {
val currentSession = Matrix.getInstance().currentSession
val roomSelectionRepository = activity.get<RoomSelectionRepository>()
val selectedGroupHolder = activity.get<SelectedGroupHolder>()
return RoomListViewModel(state, currentSession, selectedGroupHolder, roomSelectionRepository)
}
}

init {
observeRoomSummaries()
}

fun accept(action: RoomListActions) {
when (action) {
is RoomListActions.SelectRoom -> handleSelectRoom(action)
}
}

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

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 observeRoomSummaries() {
Observable.combineLatest<List<RoomSummary>, Option<GroupSummary>, RoomSummaries>(
session.rx().liveRoomSummaries(),
selectedGroupHolder.selectedGroup(),
BiFunction { rooms, selectedGroupOption ->
val selectedGroup = selectedGroupOption.orNull()

val filteredDirectRooms = rooms
.filter { it.isDirect }
.filter {
if (selectedGroup == null) {
true
} else {
it.otherMemberIds
.intersect(selectedGroup.userIds)
.isNotEmpty()
}
}

val filteredGroupRooms = rooms
.filter { !it.isDirect }
.filter {
selectedGroup?.roomIds?.contains(it.roomId) ?: true
}
RoomSummaries(filteredDirectRooms, filteredGroupRooms)
}
)
.execute { async ->
val summaries = async()
val selectedRoomId = selectedRoomId
?: roomSelectionRepository.lastSelectedRoom()
?: summaries?.directRooms?.firstOrNull()?.roomId
?: summaries?.groupRooms?.firstOrNull()?.roomId

copy(
asyncRooms = async,
selectedRoomId = selectedRoomId
)
}
}
}

View File

@ -0,0 +1,20 @@
package im.vector.riotredesign.features.home.room.list

import com.airbnb.mvrx.Async
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.Uninitialized
import im.vector.matrix.android.api.session.room.model.RoomSummary

data class RoomListViewState(
val asyncRooms: Async<RoomSummaries> = Uninitialized,
val selectedRoomId: String? = null
) : MvRxState

data class RoomSummaries(
val directRooms: List<RoomSummary>,
val groupRooms: List<RoomSummary>
)

fun RoomSummaries?.isNullOrEmpty(): Boolean {
return this == null || (directRooms.isEmpty() && groupRooms.isEmpty())
}

View File

@ -2,17 +2,15 @@ package im.vector.riotredesign.features.home.room.list


import com.airbnb.epoxy.TypedEpoxyController import com.airbnb.epoxy.TypedEpoxyController
import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.riotredesign.features.home.HomeViewState


class RoomSummaryController(private val callback: Callback? = null class RoomSummaryController(private val callback: Callback? = null
) : TypedEpoxyController<HomeViewState>() { ) : TypedEpoxyController<RoomListViewState>() {



private var isDirectRoomsExpanded = true private var isDirectRoomsExpanded = true
private var isGroupRoomsExpanded = true private var isGroupRoomsExpanded = true


override fun buildModels(viewState: HomeViewState) { override fun buildModels(viewState: RoomListViewState) {

val roomSummaries = viewState.asyncRooms()
RoomCategoryItem( RoomCategoryItem(
title = "DIRECT MESSAGES", title = "DIRECT MESSAGES",
isExpanded = isDirectRoomsExpanded, isExpanded = isDirectRoomsExpanded,
@ -25,16 +23,7 @@ class RoomSummaryController(private val callback: Callback? = null
.addTo(this) .addTo(this)


if (isDirectRoomsExpanded) { if (isDirectRoomsExpanded) {
val filteredDirectRooms = viewState.directRooms.filter { buildRoomModels(roomSummaries?.directRooms ?: emptyList(), viewState.selectedRoomId)
if (viewState.selectedGroup == null) {
true
} else {
it.otherMemberIds
.intersect(viewState.selectedGroup.userIds)
.isNotEmpty()
}
}
buildRoomModels(filteredDirectRooms, viewState.selectedRoomId)
} }


RoomCategoryItem( RoomCategoryItem(
@ -49,10 +38,7 @@ class RoomSummaryController(private val callback: Callback? = null
.addTo(this) .addTo(this)


if (isGroupRoomsExpanded) { if (isGroupRoomsExpanded) {
val filteredGroupRooms = viewState.groupRooms.filter { buildRoomModels(roomSummaries?.groupRooms ?: emptyList(), viewState.selectedRoomId)
viewState.selectedGroup?.roomIds?.contains(it.roomId) ?: true
}
buildRoomModels(filteredGroupRooms, viewState.selectedRoomId)
} }


} }

View File

@ -40,7 +40,7 @@ dependencies {
implementation 'io.reactivex.rxjava2:rxandroid:2.0.2' implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'


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




testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12'

View File

@ -2,10 +2,10 @@ package im.vector.matrix.rx


import android.arch.paging.PagedList import android.arch.paging.PagedList
import im.vector.matrix.android.api.session.events.model.EnrichedEvent 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.Room
import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.RoomSummary
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.schedulers.Schedulers


class RxRoom(private val room: Room) { class RxRoom(private val room: Room) {


@ -15,6 +15,7 @@ class RxRoom(private val room: Room) {


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


} }