diff --git a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/LiveDataObservable.kt b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/LiveDataObservable.kt index d4c9a79fc6..a1943bbe1c 100644 --- a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/LiveDataObservable.kt +++ b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/LiveDataObservable.kt @@ -20,6 +20,8 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.Observer import io.reactivex.Observable import io.reactivex.android.MainThreadDisposable +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.schedulers.Schedulers private class LiveDataObservable( private val liveData: LiveData, @@ -57,5 +59,5 @@ private class LiveDataObservable( } fun LiveData.asObservable(): Observable { - return LiveDataObservable(this) + return LiveDataObservable(this).observeOn(Schedulers.computation()) } \ No newline at end of file diff --git a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt index 2c9c7d8b14..a01a99f531 100644 --- a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt +++ b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt @@ -26,19 +26,19 @@ import io.reactivex.schedulers.Schedulers class RxRoom(private val room: Room) { fun liveRoomSummary(): Observable { - return room.liveRoomSummary().asObservable().observeOn(Schedulers.computation()) + return room.liveRoomSummary().asObservable() } fun liveRoomMemberIds(): Observable> { - return room.getRoomMemberIdsLive().asObservable().observeOn(Schedulers.computation()) + return room.getRoomMemberIdsLive().asObservable() } fun liveAnnotationSummary(eventId: String): Observable { - return room.getEventSummaryLive(eventId).asObservable().observeOn(Schedulers.computation()) + return room.getEventSummaryLive(eventId).asObservable() } fun liveTimelineEvent(eventId: String): Observable { - return room.liveTimeLineEvent(eventId).asObservable().observeOn(Schedulers.computation()) + return room.liveTimeLineEvent(eventId).asObservable() } } diff --git a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt index 709b28e1e4..76724939e4 100644 --- a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt +++ b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt @@ -23,28 +23,29 @@ import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.sync.SyncState import im.vector.matrix.android.api.session.user.model.User import io.reactivex.Observable +import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.schedulers.Schedulers class RxSession(private val session: Session) { fun liveRoomSummaries(): Observable> { - return session.liveRoomSummaries().asObservable().observeOn(Schedulers.computation()) + return session.liveRoomSummaries().asObservable() } fun liveGroupSummaries(): Observable> { - return session.liveGroupSummaries().asObservable().observeOn(Schedulers.computation()) + return session.liveGroupSummaries().asObservable() } fun liveSyncState(): Observable { - return session.syncState().asObservable().observeOn(Schedulers.computation()) + return session.syncState().asObservable() } fun livePushers(): Observable> { - return session.livePushers().asObservable().observeOn(Schedulers.computation()) + return session.livePushers().asObservable() } fun liveUsers(): Observable> { - return session.liveUsers().asObservable().observeOn(Schedulers.computation()) + return session.liveUsers().asObservable() } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/DefaultUserService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/DefaultUserService.kt index b0fded3b80..7eaf966a1f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/DefaultUserService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/DefaultUserService.kt @@ -23,6 +23,8 @@ import im.vector.matrix.android.api.session.user.UserService import im.vector.matrix.android.api.session.user.model.User import im.vector.matrix.android.internal.database.RealmLiveData import im.vector.matrix.android.internal.database.mapper.asDomain +import im.vector.matrix.android.internal.database.model.RoomSummaryEntity +import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields import im.vector.matrix.android.internal.database.model.UserEntity import im.vector.matrix.android.internal.database.model.UserEntityFields import im.vector.matrix.android.internal.database.query.where @@ -34,7 +36,7 @@ internal class DefaultUserService @Inject constructor(private val monarchy: Mona override fun getUser(userId: String): User? { val userEntity = monarchy.fetchCopied { UserEntity.where(it, userId).findFirst() } - ?: return null + ?: return null return userEntity.asDomain() } @@ -51,11 +53,13 @@ internal class DefaultUserService @Inject constructor(private val monarchy: Mona } override fun liveUsers(): LiveData> { - val liveRealmData = RealmLiveData(monarchy.realmConfiguration) { realm -> - realm.where(UserEntity::class.java).sort(UserEntityFields.DISPLAY_NAME) - } - return Transformations.map(liveRealmData) { results -> - results.map { it.asDomain() } - } + return monarchy.findAllMappedWithChanges( + { realm -> + realm.where(UserEntity::class.java) + .isNotEmpty(UserEntityFields.USER_ID) + .sort(UserEntityFields.DISPLAY_NAME) + }, + { it.asDomain() } + ) } } \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomActions.kt b/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomActions.kt new file mode 100644 index 0000000000..a44be66ab2 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomActions.kt @@ -0,0 +1,26 @@ +/* + * 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 + +sealed class CreateDirectRoomActions { + + object CreateRoomAndInviteSelectedUsers : CreateDirectRoomActions() + data class FilterKnownUsers(val value: String) : CreateDirectRoomActions() + object ClearFilterKnownUsers: CreateDirectRoomActions() + object SelectAddByMatrixId : CreateDirectRoomActions() + +} diff --git a/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomController.kt b/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomController.kt index e435e726d1..af7de9c63f 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomController.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomController.kt @@ -45,13 +45,17 @@ class CreateDirectRoomController @Inject constructor(private val avatarRenderer: var lastFirstLetter: String? = null knownUsers.forEach { user -> val currentFirstLetter = user.displayName.firstLetterOfDisplayName() - val showLetter = lastFirstLetter != currentFirstLetter + val showLetter = currentFirstLetter.isNotEmpty() && lastFirstLetter != currentFirstLetter lastFirstLetter = currentFirstLetter + + CreateDirectRoomLetterHeaderItem_() + .id(currentFirstLetter) + .letter(currentFirstLetter) + .addIf(showLetter, this) + createDirectRoomUserItem { id(user.userId) userId(user.userId) - showLetter(showLetter) - firstLetter(currentFirstLetter) name(user.displayName) avatarUrl(user.avatarUrl) avatarRenderer(avatarRenderer) diff --git a/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomFragment.kt index f71998cbf2..eedb561783 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomFragment.kt @@ -19,18 +19,23 @@ package im.vector.riotx.features.home.createdirect import android.os.Bundle +import android.view.MenuItem import com.airbnb.mvrx.fragmentViewModel +import com.jakewharton.rxbinding3.appcompat.queryTextChanges import im.vector.matrix.android.api.session.user.model.User import im.vector.riotx.R import im.vector.riotx.core.di.ScreenComponent import im.vector.riotx.core.platform.VectorBaseFragment import kotlinx.android.synthetic.main.fragment_create_direct_room.* +import java.util.concurrent.TimeUnit import javax.inject.Inject class CreateDirectRoomFragment : VectorBaseFragment(), CreateDirectRoomController.Callback { override fun getLayoutResId() = R.layout.fragment_create_direct_room + override fun getMenuRes() = R.menu.vector_create_direct_room + private val viewModel: CreateDirectRoomViewModel by fragmentViewModel() @Inject lateinit var createDirectRoomViewModelFactory: CreateDirectRoomViewModel.Factory @@ -43,15 +48,43 @@ class CreateDirectRoomFragment : VectorBaseFragment(), CreateDirectRoomControlle override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) setupRecyclerView() + setupFilterView() viewModel.subscribe(this) { renderState(it) } } + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + R.id.action_create_room -> { + viewModel.handle(CreateDirectRoomActions.CreateRoomAndInviteSelectedUsers) + true + } + else -> + super.onOptionsItemSelected(item) + } + } + private fun setupRecyclerView() { recyclerView.setHasFixedSize(true) + // Don't activate animation as we might have way to much item animation when filtering + recyclerView.itemAnimator = null directRoomController.callback = this recyclerView.setController(directRoomController) } + private fun setupFilterView() { + createDirectRoomFilter + .queryTextChanges() + .subscribe { + val action = if (it.isNullOrEmpty()) { + CreateDirectRoomActions.ClearFilterKnownUsers + } else { + CreateDirectRoomActions.FilterKnownUsers(it.toString()) + } + viewModel.handle(action) + } + .disposeOnDestroy() + } + private fun renderState(state: CreateDirectRoomViewState) { directRoomController.setData(state) } diff --git a/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomLetterHeaderItem.kt b/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomLetterHeaderItem.kt new file mode 100644 index 0000000000..fcb3b10ca6 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomLetterHeaderItem.kt @@ -0,0 +1,39 @@ +/* + * 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 android.widget.TextView +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.riotx.R +import im.vector.riotx.core.epoxy.VectorEpoxyHolder +import im.vector.riotx.core.epoxy.VectorEpoxyModel + +@EpoxyModelClass(layout = R.layout.item_create_direct_room_letter_header) +abstract class CreateDirectRoomLetterHeaderItem : VectorEpoxyModel() { + + @EpoxyAttribute var letter: String = "" + + override fun bind(holder: Holder) { + holder.letterView.text = letter + } + + class Holder : VectorEpoxyHolder() { + val letterView by bind(R.id.createDirectRoomLetterView) + } + +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomUserItem.kt b/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomUserItem.kt index c3b2d4f57e..57d9347e1f 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomUserItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomUserItem.kt @@ -32,8 +32,6 @@ import im.vector.riotx.features.home.AvatarRenderer abstract class CreateDirectRoomUserItem : VectorEpoxyModel() { @EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer - @EpoxyAttribute var showLetter: Boolean = false - @EpoxyAttribute var firstLetter: String = "" @EpoxyAttribute var name: String? = null @EpoxyAttribute var userId: String = "" @EpoxyAttribute var avatarUrl: String? = null @@ -41,14 +39,20 @@ abstract class CreateDirectRoomUserItem : VectorEpoxyModel(R.id.createDirectRoomUserLetter) + val userIdView by bind(R.id.createDirectRoomUserID) val nameView by bind(R.id.createDirectRoomUserName) val avatarImageView by bind(R.id.createDirectRoomUserAvatar) } diff --git a/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomViewModel.kt index ce40bc0a22..c9b976cd5a 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomViewModel.kt @@ -18,14 +18,23 @@ package im.vector.riotx.features.home.createdirect +import arrow.core.Option import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.ViewModelContext +import com.jakewharton.rxrelay2.BehaviorRelay import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.api.session.user.model.User import im.vector.matrix.rx.rx import im.vector.riotx.core.platform.VectorViewModel +import io.reactivex.Observable +import io.reactivex.functions.BiFunction +import io.reactivex.subjects.BehaviorSubject +import java.util.concurrent.TimeUnit + +private typealias KnowUsersFilter = String class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted initialState: CreateDirectRoomViewState, @@ -37,6 +46,8 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted fun create(initialState: CreateDirectRoomViewState): CreateDirectRoomViewModel } + private val knownUsersFilter = BehaviorRelay.createDefault>(Option.empty()) + companion object : MvRxViewModelFactory { @JvmStatic @@ -50,10 +61,42 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted observeKnownUsers() } - private fun observeKnownUsers() { - session.rx().liveUsers().execute { - this.copy(knownUsers = it) + fun handle(createDirectRoomActions: CreateDirectRoomActions) { + when (createDirectRoomActions) { + is CreateDirectRoomActions.CreateRoomAndInviteSelectedUsers -> createRoomAndInviteSelectedUsers() + is CreateDirectRoomActions.SelectAddByMatrixId -> handleSelectAddByMatrixId() + is CreateDirectRoomActions.FilterKnownUsers -> knownUsersFilter.accept(Option.just(createDirectRoomActions.value)) + is CreateDirectRoomActions.ClearFilterKnownUsers -> knownUsersFilter.accept(Option.empty()) } } + private fun handleSelectAddByMatrixId() { + // TODO + } + + private fun createRoomAndInviteSelectedUsers() { + // TODO + } + + private fun observeKnownUsers() { + Observable + .combineLatest, Option, List>( + 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 -> + copy(knownUsers = async) + } + } + } \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomViewState.kt b/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomViewState.kt index 18b65357c1..1ad1f88885 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomViewState.kt @@ -24,5 +24,14 @@ import com.airbnb.mvrx.Uninitialized import im.vector.matrix.android.api.session.user.model.User data class CreateDirectRoomViewState( - val knownUsers: Async> = Uninitialized -) : MvRxState \ No newline at end of file + val displayMode: DisplayMode = DisplayMode.KNOWN_USERS, + val knownUsers: Async> = Uninitialized, + val filteredKnownUsers: Async> = Uninitialized +) : MvRxState { + + enum class DisplayMode { + KNOWN_USERS, + MATRIX_ID_USERS + } + +} \ No newline at end of file diff --git a/vector/src/main/res/layout/fragment_create_direct_room.xml b/vector/src/main/res/layout/fragment_create_direct_room.xml index b683d0331e..7895538922 100644 --- a/vector/src/main/res/layout/fragment_create_direct_room.xml +++ b/vector/src/main/res/layout/fragment_create_direct_room.xml @@ -1,14 +1,113 @@ - - + android:layout_height="match_parent"> + + + + + + + + + + + + + + + + + + + + + + + + + + - diff --git a/vector/src/main/res/layout/item_create_direct_room_letter_header.xml b/vector/src/main/res/layout/item_create_direct_room_letter_header.xml new file mode 100644 index 0000000000..80a0fc4ce1 --- /dev/null +++ b/vector/src/main/res/layout/item_create_direct_room_letter_header.xml @@ -0,0 +1,14 @@ + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/item_create_direct_room_user.xml b/vector/src/main/res/layout/item_create_direct_room_user.xml index a078a5ee1c..de1afd5f6c 100644 --- a/vector/src/main/res/layout/item_create_direct_room_user.xml +++ b/vector/src/main/res/layout/item_create_direct_room_user.xml @@ -1,7 +1,8 @@ - - - - \ No newline at end of file + + + \ No newline at end of file diff --git a/vector/src/main/res/menu/vector_create_direct_room.xml b/vector/src/main/res/menu/vector_create_direct_room.xml new file mode 100755 index 0000000000..42a21da9ab --- /dev/null +++ b/vector/src/main/res/menu/vector_create_direct_room.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index f6ec489276..5de5c81b11 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -39,4 +39,6 @@ Link copied to clipboard + Add by matrix ID + \ No newline at end of file