mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-21 17:05:39 +03:00
Direct room: finally use PagedList as we can get a lot of users in DB.
This commit is contained in:
parent
ff6ce8a4b7
commit
6deba31111
16 changed files with 245 additions and 72 deletions
|
@ -38,6 +38,8 @@ dependencies {
|
|||
implementation 'androidx.appcompat:appcompat:1.1.0-beta01'
|
||||
implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'
|
||||
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
|
||||
// Paging
|
||||
implementation "androidx.paging:paging-runtime-ktx:2.1.0"
|
||||
|
||||
testImplementation 'junit:junit:4.12'
|
||||
androidTestImplementation 'androidx.test:runner:1.2.0'
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package im.vector.matrix.rx
|
||||
|
||||
import androidx.paging.PagedList
|
||||
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.pushers.Pusher
|
||||
|
@ -48,6 +49,10 @@ class RxSession(private val session: Session) {
|
|||
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 {
|
||||
session.createRoom(roomParams, MatrixCallbackSingle(it)).toSingle(it)
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package im.vector.matrix.android.api.session.user
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.paging.PagedList
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.session.user.model.User
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
|
@ -56,4 +57,11 @@ interface UserService {
|
|||
*/
|
||||
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>>
|
||||
|
||||
}
|
|
@ -19,14 +19,17 @@ package im.vector.matrix.android.internal.database
|
|||
import android.os.Handler
|
||||
import android.os.HandlerThread
|
||||
import io.realm.*
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
private const val THREAD_NAME = "REALM_QUERY_LATCH"
|
||||
|
||||
class RealmQueryLatch<E : RealmObject>(private val realmConfiguration: RealmConfiguration,
|
||||
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 handlerThread = HandlerThread(THREAD_NAME + hashCode())
|
||||
handlerThread.start()
|
||||
|
@ -46,8 +49,13 @@ class RealmQueryLatch<E : RealmObject>(private val realmConfiguration: RealmConf
|
|||
})
|
||||
}
|
||||
handler.post(runnable)
|
||||
latch.await()
|
||||
handlerThread.quit()
|
||||
try {
|
||||
latch.await(timeout, timeUnit)
|
||||
} catch (exception: InterruptedException) {
|
||||
throw exception
|
||||
} finally {
|
||||
handlerThread.quit()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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.util.tryTransactionSync
|
||||
import io.realm.RealmConfiguration
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
|
||||
internal interface CreateRoomTask : Task<CreateRoomParams, String>
|
||||
|
@ -56,8 +57,10 @@ internal class DefaultCreateRoomTask @Inject constructor(private val roomAPI: Ro
|
|||
realm.where(RoomEntity::class.java)
|
||||
.equalTo(RoomEntityFields.ROOM_ID, roomId)
|
||||
}
|
||||
rql.await()
|
||||
Try.just(roomId)
|
||||
Try {
|
||||
rql.await(timeout = 20L, timeUnit = TimeUnit.SECONDS)
|
||||
roomId
|
||||
}
|
||||
}.flatMap { roomId ->
|
||||
if (params.isDirect()) {
|
||||
handleDirectChatCreation(params, roomId)
|
||||
|
|
|
@ -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.task.Task
|
||||
import io.realm.RealmConfiguration
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
|
||||
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)
|
||||
.equalTo(RoomEntityFields.ROOM_ID, roomId)
|
||||
}
|
||||
rql.await()
|
||||
Try.just(roomId)
|
||||
Try {
|
||||
rql.await(20L, TimeUnit.SECONDS)
|
||||
roomId
|
||||
}
|
||||
}.flatMap { roomId ->
|
||||
setReadMarkers(roomId)
|
||||
}
|
||||
|
|
|
@ -18,6 +18,9 @@ package im.vector.matrix.android.internal.session.user
|
|||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Transformations
|
||||
import androidx.paging.DataSource
|
||||
import androidx.paging.LivePagedListBuilder
|
||||
import androidx.paging.PagedList
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
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 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? {
|
||||
val userEntity = monarchy.fetchCopied { UserEntity.where(it, userId).findFirst() }
|
||||
?: 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,
|
||||
limit: Int,
|
||||
excludedUserIds: Set<String>,
|
||||
|
@ -77,4 +117,4 @@ internal class DefaultUserService @Inject constructor(private val monarchy: Mona
|
|||
.dispatchTo(callback)
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -148,7 +148,7 @@ android {
|
|||
|
||||
dependencies {
|
||||
|
||||
def epoxy_version = "3.3.0"
|
||||
def epoxy_version = "3.7.0"
|
||||
def arrow_version = "0.8.2"
|
||||
def coroutines_version = "1.0.1"
|
||||
def markwon_version = '3.0.0'
|
||||
|
@ -193,11 +193,15 @@ dependencies {
|
|||
|
||||
implementation("com.airbnb.android:epoxy:$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'
|
||||
|
||||
// Work
|
||||
implementation "androidx.work:work-runtime-ktx:2.1.0-rc01"
|
||||
|
||||
// Paging
|
||||
implementation "androidx.paging:paging-runtime-ktx:2.1.0"
|
||||
|
||||
// Functional Programming
|
||||
implementation "io.arrow-kt:arrow-core:$arrow_version"
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ import im.vector.riotx.features.home.HomeDrawerFragment
|
|||
import im.vector.riotx.features.home.HomeModule
|
||||
import im.vector.riotx.features.home.createdirect.CreateDirectRoomActivity
|
||||
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.room.detail.RoomDetailFragment
|
||||
import im.vector.riotx.features.home.room.detail.timeline.action.*
|
||||
|
@ -159,7 +159,7 @@ interface ScreenComponent {
|
|||
|
||||
fun inject(pushGatewaysFragment: PushGatewaysFragment)
|
||||
|
||||
fun inject(createDirectRoomKnownUsersFragment: CreateDirectRoomFragment)
|
||||
fun inject(createDirectRoomKnownUsersFragment: CreateDirectRoomKnownUsersFragment)
|
||||
|
||||
fun inject(createDirectRoomDirectoryUsersFragment: CreateDirectRoomDirectoryUsersFragment)
|
||||
|
||||
|
|
|
@ -70,7 +70,7 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() {
|
|||
}
|
||||
}
|
||||
if (isFirstCreation()) {
|
||||
addFragment(CreateDirectRoomFragment(), R.id.container)
|
||||
addFragment(CreateDirectRoomKnownUsersFragment(), R.id.container)
|
||||
}
|
||||
viewModel.selectSubscribe(this, CreateDirectRoomViewState::createAndInviteState) {
|
||||
renderCreateAndInviteState(it)
|
||||
|
|
|
@ -32,13 +32,13 @@ import im.vector.riotx.core.platform.VectorBaseFragment
|
|||
import kotlinx.android.synthetic.main.fragment_create_direct_room_directory_users.*
|
||||
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
|
||||
|
||||
private val viewModel: CreateDirectRoomViewModel by activityViewModel()
|
||||
|
||||
@Inject lateinit var directRoomController: CreateDirectRoomController
|
||||
@Inject lateinit var directRoomController: DirectoryUsersController
|
||||
private lateinit var navigationViewModel: CreateDirectRoomNavigationViewModel
|
||||
|
||||
override fun injectWith(injector: ScreenComponent) {
|
||||
|
@ -56,7 +56,6 @@ class CreateDirectRoomDirectoryUsersFragment : VectorBaseFragment(), CreateDirec
|
|||
private fun setupRecyclerView() {
|
||||
recyclerView.setHasFixedSize(true)
|
||||
directRoomController.callback = this
|
||||
directRoomController.displayMode = CreateDirectRoomViewState.DisplayMode.DIRECTORY_USERS
|
||||
recyclerView.setController(directRoomController)
|
||||
}
|
||||
|
||||
|
@ -76,7 +75,7 @@ class CreateDirectRoomDirectoryUsersFragment : VectorBaseFragment(), CreateDirec
|
|||
|
||||
private fun setupCloseView() {
|
||||
createDirectRoomClose.setOnClickListener {
|
||||
navigationViewModel.goTo(CreateDirectRoomActivity.Navigation.Close)
|
||||
navigationViewModel.goTo(CreateDirectRoomActivity.Navigation.Previous)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ import im.vector.riotx.features.home.AvatarRenderer
|
|||
import kotlinx.android.synthetic.main.fragment_create_direct_room.*
|
||||
import javax.inject.Inject
|
||||
|
||||
class CreateDirectRoomFragment : VectorBaseFragment(), CreateDirectRoomController.Callback {
|
||||
class CreateDirectRoomKnownUsersFragment : VectorBaseFragment(), KnownUsersController.Callback {
|
||||
|
||||
override fun getLayoutResId() = R.layout.fragment_create_direct_room
|
||||
|
||||
|
@ -49,7 +49,7 @@ class CreateDirectRoomFragment : VectorBaseFragment(), CreateDirectRoomControlle
|
|||
|
||||
private val viewModel: CreateDirectRoomViewModel by activityViewModel()
|
||||
|
||||
@Inject lateinit var directRoomController: CreateDirectRoomController
|
||||
@Inject lateinit var directRoomController: KnownUsersController
|
||||
@Inject lateinit var avatarRenderer: AvatarRenderer
|
||||
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
|
||||
recyclerView.itemAnimator = null
|
||||
directRoomController.callback = this
|
||||
directRoomController.displayMode = CreateDirectRoomViewState.DisplayMode.KNOWN_USERS
|
||||
recyclerView.setController(directRoomController)
|
||||
}
|
||||
|
||||
private fun setupFilterView() {
|
||||
createDirectRoomFilter
|
||||
.textChanges()
|
||||
.startWith(createDirectRoomFilter.text)
|
||||
.subscribe { text ->
|
||||
val filterValue = text.trim()
|
||||
val action = if (filterValue.isBlank()) {
|
|
@ -35,6 +35,7 @@ import im.vector.riotx.core.platform.VectorViewModel
|
|||
import im.vector.riotx.core.utils.LiveEvent
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.functions.BiFunction
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
|
@ -154,22 +155,13 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
|
|||
}
|
||||
|
||||
private fun observeKnownUsers() {
|
||||
Observable
|
||||
.combineLatest<List<User>, Option<KnowUsersFilter>, List<User>>(
|
||||
session.rx().liveUsers(),
|
||||
knownUsersFilter.throttleLast(300, TimeUnit.MILLISECONDS),
|
||||
BiFunction { users, filter ->
|
||||
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 ->
|
||||
knownUsersFilter
|
||||
.throttleLast(300, TimeUnit.MILLISECONDS)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.switchMap {
|
||||
session.rx().livePagedUsers(it.orNull())
|
||||
}
|
||||
.execute { async ->
|
||||
copy(
|
||||
knownUsers = async,
|
||||
filterKnownUsersValue = knownUsersFilter.value ?: Option.empty()
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
package im.vector.riotx.features.home.createdirect
|
||||
|
||||
import androidx.paging.PagedList
|
||||
import arrow.core.Option
|
||||
import com.airbnb.mvrx.Async
|
||||
import com.airbnb.mvrx.MvRxState
|
||||
|
@ -25,7 +26,7 @@ import com.airbnb.mvrx.Uninitialized
|
|||
import im.vector.matrix.android.api.session.user.model.User
|
||||
|
||||
data class CreateDirectRoomViewState(
|
||||
val knownUsers: Async<List<User>> = Uninitialized,
|
||||
val knownUsers: Async<PagedList<User>> = Uninitialized,
|
||||
val directoryUsers: Async<List<User>> = Uninitialized,
|
||||
val selectedUsers: Set<User> = emptySet(),
|
||||
val createAndInviteState: Async<String> = Uninitialized,
|
||||
|
|
|
@ -32,13 +32,12 @@ import im.vector.riotx.core.resources.StringProvider
|
|||
import im.vector.riotx.features.home.AvatarRenderer
|
||||
import javax.inject.Inject
|
||||
|
||||
class CreateDirectRoomController @Inject constructor(private val session: Session,
|
||||
private val avatarRenderer: AvatarRenderer,
|
||||
private val stringProvider: StringProvider,
|
||||
private val errorFormatter: ErrorFormatter) : EpoxyController() {
|
||||
class DirectoryUsersController @Inject constructor(private val session: Session,
|
||||
private val avatarRenderer: AvatarRenderer,
|
||||
private val stringProvider: StringProvider,
|
||||
private val errorFormatter: ErrorFormatter) : EpoxyController() {
|
||||
|
||||
private var state: CreateDirectRoomViewState? = null
|
||||
var displayMode = CreateDirectRoomViewState.DisplayMode.KNOWN_USERS
|
||||
|
||||
var callback: Callback? = null
|
||||
|
||||
|
@ -51,19 +50,15 @@ class CreateDirectRoomController @Inject constructor(private val session: Sessio
|
|||
requestModelBuild()
|
||||
}
|
||||
|
||||
|
||||
override fun buildModels() {
|
||||
val currentState = state ?: return
|
||||
val hasSearch = currentState.directorySearchTerm.isNotBlank()
|
||||
val isFiltering = currentState.filterKnownUsersValue.nonEmpty()
|
||||
val asyncUsers = if (displayMode == CreateDirectRoomViewState.DisplayMode.DIRECTORY_USERS) {
|
||||
currentState.directoryUsers
|
||||
} else {
|
||||
currentState.knownUsers
|
||||
}
|
||||
val asyncUsers = currentState.directoryUsers
|
||||
when (asyncUsers) {
|
||||
is Uninitialized -> renderEmptyState(false)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -84,31 +79,20 @@ class CreateDirectRoomController @Inject constructor(private val session: Sessio
|
|||
|
||||
private fun renderSuccess(users: List<User>,
|
||||
selectedUsers: List<String>,
|
||||
hasSearch: Boolean,
|
||||
isFiltering: Boolean) {
|
||||
hasSearch: Boolean) {
|
||||
if (users.isEmpty()) {
|
||||
renderEmptyState(hasSearch)
|
||||
} else {
|
||||
renderUsers(users, selectedUsers, isFiltering)
|
||||
renderUsers(users, selectedUsers)
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderUsers(users: List<User>, selectedUsers: List<String>, isFiltering: Boolean) {
|
||||
var lastFirstLetter: String? = null
|
||||
private fun renderUsers(users: List<User>, selectedUsers: List<String>) {
|
||||
for (user in users) {
|
||||
if (user.userId == session.myUserId) {
|
||||
continue
|
||||
}
|
||||
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 {
|
||||
id(user.userId)
|
||||
selected(isSelected)
|
||||
|
@ -124,14 +108,10 @@ class CreateDirectRoomController @Inject constructor(private val session: Sessio
|
|||
}
|
||||
|
||||
private fun renderEmptyState(hasSearch: Boolean) {
|
||||
val noResultRes = if (displayMode == CreateDirectRoomViewState.DisplayMode.DIRECTORY_USERS) {
|
||||
if (hasSearch) {
|
||||
R.string.no_result_placeholder
|
||||
} else {
|
||||
R.string.direct_room_start_search
|
||||
}
|
||||
val noResultRes = if (hasSearch) {
|
||||
R.string.no_result_placeholder
|
||||
} else {
|
||||
R.string.direct_room_no_known_users
|
||||
R.string.direct_room_start_search
|
||||
}
|
||||
noResultItem {
|
||||
id("noResult")
|
||||
|
@ -141,9 +121,7 @@ class CreateDirectRoomController @Inject constructor(private val session: Sessio
|
|||
|
||||
interface Callback {
|
||||
fun onItemClick(user: User)
|
||||
fun retryDirectoryUsersRequest() {
|
||||
// NO-OP
|
||||
}
|
||||
fun retryDirectoryUsersRequest()
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue