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,12 +89,9 @@ 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) }
} else {
replaceFragment(roomDetailFragment, R.id.homeDetailFragmentContainer) replaceFragment(roomDetailFragment, R.id.homeDetailFragmentContainer)
} }
}
override fun openGroupDetail(groupId: String) { override fun openGroupDetail(groupId: String) {
Timber.v("Open group detail $groupId") Timber.v("Open group detail $groupId")
@ -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())
} }
} }