mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-25 02:45:53 +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 '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'
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>>
|
||||||
|
|
||||||
}
|
}
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>,
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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()) {
|
|
@ -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()
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -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