Direct room: finally use PagedList as we can get a lot of users in DB.

This commit is contained in:
ganfra 2019-07-30 14:51:14 +02:00
parent ff6ce8a4b7
commit 6deba31111
16 changed files with 245 additions and 72 deletions

View File

@ -38,6 +38,8 @@ dependencies {
implementation 'androidx.appcompat:appcompat:1.1.0-beta01' implementation 'androidx.appcompat:appcompat:1.1.0-beta01'
implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0' implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
// Paging
implementation "androidx.paging:paging-runtime-ktx:2.1.0"


testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test:runner:1.2.0'

View File

@ -16,6 +16,7 @@


package im.vector.matrix.rx package im.vector.matrix.rx


import androidx.paging.PagedList
import im.vector.matrix.android.api.session.Session 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.group.model.GroupSummary
import im.vector.matrix.android.api.session.pushers.Pusher import im.vector.matrix.android.api.session.pushers.Pusher
@ -48,6 +49,10 @@ class RxSession(private val session: Session) {
return session.liveUsers().asObservable() return session.liveUsers().asObservable()
} }


fun livePagedUsers(filter: String? = null): Observable<PagedList<User>> {
return session.livePagedUsers(filter).asObservable()
}

fun createRoom(roomParams: CreateRoomParams): Single<String> = Single.create { fun createRoom(roomParams: CreateRoomParams): Single<String> = Single.create {
session.createRoom(roomParams, MatrixCallbackSingle(it)).toSingle(it) session.createRoom(roomParams, MatrixCallbackSingle(it)).toSingle(it)
} }

View File

@ -17,6 +17,7 @@
package im.vector.matrix.android.api.session.user package im.vector.matrix.android.api.session.user


import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.paging.PagedList
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.user.model.User import im.vector.matrix.android.api.session.user.model.User
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
@ -56,4 +57,11 @@ interface UserService {
*/ */
fun liveUsers(): LiveData<List<User>> fun liveUsers(): LiveData<List<User>>


/**
* Observe a live [PagedList] of users sorted alphabetically. You can filter the users.
* @param filter the filter. It will look into userId and displayName.
* @return a Livedata of users
*/
fun livePagedUsers(filter: String? = null): LiveData<PagedList<User>>

} }

View File

@ -19,14 +19,17 @@ package im.vector.matrix.android.internal.database
import android.os.Handler import android.os.Handler
import android.os.HandlerThread import android.os.HandlerThread
import io.realm.* import io.realm.*
import timber.log.Timber
import java.util.concurrent.CountDownLatch import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit


private const val THREAD_NAME = "REALM_QUERY_LATCH" private const val THREAD_NAME = "REALM_QUERY_LATCH"


class RealmQueryLatch<E : RealmObject>(private val realmConfiguration: RealmConfiguration, class RealmQueryLatch<E : RealmObject>(private val realmConfiguration: RealmConfiguration,
private val realmQueryBuilder: (Realm) -> RealmQuery<E>) { private val realmQueryBuilder: (Realm) -> RealmQuery<E>) {


fun await() { @Throws(InterruptedException::class)
fun await(timeout: Long = Long.MAX_VALUE, timeUnit: TimeUnit = TimeUnit.MILLISECONDS) {
val latch = CountDownLatch(1) val latch = CountDownLatch(1)
val handlerThread = HandlerThread(THREAD_NAME + hashCode()) val handlerThread = HandlerThread(THREAD_NAME + hashCode())
handlerThread.start() handlerThread.start()
@ -46,9 +49,14 @@ class RealmQueryLatch<E : RealmObject>(private val realmConfiguration: RealmConf
}) })
} }
handler.post(runnable) handler.post(runnable)
latch.await() try {
latch.await(timeout, timeUnit)
} catch (exception: InterruptedException) {
throw exception
} finally {
handlerThread.quit() handlerThread.quit()
} }
}




} }

View File

@ -34,6 +34,7 @@ import im.vector.matrix.android.internal.session.user.accountdata.UpdateUserAcco
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.util.tryTransactionSync import im.vector.matrix.android.internal.util.tryTransactionSync
import io.realm.RealmConfiguration import io.realm.RealmConfiguration
import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject


