From a4eba653a32fa1c1c8bca0cce3c10e6135385ab3 Mon Sep 17 00:00:00 2001 From: onurays Date: Thu, 30 Apr 2020 02:50:30 +0300 Subject: [PATCH] Make a generic user directory search & selection views. --- .../im/vector/riotx/core/di/FragmentModule.kt | 12 +- .../vector/riotx/core/di/ViewModelModule.kt | 6 +- .../createdirect/CreateDirectRoomAction.kt | 11 +- .../createdirect/CreateDirectRoomActivity.kt | 35 ++++- .../CreateDirectRoomViewEvents.kt | 3 - .../createdirect/CreateDirectRoomViewModel.kt | 102 ++----------- .../createdirect/CreateDirectRoomViewState.kt | 42 ++--- .../DirectoryUsersController.kt | 32 ++-- .../KnownUsersController.kt | 14 +- .../KnownUsersFragment.kt} | 85 +++++------ .../KnownUsersFragmentArgs.kt} | 17 ++- .../userdirectory/UserDirectoryAction.kt | 28 ++++ .../UserDirectoryFragment.kt} | 40 ++--- .../UserDirectoryLetterHeaderItem.kt} | 12 +- .../UserDirectorySharedAction.kt | 27 ++++ .../UserDirectorySharedActionViewModel.kt} | 8 +- .../UserDirectoryUserItem.kt} | 38 +++-- .../userdirectory/UserDirectoryViewEvents.kt | 24 +++ .../userdirectory/UserDirectoryViewModel.kt | 132 ++++++++++++++++ .../userdirectory/UserDirectoryViewState.kt | 43 ++++++ .../main/res/layout/fragment_known_users.xml | 143 ++++++++++++++++++ .../res/layout/fragment_user_directory.xml | 109 +++++++++++++ .../src/main/res/layout/item_known_user.xml | 72 +++++++++ .../item_user_directory_letter_header.xml | 14 ++ 24 files changed, 780 insertions(+), 269 deletions(-) rename vector/src/main/java/im/vector/riotx/features/{createdirect => userdirectory}/DirectoryUsersController.kt (83%) rename vector/src/main/java/im/vector/riotx/features/{createdirect => userdirectory}/KnownUsersController.kt (92%) rename vector/src/main/java/im/vector/riotx/features/{createdirect/CreateDirectRoomKnownUsersFragment.kt => userdirectory/KnownUsersFragment.kt} (60%) rename vector/src/main/java/im/vector/riotx/features/{createdirect/CreateDirectRoomSharedAction.kt => userdirectory/KnownUsersFragmentArgs.kt} (60%) create mode 100644 vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryAction.kt rename vector/src/main/java/im/vector/riotx/features/{createdirect/CreateDirectRoomDirectoryUsersFragment.kt => userdirectory/UserDirectoryFragment.kt} (64%) rename vector/src/main/java/im/vector/riotx/features/{createdirect/CreateDirectRoomLetterHeaderItem.kt => userdirectory/UserDirectoryLetterHeaderItem.kt} (70%) create mode 100644 vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectorySharedAction.kt rename vector/src/main/java/im/vector/riotx/features/{createdirect/CreateDirectRoomSharedActionViewModel.kt => userdirectory/UserDirectorySharedActionViewModel.kt} (70%) rename vector/src/main/java/im/vector/riotx/features/{createdirect/CreateDirectRoomUserItem.kt => userdirectory/UserDirectoryUserItem.kt} (63%) create mode 100644 vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryViewEvents.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryViewModel.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryViewState.kt create mode 100644 vector/src/main/res/layout/fragment_known_users.xml create mode 100644 vector/src/main/res/layout/fragment_user_directory.xml create mode 100644 vector/src/main/res/layout/item_known_user.xml create mode 100644 vector/src/main/res/layout/item_user_directory_letter_header.xml diff --git a/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt index db62ddc2d8..01709efcac 100644 --- a/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt +++ b/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt @@ -23,8 +23,6 @@ import dagger.Binds import dagger.Module import dagger.multibindings.IntoMap import im.vector.riotx.features.attachments.preview.AttachmentsPreviewFragment -import im.vector.riotx.features.createdirect.CreateDirectRoomDirectoryUsersFragment -import im.vector.riotx.features.createdirect.CreateDirectRoomKnownUsersFragment import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupSettingsFragment import im.vector.riotx.features.crypto.quads.SharedSecuredStorageKeyFragment import im.vector.riotx.features.crypto.quads.SharedSecuredStoragePassphraseFragment @@ -63,6 +61,8 @@ import im.vector.riotx.features.login.LoginSplashFragment import im.vector.riotx.features.login.LoginWaitForEmailFragment import im.vector.riotx.features.login.LoginWebFragment import im.vector.riotx.features.login.terms.LoginTermsFragment +import im.vector.riotx.features.userdirectory.KnownUsersFragment +import im.vector.riotx.features.userdirectory.UserDirectoryFragment import im.vector.riotx.features.qrcode.QrCodeScannerFragment import im.vector.riotx.features.reactions.EmojiChooserFragment import im.vector.riotx.features.reactions.EmojiSearchResultFragment @@ -226,13 +226,13 @@ interface FragmentModule { @Binds @IntoMap - @FragmentKey(CreateDirectRoomDirectoryUsersFragment::class) - fun bindCreateDirectRoomDirectoryUsersFragment(fragment: CreateDirectRoomDirectoryUsersFragment): Fragment + @FragmentKey(UserDirectoryFragment::class) + fun bindUserDirectoryFragment(fragment: UserDirectoryFragment): Fragment @Binds @IntoMap - @FragmentKey(CreateDirectRoomKnownUsersFragment::class) - fun bindCreateDirectRoomKnownUsersFragment(fragment: CreateDirectRoomKnownUsersFragment): Fragment + @FragmentKey(KnownUsersFragment::class) + fun bindKnownUsersFragment(fragment: KnownUsersFragment): Fragment @Binds @IntoMap diff --git a/vector/src/main/java/im/vector/riotx/core/di/ViewModelModule.kt b/vector/src/main/java/im/vector/riotx/core/di/ViewModelModule.kt index 4bb0adb9f0..e480cf22ca 100644 --- a/vector/src/main/java/im/vector/riotx/core/di/ViewModelModule.kt +++ b/vector/src/main/java/im/vector/riotx/core/di/ViewModelModule.kt @@ -22,7 +22,6 @@ import dagger.Binds import dagger.Module import dagger.multibindings.IntoMap import im.vector.riotx.core.platform.ConfigurationViewModel -import im.vector.riotx.features.createdirect.CreateDirectRoomSharedActionViewModel import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreFromKeyViewModel import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreFromPassphraseViewModel import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreSharedViewModel @@ -32,6 +31,7 @@ import im.vector.riotx.features.home.room.detail.RoomDetailSharedActionViewModel import im.vector.riotx.features.home.room.detail.timeline.action.MessageSharedActionViewModel import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel import im.vector.riotx.features.login.LoginSharedActionViewModel +import im.vector.riotx.features.userdirectory.UserDirectorySharedActionViewModel import im.vector.riotx.features.reactions.EmojiChooserViewModel import im.vector.riotx.features.roomdirectory.RoomDirectorySharedActionViewModel import im.vector.riotx.features.roomprofile.RoomProfileSharedActionViewModel @@ -87,8 +87,8 @@ interface ViewModelModule { @Binds @IntoMap - @ViewModelKey(CreateDirectRoomSharedActionViewModel::class) - fun bindCreateDirectRoomSharedActionViewModel(viewModel: CreateDirectRoomSharedActionViewModel): ViewModel + @ViewModelKey(UserDirectorySharedActionViewModel::class) + fun bindUserDirectorySharedActionViewModel(viewModel: UserDirectorySharedActionViewModel): ViewModel @Binds @IntoMap diff --git a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomAction.kt b/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomAction.kt index 0e74ff71fd..f995f82ff7 100644 --- a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomAction.kt @@ -1,11 +1,11 @@ /* - * Copyright 2019 New Vector Ltd + * Copyright (c) 2020 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 + * 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, @@ -20,10 +20,5 @@ import im.vector.matrix.android.api.session.user.model.User import im.vector.riotx.core.platform.VectorViewModelAction sealed class CreateDirectRoomAction : VectorViewModelAction { - object CreateRoomAndInviteSelectedUsers : CreateDirectRoomAction() - data class FilterKnownUsers(val value: String) : CreateDirectRoomAction() - data class SearchDirectoryUsers(val value: String) : CreateDirectRoomAction() - object ClearFilterKnownUsers : CreateDirectRoomAction() - data class SelectUser(val user: User) : CreateDirectRoomAction() - data class RemoveSelectedUser(val user: User) : CreateDirectRoomAction() + data class CreateRoomAndInviteSelectedUsers(val selectedUsers: Set) : CreateDirectRoomAction() } diff --git a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomActivity.kt b/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomActivity.kt index 3ae206cd21..ef3e9bdeff 100644 --- a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomActivity.kt @@ -37,6 +37,12 @@ import im.vector.riotx.core.extensions.addFragment import im.vector.riotx.core.extensions.addFragmentToBackstack import im.vector.riotx.core.platform.SimpleFragmentActivity import im.vector.riotx.core.platform.WaitingViewData +import im.vector.riotx.features.userdirectory.KnownUsersFragment +import im.vector.riotx.features.userdirectory.KnownUsersFragmentArgs +import im.vector.riotx.features.userdirectory.UserDirectoryFragment +import im.vector.riotx.features.userdirectory.UserDirectorySharedAction +import im.vector.riotx.features.userdirectory.UserDirectorySharedActionViewModel +import im.vector.riotx.features.userdirectory.UserDirectoryViewModel import kotlinx.android.synthetic.main.activity.* import java.net.HttpURLConnection import javax.inject.Inject @@ -44,7 +50,8 @@ import javax.inject.Inject class CreateDirectRoomActivity : SimpleFragmentActivity() { private val viewModel: CreateDirectRoomViewModel by viewModel() - private lateinit var sharedActionViewModel: CreateDirectRoomSharedActionViewModel + private lateinit var sharedActionViewModel: UserDirectorySharedActionViewModel + @Inject lateinit var userDirectoryViewModelFactory: UserDirectoryViewModel.Factory @Inject lateinit var createDirectRoomViewModelFactory: CreateDirectRoomViewModel.Factory @Inject lateinit var errorFormatter: ErrorFormatter @@ -56,26 +63,40 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) toolbar.visibility = View.GONE - sharedActionViewModel = viewModelProvider.get(CreateDirectRoomSharedActionViewModel::class.java) + sharedActionViewModel = viewModelProvider.get(UserDirectorySharedActionViewModel::class.java) sharedActionViewModel .observe() .subscribe { sharedAction -> when (sharedAction) { - CreateDirectRoomSharedAction.OpenUsersDirectory -> - addFragmentToBackstack(R.id.container, CreateDirectRoomDirectoryUsersFragment::class.java) - CreateDirectRoomSharedAction.Close -> finish() - CreateDirectRoomSharedAction.GoBack -> onBackPressed() + UserDirectorySharedAction.OpenUsersDirectory -> + addFragmentToBackstack(R.id.container, UserDirectoryFragment::class.java) + UserDirectorySharedAction.Close -> finish() + UserDirectorySharedAction.GoBack -> onBackPressed() + is UserDirectorySharedAction.OnMenuItemSelected -> onMenuItemSelected(sharedAction) } } .disposeOnDestroy() if (isFirstCreation()) { - addFragment(R.id.container, CreateDirectRoomKnownUsersFragment::class.java) + addFragment( + R.id.container, + KnownUsersFragment::class.java, + KnownUsersFragmentArgs( + title = getString(R.string.fab_menu_create_chat), + menuResId = R.menu.vector_create_direct_room + ) + ) } viewModel.selectSubscribe(this, CreateDirectRoomViewState::createAndInviteState) { renderCreateAndInviteState(it) } } + private fun onMenuItemSelected(action: UserDirectorySharedAction.OnMenuItemSelected) { + if (action.itemId == R.id.action_create_direct_room) { + viewModel.handle(CreateDirectRoomAction.CreateRoomAndInviteSelectedUsers(action.selectedUsers)) + } + } + private fun renderCreateAndInviteState(state: Async) { when (state) { is Loading -> renderCreationLoading() diff --git a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomViewEvents.kt b/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomViewEvents.kt index 0ed584ac6b..5ea344115a 100644 --- a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomViewEvents.kt +++ b/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomViewEvents.kt @@ -18,7 +18,4 @@ package im.vector.riotx.features.createdirect import im.vector.riotx.core.platform.VectorViewEvents -/** - * Transient events for create direct room screen - */ sealed class CreateDirectRoomViewEvents : VectorViewEvents diff --git a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomViewModel.kt b/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomViewModel.kt index 71fae11486..1800759da6 100644 --- a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomViewModel.kt @@ -1,42 +1,31 @@ /* + * Copyright (c) 2020 New Vector Ltd * - * * 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. + * 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.createdirect -import arrow.core.Option import com.airbnb.mvrx.ActivityViewModelContext 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.room.model.create.CreateRoomParams -import im.vector.matrix.android.api.util.toMatrixItem +import im.vector.matrix.android.api.session.user.model.User import im.vector.matrix.rx.rx -import im.vector.riotx.core.extensions.toggle import im.vector.riotx.core.platform.VectorViewModel -import io.reactivex.Single -import io.reactivex.android.schedulers.AndroidSchedulers -import java.util.concurrent.TimeUnit - -private typealias KnowUsersFilter = String -private typealias DirectoryUsersSearch = String class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted initialState: CreateDirectRoomViewState, @@ -48,9 +37,6 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted fun create(initialState: CreateDirectRoomViewState): CreateDirectRoomViewModel } - private val knownUsersFilter = BehaviorRelay.createDefault>(Option.empty()) - private val directoryUsersSearch = BehaviorRelay.create() - companion object : MvRxViewModelFactory { @JvmStatic @@ -60,25 +46,15 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted } } - init { - observeKnownUsers() - observeDirectoryUsers() - } - override fun handle(action: CreateDirectRoomAction) { when (action) { - is CreateDirectRoomAction.CreateRoomAndInviteSelectedUsers -> createRoomAndInviteSelectedUsers() - is CreateDirectRoomAction.FilterKnownUsers -> knownUsersFilter.accept(Option.just(action.value)) - is CreateDirectRoomAction.ClearFilterKnownUsers -> knownUsersFilter.accept(Option.empty()) - is CreateDirectRoomAction.SearchDirectoryUsers -> directoryUsersSearch.accept(action.value) - is CreateDirectRoomAction.SelectUser -> handleSelectUser(action) - is CreateDirectRoomAction.RemoveSelectedUser -> handleRemoveSelectedUser(action) + is CreateDirectRoomAction.CreateRoomAndInviteSelectedUsers -> createRoomAndInviteSelectedUsers(action.selectedUsers) } } - private fun createRoomAndInviteSelectedUsers() = withState { currentState -> + private fun createRoomAndInviteSelectedUsers(selectedUsers: Set) { val roomParams = CreateRoomParams( - invitedUserIds = currentState.selectedUsers.map { it.userId } + invitedUserIds = selectedUsers.map { it.userId } ) .setDirectMessage() .enableEncryptionIfInvitedUsersSupportIt() @@ -89,52 +65,4 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted copy(createAndInviteState = it) } } - - private fun handleRemoveSelectedUser(action: CreateDirectRoomAction.RemoveSelectedUser) = withState { state -> - val selectedUsers = state.selectedUsers.minus(action.user) - setState { copy(selectedUsers = selectedUsers) } - } - - private fun handleSelectUser(action: CreateDirectRoomAction.SelectUser) = withState { state -> - // Reset the filter asap - directoryUsersSearch.accept("") - val selectedUsers = state.selectedUsers.toggle(action.user) - setState { copy(selectedUsers = selectedUsers) } - } - - private fun observeDirectoryUsers() { - directoryUsersSearch - .debounce(300, TimeUnit.MILLISECONDS) - .switchMapSingle { search -> - val stream = if (search.isBlank()) { - Single.just(emptyList()) - } else { - session.rx() - .searchUsersDirectory(search, 50, emptySet()) - .map { users -> - users.sortedBy { it.toMatrixItem().firstLetterOfDisplayName() } - } - } - stream.toAsync { - copy(directoryUsers = it, directorySearchTerm = search) - } - } - .subscribe() - .disposeOnClear() - } - - private fun observeKnownUsers() { - knownUsersFilter - .throttleLast(300, TimeUnit.MILLISECONDS) - .observeOn(AndroidSchedulers.mainThread()) - .switchMap { - session.rx().livePagedUsers(it.orNull()) - } - .execute { async -> - copy( - knownUsers = async, - filterKnownUsersValue = knownUsersFilter.value ?: Option.empty() - ) - } - } } diff --git a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomViewState.kt b/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomViewState.kt index dcf86ef6f1..8bb8c3ce58 100644 --- a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomViewState.kt @@ -1,41 +1,25 @@ /* + * Copyright (c) 2020 New Vector Ltd * - * * 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. + * 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.createdirect -import androidx.paging.PagedList -import arrow.core.Option import com.airbnb.mvrx.Async import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.Uninitialized -import im.vector.matrix.android.api.session.user.model.User data class CreateDirectRoomViewState( - val knownUsers: Async> = Uninitialized, - val directoryUsers: Async> = Uninitialized, - val selectedUsers: Set = emptySet(), - val createAndInviteState: Async = Uninitialized, - val directorySearchTerm: String = "", - val filterKnownUsersValue: Option = Option.empty() -) : MvRxState { - - enum class DisplayMode { - KNOWN_USERS, - DIRECTORY_USERS - } -} + val createAndInviteState: Async = Uninitialized +) : MvRxState diff --git a/vector/src/main/java/im/vector/riotx/features/createdirect/DirectoryUsersController.kt b/vector/src/main/java/im/vector/riotx/features/userdirectory/DirectoryUsersController.kt similarity index 83% rename from vector/src/main/java/im/vector/riotx/features/createdirect/DirectoryUsersController.kt rename to vector/src/main/java/im/vector/riotx/features/userdirectory/DirectoryUsersController.kt index 1c38e6f723..9d11387fe8 100644 --- a/vector/src/main/java/im/vector/riotx/features/createdirect/DirectoryUsersController.kt +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/DirectoryUsersController.kt @@ -1,22 +1,20 @@ /* + * Copyright (c) 2020 New Vector Ltd * - * * 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. + * 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.createdirect +package im.vector.riotx.features.userdirectory import com.airbnb.epoxy.EpoxyController import com.airbnb.mvrx.Fail @@ -41,7 +39,7 @@ class DirectoryUsersController @Inject constructor(private val session: Session, private val stringProvider: StringProvider, private val errorFormatter: ErrorFormatter) : EpoxyController() { - private var state: CreateDirectRoomViewState? = null + private var state: UserDirectoryViewState? = null var callback: Callback? = null @@ -49,7 +47,7 @@ class DirectoryUsersController @Inject constructor(private val session: Session, requestModelBuild() } - fun setData(state: CreateDirectRoomViewState) { + fun setData(state: UserDirectoryViewState) { this.state = state requestModelBuild() } @@ -110,7 +108,7 @@ class DirectoryUsersController @Inject constructor(private val session: Session, continue } val isSelected = selectedUsers.contains(user.userId) - createDirectRoomUserItem { + userDirectoryUserItem { id(user.userId) selected(isSelected) matrixItem(user.toMatrixItem()) diff --git a/vector/src/main/java/im/vector/riotx/features/createdirect/KnownUsersController.kt b/vector/src/main/java/im/vector/riotx/features/userdirectory/KnownUsersController.kt similarity index 92% rename from vector/src/main/java/im/vector/riotx/features/createdirect/KnownUsersController.kt rename to vector/src/main/java/im/vector/riotx/features/userdirectory/KnownUsersController.kt index a0e20b45f5..7a1ad49b8c 100644 --- a/vector/src/main/java/im/vector/riotx/features/createdirect/KnownUsersController.kt +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/KnownUsersController.kt @@ -1,11 +1,11 @@ /* - * Copyright 2019 New Vector Ltd + * Copyright (c) 2020 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 + * 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, @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.riotx.features.createdirect +package im.vector.riotx.features.userdirectory import com.airbnb.epoxy.EpoxyModel import com.airbnb.epoxy.paging.PagedListEpoxyController @@ -49,7 +49,7 @@ class KnownUsersController @Inject constructor(private val session: Session, requestModelBuild() } - fun setData(state: CreateDirectRoomViewState) { + fun setData(state: UserDirectoryViewState) { this.isFiltering = !state.filterKnownUsersValue.isEmpty() val newSelection = state.selectedUsers.map { it.userId } this.users = state.knownUsers @@ -65,7 +65,7 @@ class KnownUsersController @Inject constructor(private val session: Session, EmptyItem_().id(currentPosition) } else { val isSelected = selectedUsers.contains(item.userId) - CreateDirectRoomUserItem_() + UserDirectoryUserItem_() .id(item.userId) .selected(isSelected) .matrixItem(item.toMatrixItem()) @@ -84,13 +84,13 @@ class KnownUsersController @Inject constructor(private val session: Session, } else { var lastFirstLetter: String? = null for (model in models) { - if (model is CreateDirectRoomUserItem) { + if (model is UserDirectoryUserItem) { if (model.matrixItem.id == session.myUserId) continue val currentFirstLetter = model.matrixItem.firstLetterOfDisplayName() val showLetter = !isFiltering && currentFirstLetter.isNotEmpty() && lastFirstLetter != currentFirstLetter lastFirstLetter = currentFirstLetter - CreateDirectRoomLetterHeaderItem_() + UserDirectoryLetterHeaderItem_() .id(currentFirstLetter) .letter(currentFirstLetter) .addIf(showLetter, this) diff --git a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomKnownUsersFragment.kt b/vector/src/main/java/im/vector/riotx/features/userdirectory/KnownUsersFragment.kt similarity index 60% rename from vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomKnownUsersFragment.kt rename to vector/src/main/java/im/vector/riotx/features/userdirectory/KnownUsersFragment.kt index 24b5394e5c..fe8b4ac6c0 100644 --- a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomKnownUsersFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/KnownUsersFragment.kt @@ -1,28 +1,27 @@ /* + * Copyright (c) 2020 New Vector Ltd * - * * 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. + * 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.createdirect +package im.vector.riotx.features.userdirectory import android.os.Bundle import android.view.Menu import android.view.MenuItem import android.view.View import android.widget.ScrollView +import androidx.core.view.forEach import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.withState import com.google.android.material.chip.Chip @@ -35,30 +34,33 @@ import im.vector.riotx.core.extensions.hideKeyboard import im.vector.riotx.core.extensions.setupAsSearch import im.vector.riotx.core.platform.VectorBaseFragment import im.vector.riotx.core.utils.DimensionConverter -import kotlinx.android.synthetic.main.fragment_create_direct_room.* +import kotlinx.android.synthetic.main.fragment_known_users.* import javax.inject.Inject -class CreateDirectRoomKnownUsersFragment @Inject constructor( +class KnownUsersFragment @Inject constructor( + val userDirectoryViewModelFactory: UserDirectoryViewModel.Factory, private val knownUsersController: KnownUsersController, private val dimensionConverter: DimensionConverter ) : VectorBaseFragment(), KnownUsersController.Callback { - override fun getLayoutResId() = R.layout.fragment_create_direct_room + override fun getLayoutResId() = R.layout.fragment_known_users - override fun getMenuRes() = R.menu.vector_create_direct_room + override fun getMenuRes() = withState(viewModel) { + return@withState it.menuResId ?: -1 + } - private val viewModel: CreateDirectRoomViewModel by activityViewModel() - private lateinit var sharedActionViewModel: CreateDirectRoomSharedActionViewModel + private val viewModel: UserDirectoryViewModel by activityViewModel() + private lateinit var sharedActionViewModel: UserDirectorySharedActionViewModel override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - sharedActionViewModel = activityViewModelProvider.get(CreateDirectRoomSharedActionViewModel::class.java) - vectorBaseActivity.setSupportActionBar(createDirectRoomToolbar) + sharedActionViewModel = activityViewModelProvider.get(UserDirectorySharedActionViewModel::class.java) + vectorBaseActivity.setSupportActionBar(knownUsersToolbar) setupRecyclerView() setupFilterView() setupAddByMatrixIdView() setupCloseView() - viewModel.selectSubscribe(this, CreateDirectRoomViewState::selectedUsers) { + viewModel.selectSubscribe(this, UserDirectoryViewState::selectedUsers) { renderSelectedUsers(it) } } @@ -71,27 +73,22 @@ class CreateDirectRoomKnownUsersFragment @Inject constructor( override fun onPrepareOptionsMenu(menu: Menu) { withState(viewModel) { - val createMenuItem = menu.findItem(R.id.action_create_direct_room) val showMenuItem = it.selectedUsers.isNotEmpty() - createMenuItem.setVisible(showMenuItem) + menu.forEach { menuItem -> + menuItem.isVisible = showMenuItem + } } super.onPrepareOptionsMenu(menu) } - override fun onOptionsItemSelected(item: MenuItem): Boolean { - return when (item.itemId) { - R.id.action_create_direct_room -> { - viewModel.handle(CreateDirectRoomAction.CreateRoomAndInviteSelectedUsers) - true - } - else -> - super.onOptionsItemSelected(item) - } + override fun onOptionsItemSelected(item: MenuItem): Boolean = withState(viewModel) { + sharedActionViewModel.post(UserDirectorySharedAction.OnMenuItemSelected(item.itemId, it.selectedUsers)) + return@withState true } private fun setupAddByMatrixIdView() { addByMatrixId.setOnClickListener { - sharedActionViewModel.post(CreateDirectRoomSharedAction.OpenUsersDirectory) + sharedActionViewModel.post(UserDirectorySharedAction.OpenUsersDirectory) } } @@ -102,26 +99,26 @@ class CreateDirectRoomKnownUsersFragment @Inject constructor( } private fun setupFilterView() { - createDirectRoomFilter + knownUsersFilter .textChanges() - .startWith(createDirectRoomFilter.text) + .startWith(knownUsersFilter.text) .subscribe { text -> val filterValue = text.trim() val action = if (filterValue.isBlank()) { - CreateDirectRoomAction.ClearFilterKnownUsers + UserDirectoryAction.ClearFilterKnownUsers } else { - CreateDirectRoomAction.FilterKnownUsers(filterValue.toString()) + UserDirectoryAction.FilterKnownUsers(filterValue.toString()) } viewModel.handle(action) } .disposeOnDestroyView() - createDirectRoomFilter.setupAsSearch() - createDirectRoomFilter.requestFocus() + knownUsersFilter.setupAsSearch() + knownUsersFilter.requestFocus() } private fun setupCloseView() { - createDirectRoomClose.setOnClickListener { + knownUsersClose.setOnClickListener { requireActivity().finish() } } @@ -157,12 +154,12 @@ class CreateDirectRoomKnownUsersFragment @Inject constructor( chip.isCloseIconVisible = true chipGroup.addView(chip) chip.setOnCloseIconClickListener { - viewModel.handle(CreateDirectRoomAction.RemoveSelectedUser(user)) + viewModel.handle(UserDirectoryAction.RemoveSelectedUser(user)) } } override fun onItemClick(user: User) { view?.hideKeyboard() - viewModel.handle(CreateDirectRoomAction.SelectUser(user)) + viewModel.handle(UserDirectoryAction.SelectUser(user)) } } diff --git a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomSharedAction.kt b/vector/src/main/java/im/vector/riotx/features/userdirectory/KnownUsersFragmentArgs.kt similarity index 60% rename from vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomSharedAction.kt rename to vector/src/main/java/im/vector/riotx/features/userdirectory/KnownUsersFragmentArgs.kt index eeffc1f119..2003f085d4 100644 --- a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomSharedAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/KnownUsersFragmentArgs.kt @@ -1,5 +1,5 @@ /* - * Copyright 2019 New Vector Ltd + * Copyright (c) 2020 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. @@ -14,12 +14,13 @@ * limitations under the License. */ -package im.vector.riotx.features.createdirect +package im.vector.riotx.features.userdirectory -import im.vector.riotx.core.platform.VectorSharedAction +import android.os.Parcelable +import kotlinx.android.parcel.Parcelize -sealed class CreateDirectRoomSharedAction : VectorSharedAction { - object OpenUsersDirectory : CreateDirectRoomSharedAction() - object Close : CreateDirectRoomSharedAction() - object GoBack : CreateDirectRoomSharedAction() -} +@Parcelize +data class KnownUsersFragmentArgs( + val title: String, + val menuResId: Int? = null +) : Parcelable diff --git a/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryAction.kt b/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryAction.kt new file mode 100644 index 0000000000..1df3c02736 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryAction.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2020 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.userdirectory + +import im.vector.matrix.android.api.session.user.model.User +import im.vector.riotx.core.platform.VectorViewModelAction + +sealed class UserDirectoryAction : VectorViewModelAction { + data class FilterKnownUsers(val value: String) : UserDirectoryAction() + data class SearchDirectoryUsers(val value: String) : UserDirectoryAction() + object ClearFilterKnownUsers : UserDirectoryAction() + data class SelectUser(val user: User) : UserDirectoryAction() + data class RemoveSelectedUser(val user: User) : UserDirectoryAction() +} diff --git a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomDirectoryUsersFragment.kt b/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryFragment.kt similarity index 64% rename from vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomDirectoryUsersFragment.kt rename to vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryFragment.kt index ecfe054767..28aa2d433b 100644 --- a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomDirectoryUsersFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryFragment.kt @@ -1,11 +1,11 @@ /* - * Copyright 2019 New Vector Ltd + * Copyright (c) 2020 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 + * 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, @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.riotx.features.createdirect +package im.vector.riotx.features.userdirectory import android.os.Bundle import android.view.View @@ -29,22 +29,22 @@ import im.vector.riotx.core.extensions.hideKeyboard import im.vector.riotx.core.extensions.setupAsSearch import im.vector.riotx.core.extensions.showKeyboard 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.recyclerView +import kotlinx.android.synthetic.main.fragment_user_directory.* import javax.inject.Inject -class CreateDirectRoomDirectoryUsersFragment @Inject constructor( +class UserDirectoryFragment @Inject constructor( private val directRoomController: DirectoryUsersController ) : VectorBaseFragment(), DirectoryUsersController.Callback { - override fun getLayoutResId() = R.layout.fragment_create_direct_room_directory_users + override fun getLayoutResId() = R.layout.fragment_user_directory + private val viewModel: UserDirectoryViewModel by activityViewModel() - private val viewModel: CreateDirectRoomViewModel by activityViewModel() - - private lateinit var sharedActionViewModel: CreateDirectRoomSharedActionViewModel + private lateinit var sharedActionViewModel: UserDirectorySharedActionViewModel override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - sharedActionViewModel = activityViewModelProvider.get(CreateDirectRoomSharedActionViewModel::class.java) + sharedActionViewModel = activityViewModelProvider.get(UserDirectorySharedActionViewModel::class.java) setupRecyclerView() setupSearchByMatrixIdView() setupCloseView() @@ -62,19 +62,19 @@ class CreateDirectRoomDirectoryUsersFragment @Inject constructor( } private fun setupSearchByMatrixIdView() { - createDirectRoomSearchById.setupAsSearch(searchIconRes = 0) - createDirectRoomSearchById + userDirectorySearchById.setupAsSearch(searchIconRes = 0) + userDirectorySearchById .textChanges() .subscribe { - viewModel.handle(CreateDirectRoomAction.SearchDirectoryUsers(it.toString())) + viewModel.handle(UserDirectoryAction.SearchDirectoryUsers(it.toString())) } .disposeOnDestroyView() - createDirectRoomSearchById.showKeyboard(andRequestFocus = true) + userDirectorySearchById.showKeyboard(andRequestFocus = true) } private fun setupCloseView() { - createDirectRoomClose.setOnClickListener { - sharedActionViewModel.post(CreateDirectRoomSharedAction.GoBack) + userDirectoryClose.setOnClickListener { + sharedActionViewModel.post(UserDirectorySharedAction.GoBack) } } @@ -84,12 +84,12 @@ class CreateDirectRoomDirectoryUsersFragment @Inject constructor( override fun onItemClick(user: User) { view?.hideKeyboard() - viewModel.handle(CreateDirectRoomAction.SelectUser(user)) - sharedActionViewModel.post(CreateDirectRoomSharedAction.GoBack) + viewModel.handle(UserDirectoryAction.SelectUser(user)) + sharedActionViewModel.post(UserDirectorySharedAction.GoBack) } override fun retryDirectoryUsersRequest() { - val currentSearch = createDirectRoomSearchById.text.toString() - viewModel.handle(CreateDirectRoomAction.SearchDirectoryUsers(currentSearch)) + val currentSearch = userDirectorySearchById.text.toString() + viewModel.handle(UserDirectoryAction.SearchDirectoryUsers(currentSearch)) } } diff --git a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomLetterHeaderItem.kt b/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryLetterHeaderItem.kt similarity index 70% rename from vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomLetterHeaderItem.kt rename to vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryLetterHeaderItem.kt index e512337c64..e7e9183ada 100644 --- a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomLetterHeaderItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryLetterHeaderItem.kt @@ -1,11 +1,11 @@ /* - * Copyright 2019 New Vector Ltd + * Copyright (c) 2020 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 + * 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, @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.riotx.features.createdirect +package im.vector.riotx.features.userdirectory import android.widget.TextView import com.airbnb.epoxy.EpoxyAttribute @@ -23,8 +23,8 @@ 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() { +@EpoxyModelClass(layout = R.layout.item_user_directory_letter_header) +abstract class UserDirectoryLetterHeaderItem : VectorEpoxyModel() { @EpoxyAttribute var letter: String = "" @@ -33,6 +33,6 @@ abstract class CreateDirectRoomLetterHeaderItem : VectorEpoxyModel(R.id.createDirectRoomLetterView) + val letterView by bind(R.id.userDirectoryLetterView) } } diff --git a/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectorySharedAction.kt b/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectorySharedAction.kt new file mode 100644 index 0000000000..7d1987aa4b --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectorySharedAction.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2020 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.userdirectory + +import im.vector.matrix.android.api.session.user.model.User +import im.vector.riotx.core.platform.VectorSharedAction + +sealed class UserDirectorySharedAction : VectorSharedAction { + object OpenUsersDirectory : UserDirectorySharedAction() + object Close : UserDirectorySharedAction() + object GoBack : UserDirectorySharedAction() + data class OnMenuItemSelected(val itemId: Int, val selectedUsers: Set) : UserDirectorySharedAction() +} diff --git a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomSharedActionViewModel.kt b/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectorySharedActionViewModel.kt similarity index 70% rename from vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomSharedActionViewModel.kt rename to vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectorySharedActionViewModel.kt index 91c21378d2..e7081ea969 100644 --- a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomSharedActionViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectorySharedActionViewModel.kt @@ -1,11 +1,11 @@ /* - * Copyright 2019 New Vector Ltd + * Copyright (c) 2020 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 + * 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, @@ -14,9 +14,9 @@ * limitations under the License. */ -package im.vector.riotx.features.createdirect +package im.vector.riotx.features.userdirectory import im.vector.riotx.core.platform.VectorSharedActionViewModel import javax.inject.Inject -class CreateDirectRoomSharedActionViewModel @Inject constructor() : VectorSharedActionViewModel() +class UserDirectorySharedActionViewModel @Inject constructor() : VectorSharedActionViewModel() diff --git a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomUserItem.kt b/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryUserItem.kt similarity index 63% rename from vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomUserItem.kt rename to vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryUserItem.kt index f2f517fd6e..7ea0709ce8 100644 --- a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomUserItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryUserItem.kt @@ -1,22 +1,20 @@ /* + * Copyright (c) 2020 New Vector Ltd * - * * 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. + * 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.createdirect +package im.vector.riotx.features.userdirectory import android.view.View import android.widget.ImageView @@ -31,8 +29,8 @@ import im.vector.riotx.core.epoxy.VectorEpoxyHolder import im.vector.riotx.core.epoxy.VectorEpoxyModel import im.vector.riotx.features.home.AvatarRenderer -@EpoxyModelClass(layout = R.layout.item_create_direct_room_user) -abstract class CreateDirectRoomUserItem : VectorEpoxyModel() { +@EpoxyModelClass(layout = R.layout.item_known_user) +abstract class UserDirectoryUserItem : VectorEpoxyModel() { @EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer @EpoxyAttribute lateinit var matrixItem: MatrixItem @@ -66,9 +64,9 @@ abstract class CreateDirectRoomUserItem : VectorEpoxyModel(R.id.createDirectRoomUserID) - val nameView by bind(R.id.createDirectRoomUserName) - val avatarImageView by bind(R.id.createDirectRoomUserAvatar) - val avatarCheckedImageView by bind(R.id.createDirectRoomUserAvatarChecked) + val userIdView by bind(R.id.knownUserID) + val nameView by bind(R.id.knownUserName) + val avatarImageView by bind(R.id.knownUserAvatar) + val avatarCheckedImageView by bind(R.id.knownUserAvatarChecked) } } diff --git a/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryViewEvents.kt b/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryViewEvents.kt new file mode 100644 index 0000000000..8673e55622 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryViewEvents.kt @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2020 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.userdirectory + +import im.vector.riotx.core.platform.VectorViewEvents + +/** + * Transient events for create direct room screen + */ +sealed class UserDirectoryViewEvents : VectorViewEvents diff --git a/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryViewModel.kt b/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryViewModel.kt new file mode 100644 index 0000000000..67cfa1c1f7 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryViewModel.kt @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2020 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.userdirectory + +import androidx.fragment.app.FragmentActivity +import arrow.core.Option +import com.airbnb.mvrx.ActivityViewModelContext +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.util.toMatrixItem +import im.vector.matrix.rx.rx +import im.vector.riotx.core.extensions.toggle +import im.vector.riotx.core.platform.VectorViewModel +import im.vector.riotx.features.createdirect.CreateDirectRoomActivity +import io.reactivex.Single +import io.reactivex.android.schedulers.AndroidSchedulers +import java.util.concurrent.TimeUnit + +private typealias KnowUsersFilter = String +private typealias DirectoryUsersSearch = String + +class UserDirectoryViewModel @AssistedInject constructor(@Assisted + initialState: UserDirectoryViewState, + private val session: Session) + : VectorViewModel(initialState) { + + @AssistedInject.Factory + interface Factory { + fun create(initialState: UserDirectoryViewState): UserDirectoryViewModel + } + + private val knownUsersFilter = BehaviorRelay.createDefault>(Option.empty()) + private val directoryUsersSearch = BehaviorRelay.create() + + companion object : MvRxViewModelFactory { + + override fun create(viewModelContext: ViewModelContext, state: UserDirectoryViewState): UserDirectoryViewModel? { + return when (viewModelContext) { + is FragmentViewModelContext -> (viewModelContext.fragment() as KnownUsersFragment).userDirectoryViewModelFactory.create(state) + is ActivityViewModelContext -> { + when (viewModelContext.activity()) { + is CreateDirectRoomActivity -> viewModelContext.activity().userDirectoryViewModelFactory.create(state) + else -> error("Wrong activity or fragment") + } + } + else -> error("Wrong activity or fragment") + } + } + } + + init { + observeKnownUsers() + observeDirectoryUsers() + } + + override fun handle(action: UserDirectoryAction) { + when (action) { + is UserDirectoryAction.FilterKnownUsers -> knownUsersFilter.accept(Option.just(action.value)) + is UserDirectoryAction.ClearFilterKnownUsers -> knownUsersFilter.accept(Option.empty()) + is UserDirectoryAction.SearchDirectoryUsers -> directoryUsersSearch.accept(action.value) + is UserDirectoryAction.SelectUser -> handleSelectUser(action) + is UserDirectoryAction.RemoveSelectedUser -> handleRemoveSelectedUser(action) + } + } + + private fun handleRemoveSelectedUser(action: UserDirectoryAction.RemoveSelectedUser) = withState { state -> + val selectedUsers = state.selectedUsers.minus(action.user) + setState { copy(selectedUsers = selectedUsers) } + } + + private fun handleSelectUser(action: UserDirectoryAction.SelectUser) = withState { state -> + // Reset the filter asap + directoryUsersSearch.accept("") + val selectedUsers = state.selectedUsers.toggle(action.user) + setState { copy(selectedUsers = selectedUsers) } + } + + private fun observeDirectoryUsers() { + directoryUsersSearch + .debounce(300, TimeUnit.MILLISECONDS) + .switchMapSingle { search -> + val stream = if (search.isBlank()) { + Single.just(emptyList()) + } else { + session.rx() + .searchUsersDirectory(search, 50, emptySet()) + .map { users -> + users.sortedBy { it.toMatrixItem().firstLetterOfDisplayName() } + } + } + stream.toAsync { + copy(directoryUsers = it, directorySearchTerm = search) + } + } + .subscribe() + .disposeOnClear() + } + + private fun observeKnownUsers() { + knownUsersFilter + .throttleLast(300, TimeUnit.MILLISECONDS) + .observeOn(AndroidSchedulers.mainThread()) + .switchMap { + session.rx().livePagedUsers(it.orNull()) + } + .execute { async -> + copy( + knownUsers = async, + filterKnownUsersValue = knownUsersFilter.value ?: Option.empty() + ) + } + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryViewState.kt b/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryViewState.kt new file mode 100644 index 0000000000..b5920aa695 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryViewState.kt @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2020 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.userdirectory + +import androidx.paging.PagedList +import arrow.core.Option +import com.airbnb.mvrx.Async +import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.Uninitialized +import im.vector.matrix.android.api.session.user.model.User + +data class UserDirectoryViewState( + val knownUsers: Async> = Uninitialized, + val directoryUsers: Async> = Uninitialized, + val selectedUsers: Set = emptySet(), + val createAndInviteState: Async = Uninitialized, + val directorySearchTerm: String = "", + val filterKnownUsersValue: Option = Option.empty(), + val title: String, + val menuResId: Int? +) : MvRxState { + + constructor(args: KnownUsersFragmentArgs) : this(title = args.title, menuResId = args.menuResId) + + enum class DisplayMode { + KNOWN_USERS, + DIRECTORY_USERS + } +} diff --git a/vector/src/main/res/layout/fragment_known_users.xml b/vector/src/main/res/layout/fragment_known_users.xml new file mode 100644 index 0000000000..915d27bdf7 --- /dev/null +++ b/vector/src/main/res/layout/fragment_known_users.xml @@ -0,0 +1,143 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vector/src/main/res/layout/fragment_user_directory.xml b/vector/src/main/res/layout/fragment_user_directory.xml new file mode 100644 index 0000000000..e10f5bcaa9 --- /dev/null +++ b/vector/src/main/res/layout/fragment_user_directory.xml @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vector/src/main/res/layout/item_known_user.xml b/vector/src/main/res/layout/item_known_user.xml new file mode 100644 index 0000000000..e90b2c6256 --- /dev/null +++ b/vector/src/main/res/layout/item_known_user.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/item_user_directory_letter_header.xml b/vector/src/main/res/layout/item_user_directory_letter_header.xml new file mode 100644 index 0000000000..0cb2faf9bc --- /dev/null +++ b/vector/src/main/res/layout/item_user_directory_letter_header.xml @@ -0,0 +1,14 @@ + + + \ No newline at end of file