From f25c9811737817b9d3c65d531b43ce03b5bf040d Mon Sep 17 00:00:00 2001 From: onurays Date: Tue, 28 Apr 2020 17:30:23 +0300 Subject: [PATCH 01/14] Add menu item to invite users to the room. --- .../roomprofile/members/RoomMemberListFragment.kt | 13 +++++++++++++ vector/src/main/res/menu/menu_room_member_list.xml | 12 ++++++++++++ vector/src/main/res/values/strings_riotX.xml | 1 + 3 files changed, 26 insertions(+) create mode 100644 vector/src/main/res/menu/menu_room_member_list.xml diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListFragment.kt index e6e54d6771..2fbcf705fb 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListFragment.kt @@ -17,6 +17,7 @@ package im.vector.riotx.features.roomprofile.members import android.os.Bundle +import android.view.MenuItem import android.view.View import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel @@ -43,6 +44,18 @@ class RoomMemberListFragment @Inject constructor( override fun getLayoutResId() = R.layout.fragment_room_setting_generic + override fun getMenuRes() = R.menu.menu_room_member_list + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.menu_room_member_list_add_member -> { + navigator.openCreateDirectRoom(requireContext()) + return true + } + } + return super.onOptionsItemSelected(item) + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) roomMemberListController.callback = this diff --git a/vector/src/main/res/menu/menu_room_member_list.xml b/vector/src/main/res/menu/menu_room_member_list.xml new file mode 100644 index 0000000000..c8d9bd31f4 --- /dev/null +++ b/vector/src/main/res/menu/menu_room_member_list.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index 8b675ee8c1..fac7795a34 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -36,6 +36,7 @@ Double-check this link The link %1$s is taking you to another site: %2$s.\n\nAre you sure you want to continue? + Add members From a4eba653a32fa1c1c8bca0cce3c10e6135385ab3 Mon Sep 17 00:00:00 2001 From: onurays Date: Thu, 30 Apr 2020 02:50:30 +0300 Subject: [PATCH 02/14] 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 From 57a87ba620ab329711bb00ea0ca66030e46d25fa Mon Sep 17 00:00:00 2001 From: onurays Date: Thu, 30 Apr 2020 13:54:09 +0300 Subject: [PATCH 03/14] Add InviteUsersToRoomActivity and mvrx classes. --- .../main/java/im/vector/matrix/rx/RxRoom.kt | 5 + .../room/membership/joining/InviteTask.kt | 2 + vector/src/main/AndroidManifest.xml | 1 + .../vector/riotx/core/di/ScreenComponent.kt | 2 + .../core/platform/SimpleFragmentActivity.kt | 12 ++ .../invite/InviteUsersToRoomAction.kt | 24 +++ .../invite/InviteUsersToRoomActivity.kt | 145 ++++++++++++++++++ .../invite/InviteUsersToRoomViewEvents.kt | 25 +++ .../invite/InviteUsersToRoomViewModel.kt | 70 +++++++++ .../invite/InviteUsersToRoomViewState.kt | 29 ++++ .../features/navigation/DefaultNavigator.kt | 6 + .../riotx/features/navigation/Navigator.kt | 2 + .../members/RoomMemberListFragment.kt | 2 +- .../userdirectory/KnownUsersFragment.kt | 6 + .../userdirectory/UserDirectoryViewModel.kt | 2 + .../res/menu/vector_invite_users_to_room.xml | 10 ++ vector/src/main/res/values/strings_riotX.xml | 5 + 17 files changed, 347 insertions(+), 1 deletion(-) create mode 100644 vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomAction.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomActivity.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomViewEvents.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomViewModel.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomViewState.kt create mode 100755 vector/src/main/res/menu/vector_invite_users_to_room.xml 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 193b5c3fbf..469bc514e0 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 @@ -28,6 +28,7 @@ import im.vector.matrix.android.api.session.room.send.UserDraft import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.util.Optional import im.vector.matrix.android.api.util.toOptional +import io.reactivex.Completable import io.reactivex.Observable import io.reactivex.Single @@ -95,6 +96,10 @@ class RxRoom(private val room: Room) { fun liveNotificationState(): Observable { return room.getLiveRoomNotificationState().asObservable() } + + fun invite(userId: String, reason: String? = null): Completable = completableBuilder { + room.invite(userId, reason, it) + } } fun Room.rx(): RxRoom { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/InviteTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/InviteTask.kt index 93b3889455..5a8b302f1b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/InviteTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/InviteTask.kt @@ -39,6 +39,8 @@ internal class DefaultInviteTask @Inject constructor( return executeRequest(eventBus) { val body = InviteBody(params.userId, params.reason) apiCall = roomAPI.invite(params.roomId, body) + isRetryable = true + maxRetryCount = 3 } } } diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index 092817a6cc..ae0ffa1f91 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -85,6 +85,7 @@ + diff --git a/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt b/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt index af49b00b59..c38c0c99e6 100644 --- a/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt +++ b/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt @@ -39,6 +39,7 @@ import im.vector.riotx.features.home.room.detail.timeline.reactions.ViewReaction import im.vector.riotx.features.home.room.filtered.FilteredRoomsActivity import im.vector.riotx.features.home.room.list.RoomListModule import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsBottomSheet +import im.vector.riotx.features.invite.InviteUsersToRoomActivity import im.vector.riotx.features.invite.VectorInviteView import im.vector.riotx.features.link.LinkHandlerActivity import im.vector.riotx.features.login.LoginActivity @@ -116,6 +117,7 @@ interface ScreenComponent { fun inject(activity: DebugMenuActivity) fun inject(activity: SharedSecureStorageActivity) fun inject(activity: BigImageViewerActivity) + fun inject(activity: InviteUsersToRoomActivity) /* ========================================================================================== * BottomSheets diff --git a/vector/src/main/java/im/vector/riotx/core/platform/SimpleFragmentActivity.kt b/vector/src/main/java/im/vector/riotx/core/platform/SimpleFragmentActivity.kt index 58ec4b22c6..e8e8f21259 100644 --- a/vector/src/main/java/im/vector/riotx/core/platform/SimpleFragmentActivity.kt +++ b/vector/src/main/java/im/vector/riotx/core/platform/SimpleFragmentActivity.kt @@ -26,6 +26,7 @@ import im.vector.matrix.android.api.session.Session import im.vector.riotx.R import im.vector.riotx.core.di.ScreenComponent import im.vector.riotx.core.extensions.hideKeyboard +import io.reactivex.android.schedulers.AndroidSchedulers import kotlinx.android.synthetic.main.activity.* import javax.inject.Inject @@ -107,4 +108,15 @@ abstract class SimpleFragmentActivity : VectorBaseActivity() { } super.onBackPressed() } + + protected fun VectorViewModel<*, *, T>.observeViewEvents(observer: (T) -> Unit) { + viewEvents + .observe() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { + hideWaitingView() + observer(it) + } + .disposeOnDestroy() + } } diff --git a/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomAction.kt b/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomAction.kt new file mode 100644 index 0000000000..8a62935bdd --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomAction.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.invite + +import im.vector.matrix.android.api.session.user.model.User +import im.vector.riotx.core.platform.VectorViewModelAction + +sealed class InviteUsersToRoomAction : VectorViewModelAction { + data class InviteSelectedUsers(val selectedUsers: Set) : InviteUsersToRoomAction() +} diff --git a/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomActivity.kt b/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomActivity.kt new file mode 100644 index 0000000000..3998a9bfa5 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomActivity.kt @@ -0,0 +1,145 @@ +/* + * 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.invite + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.os.Parcelable +import android.view.View +import androidx.appcompat.app.AlertDialog +import com.airbnb.mvrx.MvRx +import com.airbnb.mvrx.viewModel +import com.airbnb.mvrx.withState +import im.vector.matrix.android.api.failure.Failure +import im.vector.matrix.android.api.session.room.failure.CreateRoomFailure +import im.vector.riotx.R +import im.vector.riotx.core.di.ScreenComponent +import im.vector.riotx.core.error.ErrorFormatter +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.core.utils.toast +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.parcel.Parcelize +import kotlinx.android.synthetic.main.activity.* +import java.net.HttpURLConnection +import javax.inject.Inject + +@Parcelize +data class InviteUsersToRoomArgs(val roomId: String) : Parcelable + +class InviteUsersToRoomActivity : SimpleFragmentActivity() { + + private val viewModel: InviteUsersToRoomViewModel by viewModel() + private lateinit var sharedActionViewModel: UserDirectorySharedActionViewModel + @Inject lateinit var userDirectoryViewModelFactory: UserDirectoryViewModel.Factory + @Inject lateinit var inviteUsersToRoomViewModelFactory: InviteUsersToRoomViewModel.Factory + @Inject lateinit var errorFormatter: ErrorFormatter + + override fun injectWith(injector: ScreenComponent) { + super.injectWith(injector) + injector.inject(this) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + toolbar.visibility = View.GONE + sharedActionViewModel = viewModelProvider.get(UserDirectorySharedActionViewModel::class.java) + sharedActionViewModel + .observe() + .subscribe { sharedAction -> + when (sharedAction) { + 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, + KnownUsersFragment::class.java, + KnownUsersFragmentArgs( + title = getString(R.string.invite_users_to_room_title), + menuResId = R.menu.vector_invite_users_to_room + ) + ) + } + + viewModel.observeViewEvents { renderInviteEvents(it) } + } + + private fun onMenuItemSelected(action: UserDirectorySharedAction.OnMenuItemSelected) { + if (action.itemId == R.id.action_invite_users_to_room_invite) { + viewModel.handle(InviteUsersToRoomAction.InviteSelectedUsers(action.selectedUsers)) + } + } + + private fun renderInviteEvents(viewEvent: InviteUsersToRoomViewEvents) { + when (viewEvent) { + is InviteUsersToRoomViewEvents.Loading -> renderInviteLoading() + is InviteUsersToRoomViewEvents.Success -> renderInvitationSuccess() + is InviteUsersToRoomViewEvents.Failure -> renderInviteFailure(viewEvent.throwable) + } + } + + private fun renderInviteLoading() { + updateWaitingView(WaitingViewData(getString(R.string.inviting_users_to_room))) + } + + private fun renderInviteFailure(error: Throwable) { + hideWaitingView() + if (error is CreateRoomFailure.CreatedWithTimeout) { + finish() + } else { + val message = if (error is Failure.ServerError && error.httpCode == HttpURLConnection.HTTP_INTERNAL_ERROR /*500*/) { + // This error happen if the invited userId does not exist. + getString(R.string.invite_users_to_room_failure) + } else { + errorFormatter.toHumanReadable(error) + } + AlertDialog.Builder(this) + .setMessage(message) + .setPositiveButton(R.string.ok, null) + .show() + } + } + + private fun renderInvitationSuccess() = withState(viewModel) { + toast(R.string.invitations_sent_successfully) + finish() + } + + companion object { + + fun getIntent(context: Context, roomId: String): Intent { + return Intent(context, InviteUsersToRoomActivity::class.java).also { + it.putExtra(MvRx.KEY_ARG, InviteUsersToRoomArgs(roomId)) + } + } + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomViewEvents.kt b/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomViewEvents.kt new file mode 100644 index 0000000000..b4ecfc5a52 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomViewEvents.kt @@ -0,0 +1,25 @@ +/* + * 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.invite + +import im.vector.riotx.core.platform.VectorViewEvents + +sealed class InviteUsersToRoomViewEvents : VectorViewEvents { + object Loading : InviteUsersToRoomViewEvents() + data class Failure(val throwable: Throwable) : InviteUsersToRoomViewEvents() + object Success : InviteUsersToRoomViewEvents() +} diff --git a/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomViewModel.kt b/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomViewModel.kt new file mode 100644 index 0000000000..554a64d33c --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomViewModel.kt @@ -0,0 +1,70 @@ +/* + * 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.invite + +import com.airbnb.mvrx.ActivityViewModelContext +import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.ViewModelContext +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 + +class InviteUsersToRoomViewModel @AssistedInject constructor(@Assisted + initialState: InviteUsersToRoomViewState, + session: Session) + : VectorViewModel(initialState) { + + private val room = session.getRoom(initialState.roomId)!! + + @AssistedInject.Factory + interface Factory { + fun create(initialState: InviteUsersToRoomViewState): InviteUsersToRoomViewModel + } + + companion object : MvRxViewModelFactory { + + @JvmStatic + override fun create(viewModelContext: ViewModelContext, state: InviteUsersToRoomViewState): InviteUsersToRoomViewModel? { + val activity: InviteUsersToRoomActivity = (viewModelContext as ActivityViewModelContext).activity() + return activity.inviteUsersToRoomViewModelFactory.create(state) + } + } + + override fun handle(action: InviteUsersToRoomAction) { + when (action) { + is InviteUsersToRoomAction.InviteSelectedUsers -> inviteUsersToRoom(action.selectedUsers) + } + } + + private fun inviteUsersToRoom(selectedUsers: Set) { + _viewEvents.post(InviteUsersToRoomViewEvents.Loading) + + Observable.fromIterable(selectedUsers).flatMapCompletable { user -> + room.rx().invite(user.userId, null) + }.subscribe( + { + _viewEvents.post(InviteUsersToRoomViewEvents.Success) + }, + { + _viewEvents.post(InviteUsersToRoomViewEvents.Failure(it)) + }).disposeOnClear() + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomViewState.kt b/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomViewState.kt new file mode 100644 index 0000000000..e0c3ec24a3 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomViewState.kt @@ -0,0 +1,29 @@ +/* + * 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.invite + +import com.airbnb.mvrx.Async +import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.Uninitialized + +data class InviteUsersToRoomViewState( + val roomId: String, + val inviteState: Async = Uninitialized +) : MvRxState { + + constructor(args: InviteUsersToRoomArgs) : this(roomId = args.roomId) +} diff --git a/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt index 0f19a1292a..cb2adfc6b3 100644 --- a/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt @@ -41,6 +41,7 @@ import im.vector.riotx.features.debug.DebugMenuActivity import im.vector.riotx.features.home.room.detail.RoomDetailActivity import im.vector.riotx.features.home.room.detail.RoomDetailArgs import im.vector.riotx.features.home.room.filtered.FilteredRoomsActivity +import im.vector.riotx.features.invite.InviteUsersToRoomActivity import im.vector.riotx.features.media.BigImageViewerActivity import im.vector.riotx.features.roomdirectory.RoomDirectoryActivity import im.vector.riotx.features.roomdirectory.createroom.CreateRoomActivity @@ -163,6 +164,11 @@ class DefaultNavigator @Inject constructor( context.startActivity(intent) } + override fun openInviteUsersToRoom(context: Context, roomId: String) { + val intent = InviteUsersToRoomActivity.getIntent(context, roomId) + context.startActivity(intent) + } + override fun openRoomsFiltering(context: Context) { val intent = FilteredRoomsActivity.newIntent(context) context.startActivity(intent) diff --git a/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt b/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt index bf99643912..cc8e7cac34 100644 --- a/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt +++ b/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt @@ -46,6 +46,8 @@ interface Navigator { fun openCreateDirectRoom(context: Context) + fun openInviteUsersToRoom(context: Context, roomId: String) + fun openRoomDirectory(context: Context, initialFilter: String = "") fun openRoomsFiltering(context: Context) diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListFragment.kt index 2fbcf705fb..8a08cbae8a 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListFragment.kt @@ -49,7 +49,7 @@ class RoomMemberListFragment @Inject constructor( override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { R.id.menu_room_member_list_add_member -> { - navigator.openCreateDirectRoom(requireContext()) + navigator.openInviteUsersToRoom(requireContext(), roomProfileArgs.roomId) return true } } diff --git a/vector/src/main/java/im/vector/riotx/features/userdirectory/KnownUsersFragment.kt b/vector/src/main/java/im/vector/riotx/features/userdirectory/KnownUsersFragment.kt index fe8b4ac6c0..c9f97e69fe 100644 --- a/vector/src/main/java/im/vector/riotx/features/userdirectory/KnownUsersFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/KnownUsersFragment.kt @@ -23,6 +23,7 @@ import android.view.View import android.widget.ScrollView import androidx.core.view.forEach import com.airbnb.mvrx.activityViewModel +import com.airbnb.mvrx.args import com.airbnb.mvrx.withState import com.google.android.material.chip.Chip import com.jakewharton.rxbinding3.widget.textChanges @@ -43,6 +44,8 @@ class KnownUsersFragment @Inject constructor( private val dimensionConverter: DimensionConverter ) : VectorBaseFragment(), KnownUsersController.Callback { + private val args: KnownUsersFragmentArgs by args() + override fun getLayoutResId() = R.layout.fragment_known_users override fun getMenuRes() = withState(viewModel) { @@ -55,6 +58,9 @@ class KnownUsersFragment @Inject constructor( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) sharedActionViewModel = activityViewModelProvider.get(UserDirectorySharedActionViewModel::class.java) + + knownUsersTitle.text = args.title + vectorBaseActivity.setSupportActionBar(knownUsersToolbar) setupRecyclerView() setupFilterView() 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 index 67cfa1c1f7..0253e47763 100644 --- a/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryViewModel.kt @@ -31,6 +31,7 @@ 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 im.vector.riotx.features.invite.InviteUsersToRoomActivity import io.reactivex.Single import io.reactivex.android.schedulers.AndroidSchedulers import java.util.concurrent.TimeUnit @@ -59,6 +60,7 @@ class UserDirectoryViewModel @AssistedInject constructor(@Assisted is ActivityViewModelContext -> { when (viewModelContext.activity()) { is CreateDirectRoomActivity -> viewModelContext.activity().userDirectoryViewModelFactory.create(state) + is InviteUsersToRoomActivity -> viewModelContext.activity().userDirectoryViewModelFactory.create(state) else -> error("Wrong activity or fragment") } } diff --git a/vector/src/main/res/menu/vector_invite_users_to_room.xml b/vector/src/main/res/menu/vector_invite_users_to_room.xml new file mode 100755 index 0000000000..2e799b5c03 --- /dev/null +++ b/vector/src/main/res/menu/vector_invite_users_to_room.xml @@ -0,0 +1,10 @@ + + + + + + diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index fac7795a34..861a0426fc 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -37,6 +37,11 @@ Double-check this link The link %1$s is taking you to another site: %2$s.\n\nAre you sure you want to continue? Add members + INVITE + Inviting users… + Invite Users + Invitation sent successfully + We could not invite users. Please check the users you want to invite and try again. From 5dc50195b336e2e444f4ceaf2af12806b5af7db4 Mon Sep 17 00:00:00 2001 From: onurays Date: Thu, 30 Apr 2020 15:28:20 +0300 Subject: [PATCH 04/14] Filter existing room members. --- .../src/main/java/im/vector/matrix/rx/RxSession.kt | 4 ++-- .../vector/matrix/android/api/session/user/UserService.kt | 2 +- .../android/internal/session/user/DefaultUserService.kt | 5 ++++- .../riotx/features/invite/InviteUsersToRoomActivity.kt | 3 ++- .../riotx/features/invite/InviteUsersToRoomViewModel.kt | 4 ++++ .../features/userdirectory/KnownUsersFragmentArgs.kt | 3 ++- .../features/userdirectory/UserDirectoryViewModel.kt | 8 ++++---- .../features/userdirectory/UserDirectoryViewState.kt | 3 ++- 8 files changed, 21 insertions(+), 11 deletions(-) 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 87ff6f0390..dc95a3e40d 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 @@ -81,8 +81,8 @@ class RxSession(private val session: Session) { return session.getIgnoredUsersLive().asObservable() } - fun livePagedUsers(filter: String? = null): Observable> { - return session.getPagedUsersLive(filter).asObservable() + fun livePagedUsers(filter: String? = null, excludedUserIds: Set? = null): Observable> { + return session.getPagedUsersLive(filter, excludedUserIds).asObservable() } fun createRoom(roomParams: CreateRoomParams): Single = singleBuilder { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/user/UserService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/user/UserService.kt index 453400bc99..6a0d00fb4e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/user/UserService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/user/UserService.kt @@ -63,7 +63,7 @@ interface UserService { * @param filter the filter. It will look into userId and displayName. * @return a Livedata of users */ - fun getPagedUsersLive(filter: String? = null): LiveData> + fun getPagedUsersLive(filter: String? = null, excludedUserIds: Set? = null): LiveData> /** * Get list of ignored users 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 761c810b41..3a5d524347 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 @@ -91,7 +91,7 @@ internal class DefaultUserService @Inject constructor(private val monarchy: Mona ) } - override fun getPagedUsersLive(filter: String?): LiveData> { + override fun getPagedUsersLive(filter: String?, excludedUserIds: Set?): LiveData> { realmDataSourceFactory.updateQuery { realm -> val query = realm.where(UserEntity::class.java) if (filter.isNullOrEmpty()) { @@ -104,6 +104,9 @@ internal class DefaultUserService @Inject constructor(private val monarchy: Mona .contains(UserEntityFields.USER_ID, filter) .endGroup() } + excludedUserIds?.let { + query.not().`in`(UserEntityFields.USER_ID, it.toTypedArray()) + } query.sort(UserEntityFields.DISPLAY_NAME) } return monarchy.findAllPagedWithChanges(realmDataSourceFactory, livePagedListBuilder) diff --git a/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomActivity.kt b/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomActivity.kt index 3998a9bfa5..f687fe75cf 100644 --- a/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomActivity.kt @@ -85,7 +85,8 @@ class InviteUsersToRoomActivity : SimpleFragmentActivity() { KnownUsersFragment::class.java, KnownUsersFragmentArgs( title = getString(R.string.invite_users_to_room_title), - menuResId = R.menu.vector_invite_users_to_room + menuResId = R.menu.vector_invite_users_to_room, + excludedUserIds = viewModel.getUserIdsOfRoomMembers() ) ) } diff --git a/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomViewModel.kt b/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomViewModel.kt index 554a64d33c..78d968c733 100644 --- a/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomViewModel.kt @@ -67,4 +67,8 @@ class InviteUsersToRoomViewModel @AssistedInject constructor(@Assisted _viewEvents.post(InviteUsersToRoomViewEvents.Failure(it)) }).disposeOnClear() } + + fun getUserIdsOfRoomMembers(): Set { + return room.roomSummary()?.otherMemberIds?.toSet() ?: emptySet() + } } diff --git a/vector/src/main/java/im/vector/riotx/features/userdirectory/KnownUsersFragmentArgs.kt b/vector/src/main/java/im/vector/riotx/features/userdirectory/KnownUsersFragmentArgs.kt index 2003f085d4..34f1eb826b 100644 --- a/vector/src/main/java/im/vector/riotx/features/userdirectory/KnownUsersFragmentArgs.kt +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/KnownUsersFragmentArgs.kt @@ -22,5 +22,6 @@ import kotlinx.android.parcel.Parcelize @Parcelize data class KnownUsersFragmentArgs( val title: String, - val menuResId: Int? = null + val menuResId: Int? = null, + val excludedUserIds: Set? = null ) : Parcelable 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 index 0253e47763..3111a86bf7 100644 --- a/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryViewModel.kt @@ -96,7 +96,7 @@ class UserDirectoryViewModel @AssistedInject constructor(@Assisted setState { copy(selectedUsers = selectedUsers) } } - private fun observeDirectoryUsers() { + private fun observeDirectoryUsers() = withState { state -> directoryUsersSearch .debounce(300, TimeUnit.MILLISECONDS) .switchMapSingle { search -> @@ -104,7 +104,7 @@ class UserDirectoryViewModel @AssistedInject constructor(@Assisted Single.just(emptyList()) } else { session.rx() - .searchUsersDirectory(search, 50, emptySet()) + .searchUsersDirectory(search, 50, state.excludedUserIds ?: emptySet()) .map { users -> users.sortedBy { it.toMatrixItem().firstLetterOfDisplayName() } } @@ -117,12 +117,12 @@ class UserDirectoryViewModel @AssistedInject constructor(@Assisted .disposeOnClear() } - private fun observeKnownUsers() { + private fun observeKnownUsers() = withState { state -> knownUsersFilter .throttleLast(300, TimeUnit.MILLISECONDS) .observeOn(AndroidSchedulers.mainThread()) .switchMap { - session.rx().livePagedUsers(it.orNull()) + session.rx().livePagedUsers(it.orNull(), state.excludedUserIds) } .execute { async -> copy( 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 index b5920aa695..76abad5a00 100644 --- a/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryViewState.kt @@ -24,6 +24,7 @@ import com.airbnb.mvrx.Uninitialized import im.vector.matrix.android.api.session.user.model.User data class UserDirectoryViewState( + val excludedUserIds: Set? = null, val knownUsers: Async> = Uninitialized, val directoryUsers: Async> = Uninitialized, val selectedUsers: Set = emptySet(), @@ -34,7 +35,7 @@ data class UserDirectoryViewState( val menuResId: Int? ) : MvRxState { - constructor(args: KnownUsersFragmentArgs) : this(title = args.title, menuResId = args.menuResId) + constructor(args: KnownUsersFragmentArgs) : this(title = args.title, menuResId = args.menuResId, excludedUserIds = args.excludedUserIds) enum class DisplayMode { KNOWN_USERS, From 0aeb32706228355277a75fa3aa6ff0dea927b5e5 Mon Sep 17 00:00:00 2001 From: onurays Date: Thu, 30 Apr 2020 15:40:02 +0300 Subject: [PATCH 05/14] Changelog added. --- CHANGES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 550d013ba3..87645fad10 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -28,6 +28,7 @@ Improvements 🙌: - Restart broken Olm sessions ([MSC1719](https://github.com/matrix-org/matrix-doc/pull/1719)) - Cross-Signing | Hide Use recovery key when 4S is not setup (#1007) - Cross-Signing | Trust account xSigning keys by entering Recovery Key (select file or copy) #1199 + - Invite member(s) to an existing room #1276 Bugfix 🐛: - Fix summary notification staying after "mark as read" @@ -49,6 +50,7 @@ Translations 🗣: SDK API changes ⚠️: - Increase targetSdkVersion to 29 + - excludedUserIds parameter add to to UserService.getPagedUsersLive() function Build 🧱: - Compile with Android SDK 29 (Android Q) From cf5d89ea9b9e703f5c28cb1f237502c2670df684 Mon Sep 17 00:00:00 2001 From: onurays Date: Thu, 30 Apr 2020 15:40:54 +0300 Subject: [PATCH 06/14] Documentation added for new parameter excludedUserIds. --- .../im/vector/matrix/android/api/session/user/UserService.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/user/UserService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/user/UserService.kt index 6a0d00fb4e..1abda8ec05 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/user/UserService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/user/UserService.kt @@ -61,6 +61,7 @@ interface UserService { /** * Observe a live [PagedList] of users sorted alphabetically. You can filter the users. * @param filter the filter. It will look into userId and displayName. + * @param excludedUserIds userId list which will be excluded from the result list. * @return a Livedata of users */ fun getPagedUsersLive(filter: String? = null, excludedUserIds: Set? = null): LiveData> From db18272ef2f05ce2fa96f75bbed33d63ccb5bbc0 Mon Sep 17 00:00:00 2001 From: onurays Date: Thu, 30 Apr 2020 15:47:31 +0300 Subject: [PATCH 07/14] Remove strings from strings_riotX.xml --- vector/src/main/res/values/strings.xml | 9 +++++++++ vector/src/main/res/values/strings_riotX.xml | 9 +-------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 370b7cf8f4..8248e7e857 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2336,4 +2336,13 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming Could not add media file to the Gallery Set a new account password… + Double-check this link + The link %1$s is taking you to another site: %2$s.\n\nAre you sure you want to continue? + Add members + INVITE + Inviting users… + Invite Users + Invitation sent successfully + We could not invite users. Please check the users you want to invite and try again. + diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index 861a0426fc..56456aaf5d 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -34,14 +34,7 @@ - Double-check this link - The link %1$s is taking you to another site: %2$s.\n\nAre you sure you want to continue? - Add members - INVITE - Inviting users… - Invite Users - Invitation sent successfully - We could not invite users. Please check the users you want to invite and try again. + From c1c0c6f2c6c4188dedfa28a5e84b5af47dc2aa43 Mon Sep 17 00:00:00 2001 From: onurays Date: Mon, 4 May 2020 11:22:27 +0300 Subject: [PATCH 08/14] Lint fixes. --- .../vector/riotx/features/invite/InviteUsersToRoomViewModel.kt | 3 ++- vector/src/main/res/values/strings.xml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomViewModel.kt b/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomViewModel.kt index 78d968c733..7a01ab2baa 100644 --- a/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomViewModel.kt @@ -65,7 +65,8 @@ class InviteUsersToRoomViewModel @AssistedInject constructor(@Assisted }, { _viewEvents.post(InviteUsersToRoomViewEvents.Failure(it)) - }).disposeOnClear() + }) + .disposeOnClear() } fun getUserIdsOfRoomMembers(): Set { diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 65f96e5f04..698448811f 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2375,4 +2375,4 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming Invitation sent successfully We could not invite users. Please check the users you want to invite and try again. - + \ No newline at end of file From 3a0eed795a405ec76dfe1d110409cc0be0b43317 Mon Sep 17 00:00:00 2001 From: onurays Date: Mon, 4 May 2020 12:09:36 +0300 Subject: [PATCH 09/14] Lint fix. --- vector/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 698448811f..fb52976d3f 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2367,7 +2367,7 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming The link %1$s is taking you to another site: %2$s.\n\nAre you sure you want to continue? "We couldn't create your DM. Please check the users you want to invite and try again." - + Add members INVITE Inviting users… From c7c6cf70e47c497030c76e0142634102ba882538 Mon Sep 17 00:00:00 2001 From: onurays Date: Wed, 6 May 2020 11:20:08 +0300 Subject: [PATCH 10/14] Code review fixes. --- .../session/user/DefaultUserService.kt | 8 ++++--- .../invite/InviteUsersToRoomActivity.kt | 21 +++++++------------ .../userdirectory/KnownUsersFragment.kt | 4 +--- .../userdirectory/KnownUsersFragmentArgs.kt | 2 +- .../userdirectory/UserDirectoryViewEvents.kt | 2 +- .../userdirectory/UserDirectoryViewState.kt | 11 ++-------- 6 files changed, 18 insertions(+), 30 deletions(-) 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 3a5d524347..7cd2f1b743 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 @@ -104,9 +104,11 @@ internal class DefaultUserService @Inject constructor(private val monarchy: Mona .contains(UserEntityFields.USER_ID, filter) .endGroup() } - excludedUserIds?.let { - query.not().`in`(UserEntityFields.USER_ID, it.toTypedArray()) - } + excludedUserIds + ?.takeIf { it.isNotEmpty() } + ?.let { + query.not().`in`(UserEntityFields.USER_ID, it.toTypedArray()) + } query.sort(UserEntityFields.DISPLAY_NAME) } return monarchy.findAllPagedWithChanges(realmDataSourceFactory, livePagedListBuilder) diff --git a/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomActivity.kt b/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomActivity.kt index f687fe75cf..08d2653ca9 100644 --- a/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomActivity.kt @@ -26,7 +26,6 @@ import com.airbnb.mvrx.MvRx import com.airbnb.mvrx.viewModel import com.airbnb.mvrx.withState import im.vector.matrix.android.api.failure.Failure -import im.vector.matrix.android.api.session.room.failure.CreateRoomFailure import im.vector.riotx.R import im.vector.riotx.core.di.ScreenComponent import im.vector.riotx.core.error.ErrorFormatter @@ -114,20 +113,16 @@ class InviteUsersToRoomActivity : SimpleFragmentActivity() { private fun renderInviteFailure(error: Throwable) { hideWaitingView() - if (error is CreateRoomFailure.CreatedWithTimeout) { - finish() + val message = if (error is Failure.ServerError && error.httpCode == HttpURLConnection.HTTP_INTERNAL_ERROR /*500*/) { + // This error happen if the invited userId does not exist. + getString(R.string.invite_users_to_room_failure) } else { - val message = if (error is Failure.ServerError && error.httpCode == HttpURLConnection.HTTP_INTERNAL_ERROR /*500*/) { - // This error happen if the invited userId does not exist. - getString(R.string.invite_users_to_room_failure) - } else { - errorFormatter.toHumanReadable(error) - } - AlertDialog.Builder(this) - .setMessage(message) - .setPositiveButton(R.string.ok, null) - .show() + errorFormatter.toHumanReadable(error) } + AlertDialog.Builder(this) + .setMessage(message) + .setPositiveButton(R.string.ok, null) + .show() } private fun renderInvitationSuccess() = withState(viewModel) { diff --git a/vector/src/main/java/im/vector/riotx/features/userdirectory/KnownUsersFragment.kt b/vector/src/main/java/im/vector/riotx/features/userdirectory/KnownUsersFragment.kt index c9f97e69fe..78482e0b54 100644 --- a/vector/src/main/java/im/vector/riotx/features/userdirectory/KnownUsersFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/KnownUsersFragment.kt @@ -48,9 +48,7 @@ class KnownUsersFragment @Inject constructor( override fun getLayoutResId() = R.layout.fragment_known_users - override fun getMenuRes() = withState(viewModel) { - return@withState it.menuResId ?: -1 - } + override fun getMenuRes() = args.menuResId private val viewModel: UserDirectoryViewModel by activityViewModel() private lateinit var sharedActionViewModel: UserDirectorySharedActionViewModel diff --git a/vector/src/main/java/im/vector/riotx/features/userdirectory/KnownUsersFragmentArgs.kt b/vector/src/main/java/im/vector/riotx/features/userdirectory/KnownUsersFragmentArgs.kt index 34f1eb826b..9e87633608 100644 --- a/vector/src/main/java/im/vector/riotx/features/userdirectory/KnownUsersFragmentArgs.kt +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/KnownUsersFragmentArgs.kt @@ -22,6 +22,6 @@ import kotlinx.android.parcel.Parcelize @Parcelize data class KnownUsersFragmentArgs( val title: String, - val menuResId: Int? = null, + val menuResId: Int, val excludedUserIds: Set? = null ) : Parcelable 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 index 8673e55622..435fce8b16 100644 --- a/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryViewEvents.kt +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryViewEvents.kt @@ -19,6 +19,6 @@ package im.vector.riotx.features.userdirectory import im.vector.riotx.core.platform.VectorViewEvents /** - * Transient events for create direct room screen + * Transient events for invite users to room screen */ sealed class UserDirectoryViewEvents : VectorViewEvents 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 index 76abad5a00..52f92a9994 100644 --- a/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryViewState.kt @@ -30,15 +30,8 @@ data class UserDirectoryViewState( val selectedUsers: Set = emptySet(), val createAndInviteState: Async = Uninitialized, val directorySearchTerm: String = "", - val filterKnownUsersValue: Option = Option.empty(), - val title: String, - val menuResId: Int? + val filterKnownUsersValue: Option = Option.empty() ) : MvRxState { - constructor(args: KnownUsersFragmentArgs) : this(title = args.title, menuResId = args.menuResId, excludedUserIds = args.excludedUserIds) - - enum class DisplayMode { - KNOWN_USERS, - DIRECTORY_USERS - } + constructor(args: KnownUsersFragmentArgs) : this(excludedUserIds = args.excludedUserIds) } From fe013f803e3c44f080922ab29d81f98e0b4fdf1a Mon Sep 17 00:00:00 2001 From: onurays Date: Mon, 11 May 2020 22:43:55 +0300 Subject: [PATCH 11/14] Add action menu icon to invite users. --- .../src/main/res/drawable/ic_invite_users.xml | 18 ++++++++++++++++++ .../main/res/menu/menu_room_member_list.xml | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 vector/src/main/res/drawable/ic_invite_users.xml diff --git a/vector/src/main/res/drawable/ic_invite_users.xml b/vector/src/main/res/drawable/ic_invite_users.xml new file mode 100644 index 0000000000..64e5a3788d --- /dev/null +++ b/vector/src/main/res/drawable/ic_invite_users.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/vector/src/main/res/menu/menu_room_member_list.xml b/vector/src/main/res/menu/menu_room_member_list.xml index c8d9bd31f4..ef452de70f 100644 --- a/vector/src/main/res/menu/menu_room_member_list.xml +++ b/vector/src/main/res/menu/menu_room_member_list.xml @@ -5,7 +5,7 @@ From 700fd47f22efc923426d0313044ef52c87314d14 Mon Sep 17 00:00:00 2001 From: onurays Date: Tue, 12 May 2020 12:10:45 +0300 Subject: [PATCH 12/14] Toast message formatting of invited users. --- .../invite/InviteUsersToRoomActivity.kt | 7 +++---- .../invite/InviteUsersToRoomViewEvents.kt | 2 +- .../invite/InviteUsersToRoomViewModel.kt | 17 +++++++++++++++-- vector/src/main/res/values/strings.xml | 4 +++- 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomActivity.kt b/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomActivity.kt index 08d2653ca9..839a0767d8 100644 --- a/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomActivity.kt @@ -24,7 +24,6 @@ import android.view.View import androidx.appcompat.app.AlertDialog import com.airbnb.mvrx.MvRx import com.airbnb.mvrx.viewModel -import com.airbnb.mvrx.withState import im.vector.matrix.android.api.failure.Failure import im.vector.riotx.R import im.vector.riotx.core.di.ScreenComponent @@ -102,7 +101,7 @@ class InviteUsersToRoomActivity : SimpleFragmentActivity() { private fun renderInviteEvents(viewEvent: InviteUsersToRoomViewEvents) { when (viewEvent) { is InviteUsersToRoomViewEvents.Loading -> renderInviteLoading() - is InviteUsersToRoomViewEvents.Success -> renderInvitationSuccess() + is InviteUsersToRoomViewEvents.Success -> renderInvitationSuccess(viewEvent.successMessage) is InviteUsersToRoomViewEvents.Failure -> renderInviteFailure(viewEvent.throwable) } } @@ -125,8 +124,8 @@ class InviteUsersToRoomActivity : SimpleFragmentActivity() { .show() } - private fun renderInvitationSuccess() = withState(viewModel) { - toast(R.string.invitations_sent_successfully) + private fun renderInvitationSuccess(successMessage: String) { + toast(successMessage) finish() } diff --git a/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomViewEvents.kt b/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomViewEvents.kt index b4ecfc5a52..a76d4a4077 100644 --- a/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomViewEvents.kt +++ b/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomViewEvents.kt @@ -21,5 +21,5 @@ import im.vector.riotx.core.platform.VectorViewEvents sealed class InviteUsersToRoomViewEvents : VectorViewEvents { object Loading : InviteUsersToRoomViewEvents() data class Failure(val throwable: Throwable) : InviteUsersToRoomViewEvents() - object Success : InviteUsersToRoomViewEvents() + data class Success(val successMessage: String) : InviteUsersToRoomViewEvents() } diff --git a/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomViewModel.kt b/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomViewModel.kt index 7a01ab2baa..2d033fdf35 100644 --- a/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomViewModel.kt @@ -24,12 +24,15 @@ 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.R import im.vector.riotx.core.platform.VectorViewModel +import im.vector.riotx.core.resources.StringProvider import io.reactivex.Observable class InviteUsersToRoomViewModel @AssistedInject constructor(@Assisted initialState: InviteUsersToRoomViewState, - session: Session) + session: Session, + val stringProvider: StringProvider) : VectorViewModel(initialState) { private val room = session.getRoom(initialState.roomId)!! @@ -61,7 +64,17 @@ class InviteUsersToRoomViewModel @AssistedInject constructor(@Assisted room.rx().invite(user.userId, null) }.subscribe( { - _viewEvents.post(InviteUsersToRoomViewEvents.Success) + val successMessage = when (selectedUsers.size) { + 1 -> stringProvider.getString(R.string.invitation_sent_to_one_user, + selectedUsers.first().displayName) + 2 -> stringProvider.getString(R.string.invitations_sent_to_two_users, + selectedUsers.first().displayName, + selectedUsers.last().displayName) + else -> stringProvider.getString(R.string.invitations_sent_to_three_and_more_users, + selectedUsers.first().displayName, + selectedUsers.size - 1) + } + _viewEvents.post(InviteUsersToRoomViewEvents.Success(successMessage)) }, { _viewEvents.post(InviteUsersToRoomViewEvents.Failure(it)) diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index fb52976d3f..caaf1a6439 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2372,7 +2372,9 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming INVITE Inviting users… Invite Users - Invitation sent successfully + Invitation sent to %1$s + Invitations sent to %1$s and %2$s + Invitations sent to %1$s and %2$d more We could not invite users. Please check the users you want to invite and try again. \ No newline at end of file From 04dd13d03b58aacd49fb7851187cbdd08d87e75f Mon Sep 17 00:00:00 2001 From: onurays Date: Tue, 12 May 2020 14:10:23 +0300 Subject: [PATCH 13/14] Use plurals in case of 3 or more invited users. --- .../riotx/features/invite/InviteUsersToRoomViewModel.kt | 3 ++- vector/src/main/res/values/strings.xml | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomViewModel.kt b/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomViewModel.kt index 2d033fdf35..07c0cdbc7d 100644 --- a/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomViewModel.kt @@ -70,7 +70,8 @@ class InviteUsersToRoomViewModel @AssistedInject constructor(@Assisted 2 -> stringProvider.getString(R.string.invitations_sent_to_two_users, selectedUsers.first().displayName, selectedUsers.last().displayName) - else -> stringProvider.getString(R.string.invitations_sent_to_three_and_more_users, + else -> stringProvider.getQuantityString(R.plurals.invitations_sent_to_one_and_more_users, + selectedUsers.size - 1, selectedUsers.first().displayName, selectedUsers.size - 1) } diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index caaf1a6439..ecaf5f42b0 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2374,7 +2374,10 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming Invite Users Invitation sent to %1$s Invitations sent to %1$s and %2$s - Invitations sent to %1$s and %2$d more + + Invitations sent to %1$s and 2 more + Invitations sent to %1$s and %2$d more + We could not invite users. Please check the users you want to invite and try again. \ No newline at end of file From 4eaed945e2f839848e44eb2cd779e294684f72e0 Mon Sep 17 00:00:00 2001 From: onurays Date: Tue, 12 May 2020 15:31:15 +0300 Subject: [PATCH 14/14] Fix plurals. --- vector/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index ecaf5f42b0..c72f91b5c4 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2375,7 +2375,7 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming Invitation sent to %1$s Invitations sent to %1$s and %2$s - Invitations sent to %1$s and 2 more + Invitations sent to %1$s and one more Invitations sent to %1$s and %2$d more We could not invite users. Please check the users you want to invite and try again.