internal interface CreateRoomTask : Task<CreateRoomParams, String> internal interface CreateRoomTask : Task<CreateRoomParams, String>
@ -56,8 +57,10 @@ internal class DefaultCreateRoomTask @Inject constructor(private val roomAPI: Ro
realm.where(RoomEntity::class.java) realm.where(RoomEntity::class.java)
.equalTo(RoomEntityFields.ROOM_ID, roomId) .equalTo(RoomEntityFields.ROOM_ID, roomId)
} }
rql.await() Try {
Try.just(roomId) rql.await(timeout = 20L, timeUnit = TimeUnit.SECONDS)
roomId
}
}.flatMap { roomId -> }.flatMap { roomId ->
if (params.isDirect()) { if (params.isDirect()) {
handleDirectChatCreation(params, roomId) handleDirectChatCreation(params, roomId)

View File

@ -26,6 +26,7 @@ import im.vector.matrix.android.internal.session.room.RoomAPI
import im.vector.matrix.android.internal.session.room.read.SetReadMarkersTask import im.vector.matrix.android.internal.session.room.read.SetReadMarkersTask
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import io.realm.RealmConfiguration import io.realm.RealmConfiguration
import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject


internal interface JoinRoomTask : Task<JoinRoomTask.Params, Unit> { internal interface JoinRoomTask : Task<JoinRoomTask.Params, Unit> {
@ -48,8 +49,10 @@ internal class DefaultJoinRoomTask @Inject constructor(private val roomAPI: Room
realm.where(RoomEntity::class.java) realm.where(RoomEntity::class.java)
.equalTo(RoomEntityFields.ROOM_ID, roomId) .equalTo(RoomEntityFields.ROOM_ID, roomId)
} }
rql.await() Try {
Try.just(roomId) rql.await(20L, TimeUnit.SECONDS)
roomId
}
}.flatMap { roomId -> }.flatMap { roomId ->
setReadMarkers(roomId) setReadMarkers(roomId)
} }

View File

@ -18,6 +18,9 @@ package im.vector.matrix.android.internal.session.user


import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.Transformations import androidx.lifecycle.Transformations
import androidx.paging.DataSource
import androidx.paging.LivePagedListBuilder
import androidx.paging.PagedList
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.user.UserService import im.vector.matrix.android.api.session.user.UserService
@ -38,6 +41,24 @@ internal class DefaultUserService @Inject constructor(private val monarchy: Mona
private val searchUserTask: SearchUserTask, private val searchUserTask: SearchUserTask,
private val taskExecutor: TaskExecutor) : UserService { private val taskExecutor: TaskExecutor) : UserService {


private val realmDataSourceFactory: Monarchy.RealmDataSourceFactory<UserEntity> by lazy {
monarchy.createDataSourceFactory { realm ->
realm.where(UserEntity::class.java)
.isNotEmpty(UserEntityFields.USER_ID)
.sort(UserEntityFields.DISPLAY_NAME)
}
}

private val domainDataSourceFactory: DataSource.Factory<Int, User> by lazy {
realmDataSourceFactory.map {
it.asDomain()
}
}

private val livePagedListBuilder: LivePagedListBuilder<Int, User> by lazy {
LivePagedListBuilder(domainDataSourceFactory, PagedList.Config.Builder().setPageSize(100).setEnablePlaceholders(false).build())
}

override fun getUser(userId: String): User? { override fun getUser(userId: String): User? {
val userEntity = monarchy.fetchCopied { UserEntity.where(it, userId).findFirst() } val userEntity = monarchy.fetchCopied { UserEntity.where(it, userId).findFirst() }
?: return null ?: return null
@ -67,6 +88,25 @@ internal class DefaultUserService @Inject constructor(private val monarchy: Mona
) )
} }


override fun livePagedUsers(filter: String?): LiveData<PagedList<User>> {
realmDataSourceFactory.updateQuery { realm ->
val query = realm.where(UserEntity::class.java)
if (filter.isNullOrEmpty()) {
query.isNotEmpty(UserEntityFields.USER_ID)
} else {
query
.beginGroup()
.contains(UserEntityFields.DISPLAY_NAME, filter)
.or()
.contains(UserEntityFields.USER_ID, filter)
.endGroup()
}
query.sort(UserEntityFields.DISPLAY_NAME)
}
return monarchy.findAllPagedWithChanges(realmDataSourceFactory, livePagedListBuilder)
}


override fun searchUsersDirectory(search: String, override fun searchUsersDirectory(search: String,
limit: Int, limit: Int,
excludedUserIds: Set<String>, excludedUserIds: Set<String>,

View File

@ -148,7 +148,7 @@ android {


dependencies { dependencies {


def epoxy_version = "3.3.0" def epoxy_version = "3.7.0"
def arrow_version = "0.8.2" def arrow_version = "0.8.2"
def coroutines_version = "1.0.1" def coroutines_version = "1.0.1"
def markwon_version = '3.0.0' def markwon_version = '3.0.0'
@ -193,11 +193,15 @@ dependencies {


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:mvrx:1.0.1' implementation 'com.airbnb.android:mvrx:1.0.1'


// Work // Work
implementation "androidx.work:work-runtime-ktx:2.1.0-rc01" implementation "androidx.work:work-runtime-ktx:2.1.0-rc01"


// Paging
implementation "androidx.paging:paging-runtime-ktx:2.1.0"

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



View File

@ -38,7 +38,7 @@ import im.vector.riotx.features.home.HomeDrawerFragment
import im.vector.riotx.features.home.HomeModule import im.vector.riotx.features.home.HomeModule
import im.vector.riotx.features.home.createdirect.CreateDirectRoomActivity import im.vector.riotx.features.home.createdirect.CreateDirectRoomActivity
import im.vector.riotx.features.home.createdirect.CreateDirectRoomDirectoryUsersFragment import im.vector.riotx.features.home.createdirect.CreateDirectRoomDirectoryUsersFragment
import im.vector.riotx.features.home.createdirect.CreateDirectRoomFragment import im.vector.riotx.features.home.createdirect.CreateDirectRoomKnownUsersFragment
import im.vector.riotx.features.home.group.GroupListFragment import im.vector.riotx.features.home.group.GroupListFragment
import im.vector.riotx.features.home.room.detail.RoomDetailFragment import im.vector.riotx.features.home.room.detail.RoomDetailFragment
import im.vector.riotx.features.home.room.detail.timeline.action.* import im.vector.riotx.features.home.room.detail.timeline.action.*
@ -159,7 +159,7 @@ interface ScreenComponent {


fun inject(pushGatewaysFragment: PushGatewaysFragment) fun inject(pushGatewaysFragment: PushGatewaysFragment)


fun inject(createDirectRoomKnownUsersFragment: CreateDirectRoomFragment) fun inject(createDirectRoomKnownUsersFragment: CreateDirectRoomKnownUsersFragment)


fun inject(createDirectRoomDirectoryUsersFragment: CreateDirectRoomDirectoryUsersFragment) fun inject(createDirectRoomDirectoryUsersFragment: CreateDirectRoomDirectoryUsersFragment)



View File

@ -70,7 +70,7 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() {
} }
} }
if (isFirstCreation()) { if (isFirstCreation()) {
addFragment(CreateDirectRoomFragment(), R.id.container) addFragment(CreateDirectRoomKnownUsersFragment(), R.id.container)
} }
viewModel.selectSubscribe(this, CreateDirectRoomViewState::createAndInviteState) { viewModel.selectSubscribe(this, CreateDirectRoomViewState::createAndInviteState) {
renderCreateAndInviteState(it) renderCreateAndInviteState(it)

View File

@ -32,13 +32,13 @@ import im.vector.riotx.core.platform.VectorBaseFragment
import kotlinx.android.synthetic.main.fragment_create_direct_room_directory_users.* import kotlinx.android.synthetic.main.fragment_create_direct_room_directory_users.*
import javax.inject.Inject import javax.inject.Inject


class CreateDirectRoomDirectoryUsersFragment : VectorBaseFragment(), CreateDirectRoomController.Callback { class CreateDirectRoomDirectoryUsersFragment : VectorBaseFragment(), DirectoryUsersController.Callback {


override fun getLayoutResId() = R.layout.fragment_create_direct_room_directory_users override fun getLayoutResId() = R.layout.fragment_create_direct_room_directory_users


private val viewModel: CreateDirectRoomViewModel by activityViewModel() private val viewModel: CreateDirectRoomViewModel by activityViewModel()


@Inject lateinit var directRoomController: CreateDirectRoomController @Inject lateinit var directRoomController: DirectoryUsersController
private lateinit var navigationViewModel: CreateDirectRoomNavigationViewModel private lateinit var navigationViewModel: CreateDirectRoomNavigationViewModel


override fun injectWith(injector: ScreenComponent) { override fun injectWith(injector: ScreenComponent) {
@ -56,7 +56,6 @@ class CreateDirectRoomDirectoryUsersFragment : VectorBaseFragment(), CreateDirec
private fun setupRecyclerView() { private fun setupRecyclerView() {
recyclerView.setHasFixedSize(true) recyclerView.setHasFixedSize(true)
directRoomController.callback = this directRoomController.callback = this
directRoomController.displayMode = CreateDirectRoomViewState.DisplayMode.DIRECTORY_USERS
recyclerView.setController(directRoomController) recyclerView.setController(directRoomController)
} }


@ -76,7 +75,7 @@ class CreateDirectRoomDirectoryUsersFragment : VectorBaseFragment(), CreateDirec


private fun setupCloseView() { private fun setupCloseView() {
createDirectRoomClose.setOnClickListener { createDirectRoomClose.setOnClickListener {
navigationViewModel.goTo(CreateDirectRoomActivity.Navigation.Close) navigationViewModel.goTo(CreateDirectRoomActivity.Navigation.Previous)
} }
} }



View File

@ -41,7 +41,7 @@ import im.vector.riotx.features.home.AvatarRenderer
import kotlinx.android.synthetic.main.fragment_create_direct_room.* import kotlinx.android.synthetic.main.fragment_create_direct_room.*
import javax.inject.Inject import javax.inject.Inject


class CreateDirectRoomFragment : VectorBaseFragment(), CreateDirectRoomController.Callback { class CreateDirectRoomKnownUsersFragment : VectorBaseFragment(), KnownUsersController.Callback {


override fun getLayoutResId() = R.layout.fragment_create_direct_room override fun getLayoutResId() = R.layout.fragment_create_direct_room


@ -49,7 +49,7 @@ class CreateDirectRoomFragment : VectorBaseFragment(), CreateDirectRoomControlle


private val viewModel: CreateDirectRoomViewModel by activityViewModel() private val viewModel: CreateDirectRoomViewModel by activityViewModel()


@Inject lateinit var directRoomController: CreateDirectRoomController @Inject lateinit var directRoomController: KnownUsersController
@Inject lateinit var avatarRenderer: AvatarRenderer @Inject lateinit var avatarRenderer: AvatarRenderer
private lateinit var navigationViewModel: CreateDirectRoomNavigationViewModel private lateinit var navigationViewModel: CreateDirectRoomNavigationViewModel


@ -104,13 +104,13 @@ class CreateDirectRoomFragment : VectorBaseFragment(), CreateDirectRoomControlle
// Don't activate animation as we might have way to much item animation when filtering // Don't activate animation as we might have way to much item animation when filtering
recyclerView.itemAnimator = null recyclerView.itemAnimator = null
directRoomController.callback = this directRoomController.callback = this
directRoomController.displayMode = CreateDirectRoomViewState.DisplayMode.KNOWN_USERS
recyclerView.setController(directRoomController) recyclerView.setController(directRoomController)
} }


private fun setupFilterView() { private fun setupFilterView() {
createDirectRoomFilter createDirectRoomFilter
.textChanges() .textChanges()
.startWith(createDirectRoomFilter.text)
.subscribe { text -> .subscribe { text ->
val filterValue = text.trim() val filterValue = text.trim()
val action = if (filterValue.isBlank()) { val action = if (filterValue.isBlank()) {

View File

@ -35,6 +35,7 @@ import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.core.utils.LiveEvent import im.vector.riotx.core.utils.LiveEvent
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.Single import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.functions.BiFunction import io.reactivex.functions.BiFunction
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit


@ -154,22 +155,13 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
} }


private fun observeKnownUsers() { private fun observeKnownUsers() {
Observable knownUsersFilter
.combineLatest<List<User>, Option<KnowUsersFilter>, List<User>>( .throttleLast(300, TimeUnit.MILLISECONDS)
session.rx().liveUsers(), .observeOn(AndroidSchedulers.mainThread())
knownUsersFilter.throttleLast(300, TimeUnit.MILLISECONDS), .switchMap {
BiFunction { users, filter -> session.rx().livePagedUsers(it.orNull())
val filterValue = filter.orNull()
if (filterValue.isNullOrEmpty()) {
users
} else {
users.filter {
it.displayName?.contains(filterValue, ignoreCase = true) ?: false
|| it.userId.contains(filterValue, ignoreCase = true)
} }
} .execute { async ->
}
).execute { async ->
copy( copy(
knownUsers = async, knownUsers = async,
filterKnownUsersValue = knownUsersFilter.value ?: Option.empty() filterKnownUsersValue = knownUsersFilter.value ?: Option.empty()

View File

@ -18,6 +18,7 @@


package im.vector.riotx.features.home.createdirect package im.vector.riotx.features.home.createdirect


import androidx.paging.PagedList
import arrow.core.Option import arrow.core.Option
import com.airbnb.mvrx.Async import com.airbnb.mvrx.Async
import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.MvRxState
@ -25,7 +26,7 @@ import com.airbnb.mvrx.Uninitialized
import im.vector.matrix.android.api.session.user.model.User import im.vector.matrix.android.api.session.user.model.User


data class CreateDirectRoomViewState( data class CreateDirectRoomViewState(
val knownUsers: Async<List<User>> = Uninitialized, val knownUsers: Async<PagedList<User>> = Uninitialized,
val directoryUsers: Async<List<User>> = Uninitialized, val directoryUsers: Async<List<User>> = Uninitialized,
val selectedUsers: Set<User> = emptySet(), val selectedUsers: Set<User> = emptySet(),
val createAndInviteState: Async<String> = Uninitialized, val createAndInviteState: Async<String> = Uninitialized,

View File

@ -32,13 +32,12 @@ import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.home.AvatarRenderer
import javax.inject.Inject import javax.inject.Inject


class CreateDirectRoomController @Inject constructor(private val session: Session, class DirectoryUsersController @Inject constructor(private val session: Session,
private val avatarRenderer: AvatarRenderer, private val avatarRenderer: AvatarRenderer,
private val stringProvider: StringProvider, private val stringProvider: StringProvider,
private val errorFormatter: ErrorFormatter) : EpoxyController() { private val errorFormatter: ErrorFormatter) : EpoxyController() {


private var state: CreateDirectRoomViewState? = null private var state: CreateDirectRoomViewState? = null
var displayMode = CreateDirectRoomViewState.DisplayMode.KNOWN_USERS


var callback: Callback? = null var callback: Callback? = null


@ -51,19 +50,15 @@ class CreateDirectRoomController @Inject constructor(private val session: Sessio
requestModelBuild() requestModelBuild()
} }



override fun buildModels() { override fun buildModels() {
val currentState = state ?: return val currentState = state ?: return
val hasSearch = currentState.directorySearchTerm.isNotBlank() val hasSearch = currentState.directorySearchTerm.isNotBlank()
val isFiltering = currentState.filterKnownUsersValue.nonEmpty() val asyncUsers = currentState.directoryUsers
val asyncUsers = if (displayMode == CreateDirectRoomViewState.DisplayMode.DIRECTORY_USERS) {
currentState.directoryUsers
} else {
currentState.knownUsers
}
when (asyncUsers) { when (asyncUsers) {
is Uninitialized -> renderEmptyState(false) is Uninitialized -> renderEmptyState(false)
is Loading -> renderLoading() is Loading -> renderLoading()
is Success -> renderSuccess(asyncUsers(), currentState.selectedUsers.map { it.userId }, hasSearch, isFiltering) is Success -> renderSuccess(asyncUsers(), currentState.selectedUsers.map { it.userId }, hasSearch)
is Fail -> renderFailure(asyncUsers.error) is Fail -> renderFailure(asyncUsers.error)
} }
} }
@ -84,31 +79,20 @@ class CreateDirectRoomController @Inject constructor(private val session: Sessio


private fun renderSuccess(users: List<User>, private fun renderSuccess(users: List<User>,
selectedUsers: List<String>, selectedUsers: List<String>,
hasSearch: Boolean, hasSearch: Boolean) {
isFiltering: Boolean) {
if (users.isEmpty()) { if (users.isEmpty()) {
renderEmptyState(hasSearch) renderEmptyState(hasSearch)
} else { } else {
renderUsers(users, selectedUsers, isFiltering) renderUsers(users, selectedUsers)
} }
} }


private fun renderUsers(users: List<User>, selectedUsers: List<String>, isFiltering: Boolean) { private fun renderUsers(users: List<User>, selectedUsers: List<String>) {
var lastFirstLetter: String? = null
for (user in users) { for (user in users) {
if (user.userId == session.myUserId) { if (user.userId == session.myUserId) {
continue continue
} }
val isSelected = selectedUsers.contains(user.userId) val isSelected = selectedUsers.contains(user.userId)
val currentFirstLetter = user.displayName.firstLetterOfDisplayName()
val showLetter = !isFiltering && currentFirstLetter.isNotEmpty() && lastFirstLetter != currentFirstLetter
lastFirstLetter = currentFirstLetter

CreateDirectRoomLetterHeaderItem_()
.id(currentFirstLetter)
.letter(currentFirstLetter)
.addIf(showLetter, this)

createDirectRoomUserItem { createDirectRoomUserItem {
id(user.userId) id(user.userId)
selected(isSelected) selected(isSelected)
@ -124,15 +108,11 @@ class CreateDirectRoomController @Inject constructor(private val session: Sessio
} }


private fun renderEmptyState(hasSearch: Boolean) { private fun renderEmptyState(hasSearch: Boolean) {
val noResultRes = if (displayMode == CreateDirectRoomViewState.DisplayMode.DIRECTORY_USERS) { val noResultRes = if (hasSearch) {
if (hasSearch) {
R.string.no_result_placeholder R.string.no_result_placeholder
} else { } else {
R.string.direct_room_start_search R.string.direct_room_start_search
} }
} else {
R.string.direct_room_no_known_users
}
noResultItem { noResultItem {
id("noResult") id("noResult")
text(stringProvider.getString(noResultRes)) text(stringProvider.getString(noResultRes))
@ -141,9 +121,7 @@ class CreateDirectRoomController @Inject constructor(private val session: Sessio


interface Callback { interface Callback {
fun onItemClick(user: User) fun onItemClick(user: User)
fun retryDirectoryUsersRequest() { fun retryDirectoryUsersRequest()
// NO-OP
}
} }


} }

View File

@ -0,0 +1,130 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package im.vector.riotx.features.home.createdirect

import com.airbnb.epoxy.EpoxyModel
import com.airbnb.epoxy.paging.PagedListEpoxyController
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Incomplete
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.user.model.User
import im.vector.matrix.android.internal.util.createUIHandler
import im.vector.matrix.android.internal.util.firstLetterOfDisplayName
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.EmptyItem_
import im.vector.riotx.core.epoxy.errorWithRetryItem
import im.vector.riotx.core.epoxy.loadingItem
import im.vector.riotx.core.epoxy.noResultItem
import im.vector.riotx.core.error.ErrorFormatter
import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.features.home.AvatarRenderer
import javax.inject.Inject

class KnownUsersController @Inject constructor(private val session: Session,
private val avatarRenderer: AvatarRenderer,
private val stringProvider: StringProvider) : PagedListEpoxyController<User>(
modelBuildingHandler = createUIHandler()
) {

private var selectedUsers: List<String> = emptyList()
private var users: Async<List<User>> = Uninitialized
private var isFiltering: Boolean = false

var callback: Callback? = null

init {
requestModelBuild()
}

fun setData(state: CreateDirectRoomViewState) {
this.isFiltering = !state.filterKnownUsersValue.isEmpty()
val newSelection = state.selectedUsers.map { it.userId }
this.users = state.knownUsers
if (newSelection != selectedUsers) {
this.selectedUsers = newSelection
requestForcedModelBuild()
}
submitList(state.knownUsers())
}

override fun buildItemModel(currentPosition: Int, item: User?): EpoxyModel<*> {
return if (item == null) {
EmptyItem_().id(currentPosition)
} else {
val isSelected = selectedUsers.contains(item.userId)
CreateDirectRoomUserItem_()
.id(item.userId)
.selected(isSelected)
.userId(item.userId)
.name(item.displayName)
.avatarUrl(item.avatarUrl)
.avatarRenderer(avatarRenderer)
.clickListener { _ ->
callback?.onItemClick(item)
}
}
}

override fun addModels(models: List<EpoxyModel<*>>) {
if (users is Incomplete) {
renderLoading()
} else if (models.isEmpty()) {
renderEmptyState()
} else {
var lastFirstLetter: String? = null
for (model in models) {
if (model is CreateDirectRoomUserItem) {
if (model.userId == session.myUserId) continue
val currentFirstLetter = model.name.firstLetterOfDisplayName()
val showLetter = !isFiltering && currentFirstLetter.isNotEmpty() && lastFirstLetter != currentFirstLetter
lastFirstLetter = currentFirstLetter

CreateDirectRoomLetterHeaderItem_()
.id(currentFirstLetter)
.letter(currentFirstLetter)
.addIf(showLetter, this)

model.addTo(this)
} else {
continue
}
}
}
}

private fun renderLoading() {
loadingItem {
id("loading")
}
}

private fun renderEmptyState() {
noResultItem {
id("noResult")
text(stringProvider.getString(R.string.direct_room_no_known_users))
}
}

interface Callback {
fun onItemClick(user: User)
}

}