From 2e4d30ef29e8120e44b235ed273fa35db677270f Mon Sep 17 00:00:00 2001 From: Nolan Darilek Date: Fri, 10 Apr 2020 12:07:53 -0500 Subject: [PATCH 01/36] Set `tickerText` to improve accessibility of notifications. Signed-off-by: Nolan Darilek --- gradle.properties | 2 +- .../features/notifications/NotificationDrawerManager.kt | 9 ++++++++- .../riotx/features/notifications/NotificationUtils.kt | 4 +++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/gradle.properties b/gradle.properties index 2e2b110f15..d9d9e57cbc 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,7 +8,7 @@ # The setting is particularly useful for tweaking memory settings. android.enableJetifier=true android.useAndroidX=true -org.gradle.jvmargs=-Xmx1536m +org.gradle.jvmargs=-Xmx8192m # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects diff --git a/vector/src/main/java/im/vector/riotx/features/notifications/NotificationDrawerManager.kt b/vector/src/main/java/im/vector/riotx/features/notifications/NotificationDrawerManager.kt index 94d3860cca..7307e08c56 100644 --- a/vector/src/main/java/im/vector/riotx/features/notifications/NotificationDrawerManager.kt +++ b/vector/src/main/java/im/vector/riotx/features/notifications/NotificationDrawerManager.kt @@ -343,12 +343,19 @@ class NotificationDrawerManager @Inject constructor(private val context: Context globalLastMessageTimestamp = lastMessageTimestamp } + val tickerText = if (roomEventGroupInfo.isDirect) { + String.format("%s: %s", events[0].senderName, events[0].description) + } else { + String.format("%s: %s %s", roomName, events[0].senderName, events[0].description) + } + val notification = notificationUtils.buildMessagesListNotification( style, roomEventGroupInfo, largeBitmap, lastMessageTimestamp, - myUserDisplayName) + myUserDisplayName, + tickerText) // is there an id for this room? notificationUtils.showNotificationMessage(roomId, ROOM_MESSAGES_NOTIFICATION_ID, notification) diff --git a/vector/src/main/java/im/vector/riotx/features/notifications/NotificationUtils.kt b/vector/src/main/java/im/vector/riotx/features/notifications/NotificationUtils.kt index 50fb5b70de..178235ab5f 100755 --- a/vector/src/main/java/im/vector/riotx/features/notifications/NotificationUtils.kt +++ b/vector/src/main/java/im/vector/riotx/features/notifications/NotificationUtils.kt @@ -381,7 +381,8 @@ class NotificationUtils @Inject constructor(private val context: Context, roomInfo: RoomEventGroupInfo, largeIcon: Bitmap?, lastMessageTimestamp: Long, - senderDisplayNameForReplyCompat: String?): Notification { + senderDisplayNameForReplyCompat: String?, + tickerText: String): Notification { val accentColor = ContextCompat.getColor(context, R.color.notification_accent_color) // Build the pending intent for when the notification is clicked val openRoomIntent = buildOpenRoomIntent(roomInfo.roomId) @@ -478,6 +479,7 @@ class NotificationUtils @Inject constructor(private val context: Context, System.currentTimeMillis().toInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT) setDeleteIntent(pendingIntent) } + .setTicker(tickerText) .build() } From f25c9811737817b9d3c65d531b43ce03b5bf040d Mon Sep 17 00:00:00 2001 From: onurays Date: Tue, 28 Apr 2020 17:30:23 +0300 Subject: [PATCH 02/36] 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 03/36] 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 04/36] 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 05/36] 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 06/36] 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 07/36] 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 08/36] 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 09/36] 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 10/36] 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 11/36] 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 da68212255cb2eb15e115ae9aa66a03f826c9d8f Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 6 May 2020 18:11:11 +0200 Subject: [PATCH 12/36] Crashes when private key missing --- CHANGES.md | 2 +- .../features/settings/devices/DevicesViewModel.kt | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index f0cf30af2d..295ec12608 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,7 +8,7 @@ Improvements 🙌: - Bugfix 🐛: - - + - Fix | Verify Manually by Text crashes if private SSK not known (#1337) Translations 🗣: - diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devices/DevicesViewModel.kt b/vector/src/main/java/im/vector/riotx/features/settings/devices/DevicesViewModel.kt index cd5e53b7c3..d0369e7707 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/devices/DevicesViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/devices/DevicesViewModel.kt @@ -30,7 +30,6 @@ import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.NoOpMatrixCallback -import im.vector.matrix.android.api.extensions.tryThis import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.crypto.verification.VerificationMethod @@ -48,6 +47,7 @@ import io.reactivex.Observable import io.reactivex.functions.BiFunction import io.reactivex.subjects.PublishSubject import kotlinx.coroutines.launch +import timber.log.Timber import java.util.concurrent.TimeUnit data class DevicesViewState( @@ -65,6 +65,7 @@ data class DeviceFullInfo( val deviceInfo: DeviceInfo, val cryptoDeviceInfo: CryptoDeviceInfo? ) + class DevicesViewModel @AssistedInject constructor( @Assisted initialState: DevicesViewState, private val session: Session @@ -215,8 +216,13 @@ class DevicesViewModel @AssistedInject constructor( private fun handleVerifyManually(action: DevicesAction.MarkAsManuallyVerified) = withState { state -> viewModelScope.launch { if (state.hasAccountCrossSigning) { - awaitCallback { - tryThis { session.cryptoService().crossSigningService().trustDevice(action.cryptoDeviceInfo.deviceId, it) } + try { + awaitCallback { + session.cryptoService().crossSigningService().trustDevice(action.cryptoDeviceInfo.deviceId, it) + } + } catch (failure: Throwable) { + Timber.e("Failed to manually cross sign device ${action.cryptoDeviceInfo.deviceId} : ${failure.localizedMessage}") + _viewEvents.post(DevicesViewEvents.Failure(failure)) } } else { // legacy From 6bbded1e65ab00a3035bbe2440e3afefa64ecf58 Mon Sep 17 00:00:00 2001 From: Nolan Darilek Date: Thu, 7 May 2020 15:14:31 +0000 Subject: [PATCH 13/36] Use string resource for generating ticker text. --- .../riotx/features/notifications/NotificationDrawerManager.kt | 4 ++-- vector/src/main/res/values/strings.xml | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/notifications/NotificationDrawerManager.kt b/vector/src/main/java/im/vector/riotx/features/notifications/NotificationDrawerManager.kt index 7307e08c56..66a80be89e 100644 --- a/vector/src/main/java/im/vector/riotx/features/notifications/NotificationDrawerManager.kt +++ b/vector/src/main/java/im/vector/riotx/features/notifications/NotificationDrawerManager.kt @@ -344,9 +344,9 @@ class NotificationDrawerManager @Inject constructor(private val context: Context } val tickerText = if (roomEventGroupInfo.isDirect) { - String.format("%s: %s", events[0].senderName, events[0].description) + stringProvider.getString(R.string.notification_ticker_text_dm, events[0].senderName, events[0].description) } else { - String.format("%s: %s %s", roomName, events[0].senderName, events[0].description) + stringProvider.getString(R.string.notification_ticker_text_group, roomName, events[0].senderName, events[0].description) } val notification = notificationUtils.buildMessagesListNotification( diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index ae2cb7bbbb..9d20908b14 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -1089,6 +1089,8 @@ New Invitation Me ** Failed to send - please open room + %1$s: %2$s + %1$s: %2$s %3$s Search for historical From 6e8e7164c6f0c5cc365d97d09879b19074eed9fe Mon Sep 17 00:00:00 2001 From: Nolan Darilek Date: Thu, 7 May 2020 15:29:42 +0000 Subject: [PATCH 14/36] Use last event when generating ticker text. --- .../riotx/features/notifications/NotificationDrawerManager.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/notifications/NotificationDrawerManager.kt b/vector/src/main/java/im/vector/riotx/features/notifications/NotificationDrawerManager.kt index 66a80be89e..9e3a298378 100644 --- a/vector/src/main/java/im/vector/riotx/features/notifications/NotificationDrawerManager.kt +++ b/vector/src/main/java/im/vector/riotx/features/notifications/NotificationDrawerManager.kt @@ -344,9 +344,9 @@ class NotificationDrawerManager @Inject constructor(private val context: Context } val tickerText = if (roomEventGroupInfo.isDirect) { - stringProvider.getString(R.string.notification_ticker_text_dm, events[0].senderName, events[0].description) + stringProvider.getString(R.string.notification_ticker_text_dm, events.last().senderName, events.last().description) } else { - stringProvider.getString(R.string.notification_ticker_text_group, roomName, events[0].senderName, events[0].description) + stringProvider.getString(R.string.notification_ticker_text_group, roomName, events.last().senderName, events.last().description) } val notification = notificationUtils.buildMessagesListNotification( From 35ee7f0b40bc03d1f659c366750d7ae2b98659b9 Mon Sep 17 00:00:00 2001 From: Nolan Darilek Date: Thu, 7 May 2020 15:38:28 +0000 Subject: [PATCH 15/36] Add changelog entry. --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 16b39cc6b1..2bdef0f177 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -17,6 +17,7 @@ Improvements 🙌: - Cross-Signing | Restore history after recover from passphrase (#1214) - Cross-Sign | QR code scan confirmation screens design update (#1187) - Emoji Verification | It's not the same butterfly! (#1220) + - Improve notification accessibility with ticker text (#1226) Bugfix 🐛: - Missing avatar/displayname after verification request message (#841) From 54ecc25831d5ccedfb5870a92790d70de8d3c684 Mon Sep 17 00:00:00 2001 From: Emma Vanbrabant Date: Sat, 9 May 2020 19:52:27 +0100 Subject: [PATCH 16/36] Create ShortcutBuilder and use --- .../riotx/features/home/AvatarRenderer.kt | 33 +++++++ .../riotx/features/home/HomeActivity.kt | 3 + .../features/home/HomeDetailViewModel.kt | 1 + .../riotx/features/home/ShortcutsHandler.kt | 97 +++++++++++++++++++ .../home/room/detail/RoomDetailActivity.kt | 19 +++- 5 files changed, 151 insertions(+), 2 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotx/features/home/ShortcutsHandler.kt diff --git a/vector/src/main/java/im/vector/riotx/features/home/AvatarRenderer.kt b/vector/src/main/java/im/vector/riotx/features/home/AvatarRenderer.kt index 6d85dd8a3e..fb0bd121f1 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/AvatarRenderer.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/AvatarRenderer.kt @@ -72,6 +72,27 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active .into(target) } + @AnyThread + fun shortcutDrawable(context: Context, glideRequest: GlideRequests, matrixItem: MatrixItem): Drawable { + return glideRequest + .asDrawable() + .apply { + val resolvedUrl = resolvedUrl(matrixItem.avatarUrl) + if (resolvedUrl != null) { + load(resolvedUrl) + } else { + val avatarColor = avatarColor(matrixItem, context) + load(TextDrawable.builder() + .beginConfig() + .bold() + .endConfig() + .buildRect(matrixItem.firstLetterOfDisplayName(), avatarColor)) + } + } + .submit() + .get() + } + @AnyThread fun getCachedDrawable(glideRequest: GlideRequests, matrixItem: MatrixItem): Drawable { return buildGlideRequest(glideRequest, matrixItem.avatarUrl) @@ -103,4 +124,16 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active .load(resolvedUrl) .apply(RequestOptions.circleCropTransform()) } + + private fun resolvedUrl(avatarUrl: String?): String? { + return activeSessionHolder.getSafeActiveSession()?.contentUrlResolver() + ?.resolveThumbnail(avatarUrl, THUMBNAIL_SIZE, THUMBNAIL_SIZE, ContentUrlResolver.ThumbnailMethod.SCALE) + } + + private fun avatarColor(matrixItem: MatrixItem, context: Context): Int { + return when (matrixItem) { + is MatrixItem.UserItem -> ContextCompat.getColor(context, getColorFromUserId(matrixItem.id)) + else -> ContextCompat.getColor(context, getColorFromRoomId(matrixItem.id)) + } + } } diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeActivity.kt index 60c974c291..839763ffc0 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/HomeActivity.kt @@ -65,6 +65,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable { @Inject lateinit var notificationDrawerManager: NotificationDrawerManager @Inject lateinit var vectorPreferences: VectorPreferences @Inject lateinit var popupAlertManager: PopupAlertManager + @Inject lateinit var shortcutsHandler: ShortcutsHandler private val drawerListener = object : DrawerLayout.SimpleDrawerListener() { override fun onDrawerStateChanged(newState: Int) { @@ -144,6 +145,8 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable { && activeSessionHolder.getSafeActiveSession()?.hasAlreadySynced() == true) { promptCompleteSecurityIfNeeded() } + + shortcutsHandler.observeRoomsAndBuildShortcuts(context = this) } private fun promptCompleteSecurityIfNeeded() { diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeDetailViewModel.kt index 3824ba7922..e91de984b8 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/HomeDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/HomeDetailViewModel.kt @@ -16,6 +16,7 @@ package im.vector.riotx.features.home +import androidx.core.content.pm.ShortcutInfoCompat import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.ViewModelContext diff --git a/vector/src/main/java/im/vector/riotx/features/home/ShortcutsHandler.kt b/vector/src/main/java/im/vector/riotx/features/home/ShortcutsHandler.kt new file mode 100644 index 0000000000..d59e84ffa6 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/home/ShortcutsHandler.kt @@ -0,0 +1,97 @@ +/* + * 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.home + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.drawable.Drawable +import android.os.Build +import androidx.core.content.pm.ShortcutInfoCompat +import androidx.core.content.pm.ShortcutManagerCompat +import androidx.core.graphics.drawable.IconCompat +import im.vector.matrix.android.api.session.room.model.RoomSummary +import im.vector.matrix.android.api.session.room.model.tag.RoomTag +import im.vector.matrix.android.api.util.toMatrixItem +import im.vector.riotx.core.glide.GlideApp +import im.vector.riotx.core.utils.DimensionConverter +import im.vector.riotx.features.home.room.detail.RoomDetailActivity +import io.reactivex.schedulers.Schedulers +import javax.inject.Inject + +private val useAdaptiveIcon = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O +private const val adaptiveIconSizeDp = 108 +private const val adaptiveIconOuterSidesDp = 18 + +class ShortcutsHandler @Inject constructor( + private val homeRoomListStore: HomeRoomListDataSource, + private val avatarRenderer: AvatarRenderer, + private val dimensionConverter: DimensionConverter +) { + + @SuppressLint("CheckResult") + fun observeRoomsAndBuildShortcuts(context: Context) { + homeRoomListStore + .observe() + .observeOn(Schedulers.computation()) + .subscribe { rooms -> + val shortcuts = rooms + .favoriteRooms() + .map { room -> + val intent = RoomDetailActivity.shortcutIntent(context, room.roomId) + val drawable = avatarRenderer.shortcutDrawable(context, GlideApp.with(context), room.toMatrixItem()) + + ShortcutInfoCompat.Builder(context, room.roomId) + .setShortLabel(room.displayName) + .setIcon(drawable.toProfileImageIcon()) + .setIntent(intent) + .build() + } + + ShortcutManagerCompat.removeAllDynamicShortcuts(context) + ShortcutManagerCompat.addDynamicShortcuts(context, shortcuts) + } + } + + // PRIVATE API ********************************************************************************* + + private fun List.favoriteRooms(): List { + return filter { room -> room.tags.any { it.name == RoomTag.ROOM_TAG_FAVOURITE } } + .take(n = 4) // Android only allows us to create 4 shortcuts + } + + private fun Drawable.toProfileImageIcon(): IconCompat { + val adaptiveIconSize = dimensionConverter.dpToPx(adaptiveIconSizeDp) + val adaptiveIconOuterSides = dimensionConverter.dpToPx(adaptiveIconOuterSidesDp) + + val bitmap = Bitmap.createBitmap(adaptiveIconSize, adaptiveIconSize, Bitmap.Config.ARGB_8888) + val canvas = Canvas(bitmap) + + return if (useAdaptiveIcon) { + setBounds(adaptiveIconOuterSides, adaptiveIconOuterSides, adaptiveIconSize - adaptiveIconOuterSides, adaptiveIconSize - adaptiveIconOuterSides) + draw(canvas) + + IconCompat.createWithAdaptiveBitmap(bitmap) + } else { + setBounds(0, 0, bitmap.width, bitmap.height) + draw(canvas) + + IconCompat.createWithBitmap(bitmap) + } + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailActivity.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailActivity.kt index fe4d0ae1f7..6507bf6030 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailActivity.kt @@ -44,8 +44,14 @@ class RoomDetailActivity : VectorBaseActivity(), ToolbarConfigurable { super.onCreate(savedInstanceState) waitingView = waiting_view if (isFirstCreation()) { - val roomDetailArgs: RoomDetailArgs = intent?.extras?.getParcelable(EXTRA_ROOM_DETAIL_ARGS) - ?: return + val roomDetailArgs: RoomDetailArgs? = if (intent?.action == ACTION_ROOM_DETAILS_FROM_SHORTCUT) { + RoomDetailArgs(roomId = intent?.extras?.getString(EXTRA_ROOM_ID)!!) + } else { + intent?.extras?.getParcelable(EXTRA_ROOM_DETAIL_ARGS) + } + + if (roomDetailArgs == null) return + currentRoomId = roomDetailArgs.roomId replaceFragment(R.id.roomDetailContainer, RoomDetailFragment::class.java, roomDetailArgs) replaceFragment(R.id.roomDetailDrawerContainer, BreadcrumbsFragment::class.java) @@ -110,11 +116,20 @@ class RoomDetailActivity : VectorBaseActivity(), ToolbarConfigurable { companion object { const val EXTRA_ROOM_DETAIL_ARGS = "EXTRA_ROOM_DETAIL_ARGS" + const val EXTRA_ROOM_ID = "EXTRA_ROOM_ID" + const val ACTION_ROOM_DETAILS_FROM_SHORTCUT = "ROOM_DETAILS_FROM_SHORTCUT" fun newIntent(context: Context, roomDetailArgs: RoomDetailArgs): Intent { return Intent(context, RoomDetailActivity::class.java).apply { putExtra(EXTRA_ROOM_DETAIL_ARGS, roomDetailArgs) } } + + fun shortcutIntent(context: Context, roomId: String): Intent { + return Intent(context, RoomDetailActivity::class.java).apply { + action = ACTION_ROOM_DETAILS_FROM_SHORTCUT + putExtra(EXTRA_ROOM_ID, roomId) + } + } } } From 957d51cf3fb1bf9a0d7f313902fd512a463a038d Mon Sep 17 00:00:00 2001 From: Emma Vanbrabant Date: Sat, 9 May 2020 20:46:37 +0100 Subject: [PATCH 17/36] Fix placeholder icons --- .../riotx/features/home/AvatarRenderer.kt | 20 ++++----- .../riotx/features/home/ShortcutsHandler.kt | 42 +++++++------------ 2 files changed, 25 insertions(+), 37 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/AvatarRenderer.kt b/vector/src/main/java/im/vector/riotx/features/home/AvatarRenderer.kt index fb0bd121f1..2edb2c4edf 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/AvatarRenderer.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/AvatarRenderer.kt @@ -17,11 +17,13 @@ package im.vector.riotx.features.home import android.content.Context +import android.graphics.Bitmap import android.graphics.drawable.Drawable import android.widget.ImageView import androidx.annotation.AnyThread import androidx.annotation.UiThread import androidx.core.content.ContextCompat +import androidx.core.graphics.drawable.toBitmap import com.amulyakhare.textdrawable.TextDrawable import com.bumptech.glide.request.RequestOptions import com.bumptech.glide.request.target.DrawableImageViewTarget @@ -73,9 +75,9 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active } @AnyThread - fun shortcutDrawable(context: Context, glideRequest: GlideRequests, matrixItem: MatrixItem): Drawable { + fun shortcutDrawable(context: Context, glideRequest: GlideRequests, matrixItem: MatrixItem, iconSize: Int): Bitmap { return glideRequest - .asDrawable() + .asBitmap() .apply { val resolvedUrl = resolvedUrl(matrixItem.avatarUrl) if (resolvedUrl != null) { @@ -86,10 +88,11 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active .beginConfig() .bold() .endConfig() - .buildRect(matrixItem.firstLetterOfDisplayName(), avatarColor)) + .buildRect(matrixItem.firstLetterOfDisplayName(), avatarColor) + .toBitmap(width = iconSize, height = iconSize)) } } - .submit() + .submit(iconSize, iconSize) .get() } @@ -103,10 +106,7 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active @AnyThread fun getPlaceholderDrawable(context: Context, matrixItem: MatrixItem): Drawable { - val avatarColor = when (matrixItem) { - is MatrixItem.UserItem -> ContextCompat.getColor(context, getColorFromUserId(matrixItem.id)) - else -> ContextCompat.getColor(context, getColorFromRoomId(matrixItem.id)) - } + val avatarColor = avatarColor(matrixItem, context) return TextDrawable.builder() .beginConfig() .bold() @@ -117,9 +117,7 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active // PRIVATE API ********************************************************************************* private fun buildGlideRequest(glideRequest: GlideRequests, avatarUrl: String?): GlideRequest { - val resolvedUrl = activeSessionHolder.getSafeActiveSession()?.contentUrlResolver() - ?.resolveThumbnail(avatarUrl, THUMBNAIL_SIZE, THUMBNAIL_SIZE, ContentUrlResolver.ThumbnailMethod.SCALE) - + val resolvedUrl = resolvedUrl(avatarUrl) return glideRequest .load(resolvedUrl) .apply(RequestOptions.circleCropTransform()) diff --git a/vector/src/main/java/im/vector/riotx/features/home/ShortcutsHandler.kt b/vector/src/main/java/im/vector/riotx/features/home/ShortcutsHandler.kt index d59e84ffa6..1211389a12 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/ShortcutsHandler.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/ShortcutsHandler.kt @@ -19,13 +19,10 @@ package im.vector.riotx.features.home import android.annotation.SuppressLint import android.content.Context import android.graphics.Bitmap -import android.graphics.Canvas -import android.graphics.drawable.Drawable import android.os.Build import androidx.core.content.pm.ShortcutInfoCompat import androidx.core.content.pm.ShortcutManagerCompat import androidx.core.graphics.drawable.IconCompat -import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.tag.RoomTag import im.vector.matrix.android.api.util.toMatrixItem import im.vector.riotx.core.glide.GlideApp @@ -51,14 +48,24 @@ class ShortcutsHandler @Inject constructor( .observeOn(Schedulers.computation()) .subscribe { rooms -> val shortcuts = rooms - .favoriteRooms() + .filter { room -> room.tags.any { it.name == RoomTag.ROOM_TAG_FAVOURITE } } + .take(n = 4) // Android only allows us to create 4 shortcuts .map { room -> val intent = RoomDetailActivity.shortcutIntent(context, room.roomId) - val drawable = avatarRenderer.shortcutDrawable(context, GlideApp.with(context), room.toMatrixItem()) + + val adaptiveIconSize = dimensionConverter.dpToPx(adaptiveIconSizeDp) + val adaptiveIconOuterSides = dimensionConverter.dpToPx(adaptiveIconOuterSidesDp) + val size = if (useAdaptiveIcon) { + adaptiveIconSize - adaptiveIconOuterSides + } else { + dimensionConverter.dpToPx(72) + } + + val bitmap = avatarRenderer.shortcutDrawable(context, GlideApp.with(context), room.toMatrixItem(), size) ShortcutInfoCompat.Builder(context, room.roomId) .setShortLabel(room.displayName) - .setIcon(drawable.toProfileImageIcon()) + .setIcon(bitmap.toProfileImageIcon()) .setIntent(intent) .build() } @@ -70,28 +77,11 @@ class ShortcutsHandler @Inject constructor( // PRIVATE API ********************************************************************************* - private fun List.favoriteRooms(): List { - return filter { room -> room.tags.any { it.name == RoomTag.ROOM_TAG_FAVOURITE } } - .take(n = 4) // Android only allows us to create 4 shortcuts - } - - private fun Drawable.toProfileImageIcon(): IconCompat { - val adaptiveIconSize = dimensionConverter.dpToPx(adaptiveIconSizeDp) - val adaptiveIconOuterSides = dimensionConverter.dpToPx(adaptiveIconOuterSidesDp) - - val bitmap = Bitmap.createBitmap(adaptiveIconSize, adaptiveIconSize, Bitmap.Config.ARGB_8888) - val canvas = Canvas(bitmap) - + private fun Bitmap.toProfileImageIcon(): IconCompat { return if (useAdaptiveIcon) { - setBounds(adaptiveIconOuterSides, adaptiveIconOuterSides, adaptiveIconSize - adaptiveIconOuterSides, adaptiveIconSize - adaptiveIconOuterSides) - draw(canvas) - - IconCompat.createWithAdaptiveBitmap(bitmap) + IconCompat.createWithAdaptiveBitmap(this) } else { - setBounds(0, 0, bitmap.width, bitmap.height) - draw(canvas) - - IconCompat.createWithBitmap(bitmap) + IconCompat.createWithBitmap(this) } } } From 92c9d4fc22e476defef1b5c70ad59f9be8534454 Mon Sep 17 00:00:00 2001 From: Emma Vanbrabant Date: Sat, 9 May 2020 20:52:30 +0100 Subject: [PATCH 18/36] remove unused import Signed-off-by: Emma Vanbrabant --- .../java/im/vector/riotx/features/home/HomeDetailViewModel.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeDetailViewModel.kt index e91de984b8..3824ba7922 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/HomeDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/HomeDetailViewModel.kt @@ -16,7 +16,6 @@ package im.vector.riotx.features.home -import androidx.core.content.pm.ShortcutInfoCompat import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.ViewModelContext From f568553d21791cb68a3edf902bb6016159089454 Mon Sep 17 00:00:00 2001 From: Emma Vanbrabant Date: Sat, 9 May 2020 21:07:03 +0100 Subject: [PATCH 19/36] Add to changes --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 8bdd42636d..be546a1f36 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,7 +2,7 @@ Changes in RiotX 0.20.0 (2020-XX-XX) =================================================== Features ✨: - - + - Add Direct Shortcuts Improvements 🙌: - From fe013f803e3c44f080922ab29d81f98e0b4fdf1a Mon Sep 17 00:00:00 2001 From: onurays Date: Mon, 11 May 2020 22:43:55 +0300 Subject: [PATCH 20/36] 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 21/36] 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 22/36] 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 23/36] 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. From d08b4e1ea092b1c313776a1c9ece8b4ec26b37a8 Mon Sep 17 00:00:00 2001 From: Emma Vanbrabant Date: Tue, 12 May 2020 19:37:03 +0100 Subject: [PATCH 24/36] PR feedback --- CHANGES.md | 2 +- .../riotx/features/home/HomeActivity.kt | 3 +- .../riotx/features/home/ShortcutsHandler.kt | 29 ++++++++++--------- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index be546a1f36..1aa0c8d7ca 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,7 +2,7 @@ Changes in RiotX 0.20.0 (2020-XX-XX) =================================================== Features ✨: - - Add Direct Shortcuts + - Add Direct Shortcuts (#652) Improvements 🙌: - diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeActivity.kt index 839763ffc0..b6e3cbcd76 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/HomeActivity.kt @@ -146,7 +146,8 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable { promptCompleteSecurityIfNeeded() } - shortcutsHandler.observeRoomsAndBuildShortcuts(context = this) + shortcutsHandler.observeRoomsAndBuildShortcuts() + .disposeOnDestroy() } private fun promptCompleteSecurityIfNeeded() { diff --git a/vector/src/main/java/im/vector/riotx/features/home/ShortcutsHandler.kt b/vector/src/main/java/im/vector/riotx/features/home/ShortcutsHandler.kt index 1211389a12..657942457e 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/ShortcutsHandler.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/ShortcutsHandler.kt @@ -16,7 +16,6 @@ package im.vector.riotx.features.home -import android.annotation.SuppressLint import android.content.Context import android.graphics.Bitmap import android.os.Build @@ -28,6 +27,7 @@ import im.vector.matrix.android.api.util.toMatrixItem import im.vector.riotx.core.glide.GlideApp import im.vector.riotx.core.utils.DimensionConverter import im.vector.riotx.features.home.room.detail.RoomDetailActivity +import io.reactivex.disposables.Disposable import io.reactivex.schedulers.Schedulers import javax.inject.Inject @@ -36,15 +36,25 @@ private const val adaptiveIconSizeDp = 108 private const val adaptiveIconOuterSidesDp = 18 class ShortcutsHandler @Inject constructor( + private val context: Context, private val homeRoomListStore: HomeRoomListDataSource, private val avatarRenderer: AvatarRenderer, private val dimensionConverter: DimensionConverter ) { + private val adaptiveIconSize = dimensionConverter.dpToPx(adaptiveIconSizeDp) + private val adaptiveIconOuterSides = dimensionConverter.dpToPx(adaptiveIconOuterSidesDp) + private val iconSize by lazy { + if (useAdaptiveIcon) { + adaptiveIconSize - adaptiveIconOuterSides + } else { + dimensionConverter.dpToPx(72) + } + } - @SuppressLint("CheckResult") - fun observeRoomsAndBuildShortcuts(context: Context) { - homeRoomListStore + fun observeRoomsAndBuildShortcuts(): Disposable { + return homeRoomListStore .observe() + .distinct() .observeOn(Schedulers.computation()) .subscribe { rooms -> val shortcuts = rooms @@ -52,16 +62,7 @@ class ShortcutsHandler @Inject constructor( .take(n = 4) // Android only allows us to create 4 shortcuts .map { room -> val intent = RoomDetailActivity.shortcutIntent(context, room.roomId) - - val adaptiveIconSize = dimensionConverter.dpToPx(adaptiveIconSizeDp) - val adaptiveIconOuterSides = dimensionConverter.dpToPx(adaptiveIconOuterSidesDp) - val size = if (useAdaptiveIcon) { - adaptiveIconSize - adaptiveIconOuterSides - } else { - dimensionConverter.dpToPx(72) - } - - val bitmap = avatarRenderer.shortcutDrawable(context, GlideApp.with(context), room.toMatrixItem(), size) + val bitmap = avatarRenderer.shortcutDrawable(context, GlideApp.with(context), room.toMatrixItem(), iconSize) ShortcutInfoCompat.Builder(context, room.roomId) .setShortLabel(room.displayName) From 037b2e1d6071fb5109e3944642f8ce07a8fa5c9d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 13 May 2020 00:34:03 +0200 Subject: [PATCH 25/36] PR merged after a release, move 2 lines --- CHANGES.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 835d449de1..f7f471a969 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,7 @@ Features ✨: - Add Direct Shortcuts (#652) Improvements 🙌: + - - Invite member(s) to an existing room (#1276) - Improve notification accessibility with ticker text (#1226) Bugfix 🐛: @@ -14,7 +15,7 @@ Translations 🗣: - SDK API changes ⚠️: - - + - excludedUserIds parameter add to to UserService.getPagedUsersLive() function Build 🧱: - @@ -52,7 +53,6 @@ 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 - E2E timeline decoration (#1279) - Manage Session Settings / Cross Signing update (#1295) - Cross-Signing | Review sessions toast update old vs new (#1293, #1306) @@ -80,7 +80,6 @@ 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 63c18e82c8edb9b814642cffee0f36c34022a25e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 13 May 2020 00:34:33 +0200 Subject: [PATCH 26/36] typo... --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index f7f471a969..52896df63d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,7 +5,7 @@ Features ✨: - Add Direct Shortcuts (#652) Improvements 🙌: - - - Invite member(s) to an existing room (#1276) + - Invite member(s) to an existing room (#1276) - Improve notification accessibility with ticker text (#1226) Bugfix 🐛: From c9bc6f4a9e38b75057c3aa8e85017fee3d6508af Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 2 Mar 2020 17:34:31 +0100 Subject: [PATCH 27/36] Support homeserver discovery from MXID - Wellknown (#476) --- CHANGES.md | 1 + .../android/api/auth/AuthenticationService.kt | 18 +- .../matrix/android/api/auth/data/WellKnown.kt | 3 +- .../api/auth/data/WellKnownManagerConfig.kt | 2 +- .../api/auth/wellknown/WellknownResult.kt | 55 +++++ .../android/internal/auth/AuthModule.kt | 18 +- .../auth/DefaultAuthenticationService.kt | 30 ++- .../auth/wellknown/DirectLoginTask.kt | 61 ++++++ .../auth/wellknown/GetWellknownTask.kt | 199 ++++++++++++++++++ .../internal/auth/wellknown/WellKnownAPI.kt | 26 +++ .../internal/identity/IdentityPingApi.kt | 34 +++ .../internal/network/NetworkConstants.kt | 6 + .../internal/session/user/SearchUserAPI.kt | 4 +- .../matrix/android/internal/util/UrlUtils.kt | 28 +++ .../riotx/features/login/LoginActivity.kt | 19 +- .../riotx/features/login/LoginFragment.kt | 76 ++++--- .../login/LoginServerSelectionFragment.kt | 6 + .../riotx/features/login/LoginViewModel.kt | 93 +++++++- .../vector/riotx/features/login/SignMode.kt | 4 +- .../fragment_login_server_selection.xml | 25 ++- vector/src/main/res/values/strings_riotX.xml | 8 + 21 files changed, 653 insertions(+), 63 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/wellknown/WellknownResult.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/wellknown/DirectLoginTask.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/wellknown/GetWellknownTask.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/wellknown/WellKnownAPI.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/identity/IdentityPingApi.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/UrlUtils.kt diff --git a/CHANGES.md b/CHANGES.md index 52896df63d..2b36909c33 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,6 +7,7 @@ Features ✨: Improvements 🙌: - Invite member(s) to an existing room (#1276) - Improve notification accessibility with ticker text (#1226) + - Support homeserver discovery from MXID (#476) Bugfix 🐛: - Sometimes the same device appears twice in the list of devices of a user (#1329) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/AuthenticationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/AuthenticationService.kt index 140d1c259f..5150420de2 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/AuthenticationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/AuthenticationService.kt @@ -23,6 +23,7 @@ import im.vector.matrix.android.api.auth.data.LoginFlowResult import im.vector.matrix.android.api.auth.data.SessionParams import im.vector.matrix.android.api.auth.login.LoginWizard import im.vector.matrix.android.api.auth.registration.RegistrationWizard +import im.vector.matrix.android.api.auth.wellknown.WellknownResult import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.util.Cancelable @@ -30,7 +31,6 @@ import im.vector.matrix.android.api.util.Cancelable * This interface defines methods to authenticate or to create an account to a matrix server. */ interface AuthenticationService { - /** * Request the supported login flows for this homeserver. * This is the first method to call to be able to get a wizard to login or the create an account @@ -89,4 +89,20 @@ interface AuthenticationService { fun createSessionFromSso(homeServerConnectionConfig: HomeServerConnectionConfig, credentials: Credentials, callback: MatrixCallback): Cancelable + + /** + * Perform a wellknown request, using the domain from the matrixId + */ + fun getWellKnownData(matrixId: String, + callback: MatrixCallback): Cancelable + + /** + * Authenticate with a matrixId and a password + * Usually call this after a successful call to getWellKnownData() + */ + fun directAuthentication(homeServerConnectionConfig: HomeServerConnectionConfig, + matrixId: String, + password: String, + initialDeviceName: String, + callback: MatrixCallback): Cancelable } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/WellKnown.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/WellKnown.kt index bdad4702b7..9dd1fa2012 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/WellKnown.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/WellKnown.kt @@ -18,6 +18,7 @@ package im.vector.matrix.android.api.auth.data import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import im.vector.matrix.android.api.util.JsonDict /** * https://matrix.org/docs/spec/client_server/r0.4.0.html#server-discovery @@ -52,7 +53,7 @@ data class WellKnown( val identityServer: WellKnownBaseConfig? = null, @Json(name = "m.integrations") - val integrations: Map? = null + val integrations: JsonDict? = null ) { /** * Returns the list of integration managers proposed diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/WellKnownManagerConfig.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/WellKnownManagerConfig.kt index 33ed412a2a..ffdea37afe 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/WellKnownManagerConfig.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/WellKnownManagerConfig.kt @@ -16,6 +16,6 @@ package im.vector.matrix.android.api.auth.data data class WellKnownManagerConfig( - val apiUrl : String, + val apiUrl: String, val uiUrl: String ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/wellknown/WellknownResult.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/wellknown/WellknownResult.kt new file mode 100644 index 0000000000..974a65adbc --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/wellknown/WellknownResult.kt @@ -0,0 +1,55 @@ +/* + * 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.matrix.android.api.auth.wellknown + +import im.vector.matrix.android.api.auth.data.WellKnown + +/** + * Ref: https://matrix.org/docs/spec/client_server/latest#well-known-uri + */ +sealed class WellknownResult { + /** + * The provided matrixId is no valid. Unable to extract a domain name. + */ + object InvalidMatrixId : WellknownResult() + + /** + * Retrieve the specific piece of information from the user in a way which fits within the existing client user experience, + * if the client is inclined to do so. Failure can take place instead if no good user experience for this is possible at this point. + */ + data class Prompt(val homerServerUrl: String, + val identityServerUrl: String?, + val wellKnown: WellKnown) : WellknownResult() + + /** + * Stop the current auto-discovery mechanism. If no more auto-discovery mechanisms are available, + * then the client may use other methods of determining the required parameters, such as prompting the user, or using default values. + */ + object Ignore : WellknownResult() + + /** + * Inform the user that auto-discovery failed due to invalid/empty data and PROMPT for the parameter. + */ + object FailPrompt : WellknownResult() + + /** + * Inform the user that auto-discovery did not return any usable URLs. Do not continue further with the current login process. + * At this point, valid data was obtained, but no homeserver is available to serve the client. + * No further guess should be attempted and the user should make a conscientious decision what to do next. + */ + object FailError : WellknownResult() +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/AuthModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/AuthModule.kt index 6b6321de36..232cb3f541 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/AuthModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/AuthModule.kt @@ -25,6 +25,10 @@ import im.vector.matrix.android.internal.auth.db.AuthRealmMigration import im.vector.matrix.android.internal.auth.db.AuthRealmModule import im.vector.matrix.android.internal.auth.db.RealmPendingSessionStore import im.vector.matrix.android.internal.auth.db.RealmSessionParamsStore +import im.vector.matrix.android.internal.auth.wellknown.DefaultDirectLoginTask +import im.vector.matrix.android.internal.auth.wellknown.DefaultGetWellknownTask +import im.vector.matrix.android.internal.auth.wellknown.DirectLoginTask +import im.vector.matrix.android.internal.auth.wellknown.GetWellknownTask import im.vector.matrix.android.internal.database.RealmKeysUtils import im.vector.matrix.android.internal.di.AuthDatabase import io.realm.RealmConfiguration @@ -59,14 +63,20 @@ internal abstract class AuthModule { } @Binds - abstract fun bindSessionParamsStore(sessionParamsStore: RealmSessionParamsStore): SessionParamsStore + abstract fun bindSessionParamsStore(store: RealmSessionParamsStore): SessionParamsStore @Binds - abstract fun bindPendingSessionStore(pendingSessionStore: RealmPendingSessionStore): PendingSessionStore + abstract fun bindPendingSessionStore(store: RealmPendingSessionStore): PendingSessionStore @Binds - abstract fun bindAuthenticationService(authenticationService: DefaultAuthenticationService): AuthenticationService + abstract fun bindAuthenticationService(service: DefaultAuthenticationService): AuthenticationService @Binds - abstract fun bindSessionCreator(sessionCreator: DefaultSessionCreator): SessionCreator + abstract fun bindSessionCreator(creator: DefaultSessionCreator): SessionCreator + + @Binds + abstract fun bindGetWellknownTask(task: DefaultGetWellknownTask): GetWellknownTask + + @Binds + abstract fun bindDirectLoginTask(task: DefaultDirectLoginTask): DirectLoginTask } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticationService.kt index 85c2cdbf3d..b2c2dc12a5 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticationService.kt @@ -29,6 +29,7 @@ import im.vector.matrix.android.api.auth.data.isLoginAndRegistrationSupportedByS import im.vector.matrix.android.api.auth.data.isSupportedBySdk import im.vector.matrix.android.api.auth.login.LoginWizard import im.vector.matrix.android.api.auth.registration.RegistrationWizard +import im.vector.matrix.android.api.auth.wellknown.WellknownResult import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.util.Cancelable @@ -38,9 +39,13 @@ import im.vector.matrix.android.internal.auth.data.RiotConfig import im.vector.matrix.android.internal.auth.db.PendingSessionData import im.vector.matrix.android.internal.auth.login.DefaultLoginWizard import im.vector.matrix.android.internal.auth.registration.DefaultRegistrationWizard +import im.vector.matrix.android.internal.auth.wellknown.DirectLoginTask +import im.vector.matrix.android.internal.auth.wellknown.GetWellknownTask import im.vector.matrix.android.internal.di.Unauthenticated import im.vector.matrix.android.internal.network.RetrofitFactory import im.vector.matrix.android.internal.network.executeRequest +import im.vector.matrix.android.internal.task.TaskExecutor +import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.task.launchToCallback import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.toCancelable @@ -59,7 +64,10 @@ internal class DefaultAuthenticationService @Inject constructor( private val sessionParamsStore: SessionParamsStore, private val sessionManager: SessionManager, private val sessionCreator: SessionCreator, - private val pendingSessionStore: PendingSessionStore + private val pendingSessionStore: PendingSessionStore, + private val getWellknownTask: GetWellknownTask, + private val directLoginTask: DirectLoginTask, + private val taskExecutor: TaskExecutor ) : AuthenticationService { private var pendingSessionData: PendingSessionData? = pendingSessionStore.getPendingSessionData() @@ -260,6 +268,26 @@ internal class DefaultAuthenticationService @Inject constructor( } } + override fun getWellKnownData(matrixId: String, callback: MatrixCallback): Cancelable { + return getWellknownTask + .configureWith(GetWellknownTask.Params(matrixId)) { + this.callback = callback + } + .executeBy(taskExecutor) + } + + override fun directAuthentication(homeServerConnectionConfig: HomeServerConnectionConfig, + matrixId: String, + password: String, + initialDeviceName: String, + callback: MatrixCallback): Cancelable { + return directLoginTask + .configureWith(DirectLoginTask.Params(homeServerConnectionConfig, matrixId, password, initialDeviceName)) { + this.callback = callback + } + .executeBy(taskExecutor) + } + private suspend fun createSessionFromSso(credentials: Credentials, homeServerConnectionConfig: HomeServerConnectionConfig): Session = withContext(coroutineDispatchers.computation) { sessionCreator.createSession(credentials, homeServerConnectionConfig) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/wellknown/DirectLoginTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/wellknown/DirectLoginTask.kt new file mode 100644 index 0000000000..01a3ab192d --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/wellknown/DirectLoginTask.kt @@ -0,0 +1,61 @@ +/* + * 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.matrix.android.internal.auth.wellknown + +import dagger.Lazy +import im.vector.matrix.android.api.auth.data.Credentials +import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig +import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.internal.auth.AuthAPI +import im.vector.matrix.android.internal.auth.SessionCreator +import im.vector.matrix.android.internal.auth.data.PasswordLoginParams +import im.vector.matrix.android.internal.di.Unauthenticated +import im.vector.matrix.android.internal.network.RetrofitFactory +import im.vector.matrix.android.internal.network.executeRequest +import im.vector.matrix.android.internal.task.Task +import okhttp3.OkHttpClient +import javax.inject.Inject + +internal interface DirectLoginTask : Task { + data class Params( + val homeServerConnectionConfig: HomeServerConnectionConfig, + val userId: String, + val password: String, + val deviceName: String + ) +} + +internal class DefaultDirectLoginTask @Inject constructor( + @Unauthenticated + private val okHttpClient: Lazy, + private val retrofitFactory: RetrofitFactory, + private val sessionCreator: SessionCreator +) : DirectLoginTask { + + override suspend fun execute(params: DirectLoginTask.Params): Session { + val authAPI = retrofitFactory.create(okHttpClient, params.homeServerConnectionConfig.homeServerUri.toString()) + .create(AuthAPI::class.java) + + val loginParams = PasswordLoginParams.userIdentifier(params.userId, params.password, params.deviceName) + + val credentials = executeRequest(null) { + apiCall = authAPI.login(loginParams) + } + + return sessionCreator.createSession(credentials, params.homeServerConnectionConfig) + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/wellknown/GetWellknownTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/wellknown/GetWellknownTask.kt new file mode 100644 index 0000000000..8ed2cb3b0f --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/wellknown/GetWellknownTask.kt @@ -0,0 +1,199 @@ +/* + * 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.matrix.android.internal.auth.wellknown + +import android.util.MalformedJsonException +import dagger.Lazy +import im.vector.matrix.android.api.MatrixPatterns +import im.vector.matrix.android.api.auth.data.WellKnown +import im.vector.matrix.android.api.auth.wellknown.WellknownResult +import im.vector.matrix.android.api.failure.Failure +import im.vector.matrix.android.internal.di.Unauthenticated +import im.vector.matrix.android.internal.identity.IdentityPingApi +import im.vector.matrix.android.internal.network.RetrofitFactory +import im.vector.matrix.android.internal.network.executeRequest +import im.vector.matrix.android.internal.session.homeserver.CapabilitiesAPI +import im.vector.matrix.android.internal.task.Task +import im.vector.matrix.android.internal.util.isValidUrl +import okhttp3.OkHttpClient +import java.io.EOFException +import javax.inject.Inject +import javax.net.ssl.HttpsURLConnection + +internal interface GetWellknownTask : Task { + data class Params( + val matrixId: String + ) +} + +/** + * Inspired from AutoDiscovery class from legacy Matrix Android SDK + */ +internal class DefaultGetWellknownTask @Inject constructor( + @Unauthenticated + private val okHttpClient: Lazy, + private val retrofitFactory: RetrofitFactory +) : GetWellknownTask { + + override suspend fun execute(params: GetWellknownTask.Params): WellknownResult { + if (!MatrixPatterns.isUserId(params.matrixId)) { + return WellknownResult.InvalidMatrixId + } + + val homeServerDomain = params.matrixId.substringAfter(":") + + return findClientConfig(homeServerDomain) + } + + /** + * Find client config + * + * - Do the .well-known request + * - validate homeserver url and identity server url if provide in .well-known result + * - return action and .well-known data + * + * @param domain: homeserver domain, deduced from mx userId (ex: "matrix.org" from userId "@user:matrix.org") + */ + private suspend fun findClientConfig(domain: String): WellknownResult { + val wellKnownAPI = retrofitFactory.create(okHttpClient, "https://dummy.org") + .create(WellKnownAPI::class.java) + + return try { + val wellKnown = executeRequest(null) { + apiCall = wellKnownAPI.getWellKnown(domain) + } + + // Success + val homeServerBaseUrl = wellKnown.homeServer?.baseURL + if (homeServerBaseUrl.isNullOrBlank()) { + WellknownResult.FailPrompt + } else { + if (homeServerBaseUrl.isValidUrl()) { + // Check that HS is a real one + validateHomeServer(homeServerBaseUrl, wellKnown) + } else { + WellknownResult.FailError + } + } + } catch (throwable: Throwable) { + when (throwable) { + is Failure.NetworkConnection -> { + WellknownResult.Ignore + } + is Failure.OtherServerError -> { + when (throwable.httpCode) { + HttpsURLConnection.HTTP_NOT_FOUND -> WellknownResult.Ignore + else -> WellknownResult.FailPrompt + } + } + is MalformedJsonException, is EOFException -> { + WellknownResult.FailPrompt + } + else -> { + throw throwable + } + } + } + } + + /** + * Return true if home server is valid, and (if applicable) if identity server is pingable + */ + private suspend fun validateHomeServer(homeServerBaseUrl: String, wellKnown: WellKnown): WellknownResult { + val capabilitiesAPI = retrofitFactory.create(okHttpClient, homeServerBaseUrl) + .create(CapabilitiesAPI::class.java) + + try { + executeRequest(null) { + apiCall = capabilitiesAPI.getVersions() + } + } catch (throwable: Throwable) { + return WellknownResult.FailError + } + + return if (wellKnown.identityServer == null) { + // No identity server + WellknownResult.Prompt(homeServerBaseUrl, null, wellKnown) + } else { + // if m.identity_server is present it must be valid + val identityServerBaseUrl = wellKnown.identityServer.baseURL + if (identityServerBaseUrl.isNullOrBlank()) { + WellknownResult.FailError + } else { + if (identityServerBaseUrl.isValidUrl()) { + if (validateIdentityServer(identityServerBaseUrl)) { + // All is ok + WellknownResult.Prompt(homeServerBaseUrl, identityServerBaseUrl, wellKnown) + } else { + WellknownResult.FailError + } + } else { + WellknownResult.FailError + } + } + } + } + + /** + * Return true if identity server is pingable + */ + private suspend fun validateIdentityServer(identityServerBaseUrl: String): Boolean { + val identityPingApi = retrofitFactory.create(okHttpClient, identityServerBaseUrl) + .create(IdentityPingApi::class.java) + + return try { + executeRequest(null) { + apiCall = identityPingApi.ping() + } + + true + } catch (throwable: Throwable) { + false + } + } + + /** + * Try to get an identity server URL from a home server URL, using a .wellknown request + */ + /* + fun getIdentityServer(homeServerUrl: String, callback: ApiCallback) { + if (homeServerUrl.startsWith("https://")) { + wellKnownRestClient.getWellKnown(homeServerUrl.substring("https://".length), + object : SimpleApiCallback(callback) { + override fun onSuccess(info: WellKnown) { + callback.onSuccess(info.identityServer?.baseURL) + } + }) + } else { + callback.onUnexpectedError(InvalidParameterException("malformed url")) + } + } + + fun getServerPreferredIntegrationManagers(homeServerUrl: String, callback: ApiCallback>) { + if (homeServerUrl.startsWith("https://")) { + wellKnownRestClient.getWellKnown(homeServerUrl.substring("https://".length), + object : SimpleApiCallback(callback) { + override fun onSuccess(info: WellKnown) { + callback.onSuccess(info.getIntegrationManagers()) + } + }) + } else { + callback.onUnexpectedError(InvalidParameterException("malformed url")) + } + } + */ +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/wellknown/WellKnownAPI.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/wellknown/WellKnownAPI.kt new file mode 100644 index 0000000000..71928123bf --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/wellknown/WellKnownAPI.kt @@ -0,0 +1,26 @@ +/* + * 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.matrix.android.internal.auth.wellknown + +import im.vector.matrix.android.api.auth.data.WellKnown +import retrofit2.Call +import retrofit2.http.GET +import retrofit2.http.Path + +internal interface WellKnownAPI { + @GET("https://{domain}/.well-known/matrix/client") + fun getWellKnown(@Path("domain") domain: String): Call +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/identity/IdentityPingApi.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/identity/IdentityPingApi.kt new file mode 100644 index 0000000000..2a0e00704c --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/identity/IdentityPingApi.kt @@ -0,0 +1,34 @@ +/* + * 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.matrix.android.internal.identity + +import im.vector.matrix.android.internal.network.NetworkConstants +import retrofit2.Call +import retrofit2.http.GET + +internal interface IdentityPingApi { + + /** + * https://matrix.org/docs/spec/client_server/r0.4.0.html#server-discovery + * Simple ping call to check if server alive + * + * Ref: https://matrix.org/docs/spec/identity_service/unstable#status-check + * + * @return 200 in case of success + */ + @GET(NetworkConstants.URI_API_PREFIX_IDENTITY) + fun ping(): Call +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/NetworkConstants.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/NetworkConstants.kt index c6c10d9a8f..ab6745148f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/NetworkConstants.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/NetworkConstants.kt @@ -26,4 +26,10 @@ internal object NetworkConstants { // Media private const val URI_API_MEDIA_PREFIX_PATH = "_matrix/media" const val URI_API_MEDIA_PREFIX_PATH_R0 = "$URI_API_MEDIA_PREFIX_PATH/r0/" + + // Identity server + const val URI_IDENTITY_PATH = "_matrix/identity/api/v1/" + const val URI_IDENTITY_PATH_V2 = "_matrix/identity/v2/" + + const val URI_API_PREFIX_IDENTITY = "_matrix/identity/api/v1" } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/SearchUserAPI.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/SearchUserAPI.kt index e57daed617..4adcee88aa 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/SearchUserAPI.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/SearchUserAPI.kt @@ -16,7 +16,7 @@ package im.vector.matrix.android.internal.session.user -import im.vector.matrix.android.internal.network.NetworkConstants.URI_API_PREFIX_PATH_R0 +import im.vector.matrix.android.internal.network.NetworkConstants import im.vector.matrix.android.internal.session.user.model.SearchUsersParams import im.vector.matrix.android.internal.session.user.model.SearchUsersResponse import retrofit2.Call @@ -30,6 +30,6 @@ internal interface SearchUserAPI { * * @param searchUsersParams the search params. */ - @POST(URI_API_PREFIX_PATH_R0 + "user_directory/search") + @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user_directory/search") fun searchUsers(@Body searchUsersParams: SearchUsersParams): Call } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/UrlUtils.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/UrlUtils.kt new file mode 100644 index 0000000000..8ad5e89605 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/UrlUtils.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.matrix.android.internal.util + +import java.net.URL + +internal fun String.isValidUrl(): Boolean { + return try { + URL(this) + true + } catch (t: Throwable) { + false + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginActivity.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginActivity.kt index c67e45d9e7..cd8c1df46e 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginActivity.kt @@ -38,6 +38,7 @@ import im.vector.riotx.core.di.ScreenComponent import im.vector.riotx.core.extensions.POP_BACK_STACK_EXCLUSIVE import im.vector.riotx.core.extensions.addFragment import im.vector.riotx.core.extensions.addFragmentToBackstack +import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.platform.ToolbarConfigurable import im.vector.riotx.core.platform.VectorBaseActivity import im.vector.riotx.features.home.HomeActivity @@ -125,9 +126,7 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable { } private fun handleLoginNavigation(loginNavigation: LoginNavigation) { - // Assigning to dummy make sure we do not forget a case - @Suppress("UNUSED_VARIABLE") - val dummy = when (loginNavigation) { + when (loginNavigation) { is LoginNavigation.OpenServerSelection -> addFragmentToBackstack(R.id.loginFragmentContainer, LoginServerSelectionFragment::class.java, @@ -177,7 +176,7 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable { LoginGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.ConfirmMsisdn, true, loginNavigation.msisdn), tag = FRAGMENT_REGISTRATION_STAGE_TAG, option = commonOption) - } + }.exhaustive } private fun handleLoginViewEvents(loginViewEvents: LoginViewEvents) { @@ -254,11 +253,11 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable { private fun onSignModeSelected() = withState(loginViewModel) { state -> when (state.signMode) { - SignMode.Unknown -> error("Sign mode has to be set before calling this method") - SignMode.SignUp -> { + SignMode.Unknown -> error("Sign mode has to be set before calling this method") + SignMode.SignUp -> { // This is managed by the LoginViewEvents } - SignMode.SignIn -> { + SignMode.SignIn -> { // It depends on the LoginMode when (state.loginMode) { LoginMode.Unknown -> error("Developer error") @@ -272,7 +271,11 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable { LoginMode.Unsupported -> onLoginModeNotSupported(state.loginModeSupportedTypes) } } - } + SignMode.SignInWithMatrixId -> addFragmentToBackstack(R.id.loginFragmentContainer, + LoginFragment::class.java, + tag = FRAGMENT_LOGIN_TAG, + option = commonOption) + }.exhaustive } private fun onRegistrationStageNotSupported() { diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginFragment.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginFragment.kt index 8b89aeda2a..371f669736 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginFragment.kt @@ -31,6 +31,7 @@ import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.failure.MatrixError import im.vector.matrix.android.api.failure.isInvalidPassword import im.vector.riotx.R +import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.extensions.hideKeyboard import im.vector.riotx.core.extensions.showPassword import im.vector.riotx.core.extensions.toReducedUrl @@ -73,16 +74,17 @@ class LoginFragment @Inject constructor() : AbstractLoginFragment() { private fun setupAutoFill(state: LoginViewState) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { when (state.signMode) { - SignMode.Unknown -> error("developer error") - SignMode.SignUp -> { + SignMode.Unknown -> error("developer error") + SignMode.SignUp -> { loginField.setAutofillHints(HintConstants.AUTOFILL_HINT_NEW_USERNAME) passwordField.setAutofillHints(HintConstants.AUTOFILL_HINT_NEW_PASSWORD) } - SignMode.SignIn -> { + SignMode.SignIn, + SignMode.SignInWithMatrixId -> { loginField.setAutofillHints(HintConstants.AUTOFILL_HINT_USERNAME) passwordField.setAutofillHints(HintConstants.AUTOFILL_HINT_PASSWORD) } - } + }.exhaustive } } @@ -116,35 +118,44 @@ class LoginFragment @Inject constructor() : AbstractLoginFragment() { } private fun setupUi(state: LoginViewState) { - val resId = when (state.signMode) { - SignMode.Unknown -> error("developer error") - SignMode.SignUp -> R.string.login_signup_to - SignMode.SignIn -> R.string.login_connect_to - } - loginFieldTil.hint = getString(when (state.signMode) { - SignMode.Unknown -> error("developer error") - SignMode.SignUp -> R.string.login_signup_username_hint - SignMode.SignIn -> R.string.login_signin_username_hint + SignMode.Unknown -> error("developer error") + SignMode.SignUp -> R.string.login_signup_username_hint + SignMode.SignIn -> R.string.login_signin_username_hint + SignMode.SignInWithMatrixId -> R.string.login_signin_matrix_id_hint }) - when (state.serverType) { - ServerType.MatrixOrg -> { - loginServerIcon.isVisible = true - loginServerIcon.setImageResource(R.drawable.ic_logo_matrix_org) - loginTitle.text = getString(resId, state.homeServerUrl.toReducedUrl()) - loginNotice.text = getString(R.string.login_server_matrix_org_text) + // Handle direct signin first + if (state.signMode == SignMode.SignInWithMatrixId) { + loginServerIcon.isVisible = false + loginTitle.text = getString(R.string.login_signin_matrix_id_title) + loginNotice.text = getString(R.string.login_signin_matrix_id_notice) + } else { + val resId = when (state.signMode) { + SignMode.Unknown -> error("developer error") + SignMode.SignUp -> R.string.login_signup_to + SignMode.SignIn -> R.string.login_connect_to + SignMode.SignInWithMatrixId -> R.string.login_connect_to } - ServerType.Modular -> { - loginServerIcon.isVisible = true - loginServerIcon.setImageResource(R.drawable.ic_logo_modular) - loginTitle.text = getString(resId, "Modular") - loginNotice.text = getString(R.string.login_server_modular_text) - } - ServerType.Other -> { - loginServerIcon.isVisible = false - loginTitle.text = getString(resId, state.homeServerUrl.toReducedUrl()) - loginNotice.text = getString(R.string.login_server_other_text) + + when (state.serverType) { + ServerType.MatrixOrg -> { + loginServerIcon.isVisible = true + loginServerIcon.setImageResource(R.drawable.ic_logo_matrix_org) + loginTitle.text = getString(resId, state.homeServerUrl.toReducedUrl()) + loginNotice.text = getString(R.string.login_server_matrix_org_text) + } + ServerType.Modular -> { + loginServerIcon.isVisible = true + loginServerIcon.setImageResource(R.drawable.ic_logo_modular) + loginTitle.text = getString(resId, "Modular") + loginNotice.text = getString(R.string.login_server_modular_text) + } + ServerType.Other -> { + loginServerIcon.isVisible = false + loginTitle.text = getString(resId, state.homeServerUrl.toReducedUrl()) + loginNotice.text = getString(R.string.login_server_other_text) + } } } } @@ -153,9 +164,10 @@ class LoginFragment @Inject constructor() : AbstractLoginFragment() { forgetPasswordButton.isVisible = state.signMode == SignMode.SignIn loginSubmit.text = getString(when (state.signMode) { - SignMode.Unknown -> error("developer error") - SignMode.SignUp -> R.string.login_signup_submit - SignMode.SignIn -> R.string.login_signin + SignMode.Unknown -> error("developer error") + SignMode.SignUp -> R.string.login_signup_submit + SignMode.SignIn, + SignMode.SignInWithMatrixId -> R.string.login_signin }) } diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginServerSelectionFragment.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginServerSelectionFragment.kt index 9050ea2688..ddd56c314e 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginServerSelectionFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginServerSelectionFragment.kt @@ -99,6 +99,12 @@ class LoginServerSelectionFragment @Inject constructor() : AbstractLoginFragment } } + @OnClick(R.id.loginServerIKnowMyIdSubmit) + fun loginWithMatrixId() { + loginViewModel.handle(LoginAction.UpdateSignMode(SignMode.SignInWithMatrixId)) + loginSharedActionViewModel.post(LoginNavigation.OnSignModeSelected) + } + override fun resetViewModel() { loginViewModel.handle(LoginAction.ResetHomeServerType) } diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt index 80b04fe062..05f841269f 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt @@ -17,6 +17,7 @@ package im.vector.riotx.features.login import android.content.Context +import android.net.Uri import androidx.fragment.app.FragmentActivity import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Fail @@ -29,19 +30,24 @@ import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.auth.AuthenticationService +import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig import im.vector.matrix.android.api.auth.data.LoginFlowResult import im.vector.matrix.android.api.auth.login.LoginWizard import im.vector.matrix.android.api.auth.registration.FlowResult import im.vector.matrix.android.api.auth.registration.RegistrationResult import im.vector.matrix.android.api.auth.registration.RegistrationWizard import im.vector.matrix.android.api.auth.registration.Stage +import im.vector.matrix.android.api.auth.wellknown.WellknownResult import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.internal.auth.data.LoginFlowTypes import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth +import im.vector.riotx.R import im.vector.riotx.core.di.ActiveSessionHolder import im.vector.riotx.core.extensions.configureAndStart +import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.platform.VectorViewModel +import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.features.notifications.PushRuleTriggerListener import im.vector.riotx.features.session.SessionListener import im.vector.riotx.features.signout.soft.SoftLogoutActivity @@ -51,14 +57,16 @@ import java.util.concurrent.CancellationException /** * */ -class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginViewState, - private val applicationContext: Context, - private val authenticationService: AuthenticationService, - private val activeSessionHolder: ActiveSessionHolder, - private val pushRuleTriggerListener: PushRuleTriggerListener, - private val homeServerConnectionConfigFactory: HomeServerConnectionConfigFactory, - private val sessionListener: SessionListener, - private val reAuthHelper: ReAuthHelper) +class LoginViewModel @AssistedInject constructor( + @Assisted initialState: LoginViewState, + private val applicationContext: Context, + private val authenticationService: AuthenticationService, + private val activeSessionHolder: ActiveSessionHolder, + private val pushRuleTriggerListener: PushRuleTriggerListener, + private val homeServerConnectionConfigFactory: HomeServerConnectionConfigFactory, + private val sessionListener: SessionListener, + private val reAuthHelper: ReAuthHelper, + private val stringProvider: StringProvider) : VectorViewModel(initialState) { @AssistedInject.Factory @@ -421,10 +429,73 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi private fun handleLoginOrRegister(action: LoginAction.LoginOrRegister) = withState { state -> when (state.signMode) { - SignMode.SignIn -> handleLogin(action) - SignMode.SignUp -> handleRegisterWith(action) - else -> error("Developer error, invalid sign mode") + SignMode.Unknown -> error("Developer error, invalid sign mode") + SignMode.SignIn -> handleLogin(action) + SignMode.SignUp -> handleRegisterWith(action) + SignMode.SignInWithMatrixId -> handleDirectLogin(action) + }.exhaustive + } + + private fun handleDirectLogin(action: LoginAction.LoginOrRegister) { + setState { + copy( + asyncLoginAction = Loading() + ) } + + authenticationService.getWellKnownData(action.username, object : MatrixCallback { + override fun onSuccess(data: WellknownResult) { + when (data) { + is WellknownResult.Prompt -> + onWellknownSuccess(action, data) + is WellknownResult.InvalidMatrixId -> { + setState { + copy( + asyncLoginAction = Uninitialized + ) + } + _viewEvents.post(LoginViewEvents.Failure(Exception(stringProvider.getString(R.string.login_signin_matrix_id_error_invalid_matrix_id)))) + } + else -> { + setState { + copy( + asyncLoginAction = Uninitialized + ) + } + _viewEvents.post(LoginViewEvents.Failure(Exception(stringProvider.getString(R.string.autodiscover_well_known_error)))) + } + }.exhaustive + } + + override fun onFailure(failure: Throwable) { + setState { + copy( + asyncLoginAction = Fail(failure) + ) + } + } + }) + } + + private fun onWellknownSuccess(action: LoginAction.LoginOrRegister, wellKnownPrompt: WellknownResult.Prompt) { + val homeServerConnectionConfig = HomeServerConnectionConfig( + homeServerUri = Uri.parse(wellKnownPrompt.homerServerUrl), + identityServerUri = wellKnownPrompt.identityServerUrl?.let { Uri.parse(it) } + ) + + authenticationService.directAuthentication(homeServerConnectionConfig, action.username, action.password, action.initialDeviceName, object : MatrixCallback { + override fun onSuccess(data: Session) { + onSessionCreated(data) + } + + override fun onFailure(failure: Throwable) { + setState { + copy( + asyncLoginAction = Fail(failure) + ) + } + } + }) } private fun handleLogin(action: LoginAction.LoginOrRegister) { diff --git a/vector/src/main/java/im/vector/riotx/features/login/SignMode.kt b/vector/src/main/java/im/vector/riotx/features/login/SignMode.kt index b793a0fe1d..ad06f1f4a9 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/SignMode.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/SignMode.kt @@ -21,5 +21,7 @@ enum class SignMode { // Account creation SignUp, // Login - SignIn + SignIn, + // Login directly with matrix Id + SignInWithMatrixId } diff --git a/vector/src/main/res/layout/fragment_login_server_selection.xml b/vector/src/main/res/layout/fragment_login_server_selection.xml index c97b32bd21..dc04f202fa 100644 --- a/vector/src/main/res/layout/fragment_login_server_selection.xml +++ b/vector/src/main/res/layout/fragment_login_server_selection.xml @@ -184,11 +184,34 @@ android:layout_marginTop="24dp" android:text="@string/login_continue" android:transitionName="loginSubmitTransition" - app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintBottom_toTopOf="@+id/loginServerIKnowMyIdNotice" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/loginServerChoiceOther" /> + + + + diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index 585102d46a..dd1043819d 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -57,4 +57,12 @@ + Alternatively, if you already have an account and you know your Matrix identifier and your password, you can use this method: + Sign in with my Matrix identifier + Sign in + Enter your identifier and your password + User identifier + This is not a valid user identifier. Expected format: \'@user:homeserver.org\' + Unable to find a valid homeserver. Please check your identifier + From f74b1e6c2e1d4b2008a8cca6f9b49a201c9ca7fa Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 2 Mar 2020 19:11:52 +0100 Subject: [PATCH 28/36] Migrate Login Navigation view model to regular ViewEvents --- .../vector/riotx/core/di/ViewModelModule.kt | 8 +- .../features/login/AbstractLoginFragment.kt | 3 - .../riotx/features/login/LoginAction.kt | 2 + .../riotx/features/login/LoginActivity.kt | 121 ++++++++---------- .../riotx/features/login/LoginFragment.kt | 2 +- .../LoginGenericTextInputFormFragment.kt | 4 +- .../riotx/features/login/LoginNavigation.kt | 36 ------ .../login/LoginResetPasswordFragment.kt | 2 +- ...inResetPasswordMailConfirmationFragment.kt | 2 +- .../LoginResetPasswordSuccessFragment.kt | 2 +- .../login/LoginServerSelectionFragment.kt | 5 +- .../login/LoginServerUrlFormFragment.kt | 2 +- .../login/LoginSharedActionViewModel.kt | 22 ---- .../LoginSignUpSignInSelectionFragment.kt | 1 - .../features/login/LoginSplashFragment.kt | 2 +- .../riotx/features/login/LoginViewEvents.kt | 18 ++- .../riotx/features/login/LoginViewModel.kt | 20 ++- .../riotx/features/login/LoginWebFragment.kt | 2 +- .../signout/soft/SoftLogoutFragment.kt | 6 +- 19 files changed, 102 insertions(+), 158 deletions(-) delete mode 100644 vector/src/main/java/im/vector/riotx/features/login/LoginNavigation.kt delete mode 100644 vector/src/main/java/im/vector/riotx/features/login/LoginSharedActionViewModel.kt 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 e480cf22ca..8046f67668 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 @@ -30,11 +30,10 @@ import im.vector.riotx.features.home.HomeSharedActionViewModel 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 +import im.vector.riotx.features.userdirectory.UserDirectorySharedActionViewModel import im.vector.riotx.features.workers.signout.SignOutViewModel @Module @@ -110,11 +109,6 @@ interface ViewModelModule { @ViewModelKey(RoomDirectorySharedActionViewModel::class) fun bindRoomDirectorySharedActionViewModel(viewModel: RoomDirectorySharedActionViewModel): ViewModel - @Binds - @IntoMap - @ViewModelKey(LoginSharedActionViewModel::class) - fun bindLoginSharedActionViewModel(viewModel: LoginSharedActionViewModel): ViewModel - @Binds @IntoMap @ViewModelKey(RoomDetailSharedActionViewModel::class) diff --git a/vector/src/main/java/im/vector/riotx/features/login/AbstractLoginFragment.kt b/vector/src/main/java/im/vector/riotx/features/login/AbstractLoginFragment.kt index 83263d05a2..8fceaad07f 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/AbstractLoginFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/AbstractLoginFragment.kt @@ -38,7 +38,6 @@ import javax.net.ssl.HttpsURLConnection abstract class AbstractLoginFragment : VectorBaseFragment(), OnBackPressed { protected val loginViewModel: LoginViewModel by activityViewModel() - protected lateinit var loginSharedActionViewModel: LoginSharedActionViewModel private var isResetPasswordStarted = false @@ -57,8 +56,6 @@ abstract class AbstractLoginFragment : VectorBaseFragment(), OnBackPressed { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - loginSharedActionViewModel = activityViewModelProvider.get(LoginSharedActionViewModel::class.java) - loginViewModel.observeViewEvents { handleLoginViewEvents(it) } diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginAction.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginAction.kt index 90d6754448..3403760136 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginAction.kt @@ -58,4 +58,6 @@ sealed class LoginAction : VectorViewModelAction { // For the soft logout case data class SetupSsoForSessionRecovery(val homeServerUrl: String, val deviceId: String) : LoginAction() + + data class PostViewEvent(val viewEvent: LoginViewEvents) : LoginAction() } diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginActivity.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginActivity.kt index cd8c1df46e..96da88f15c 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginActivity.kt @@ -55,7 +55,6 @@ import javax.inject.Inject open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable { private val loginViewModel: LoginViewModel by viewModel() - private lateinit var loginSharedActionViewModel: LoginSharedActionViewModel @Inject lateinit var loginViewModelFactory: LoginViewModel.Factory @@ -99,14 +98,6 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable { loginViewModel.handle(LoginAction.InitWith(loginConfig)) } - loginSharedActionViewModel = viewModelProvider.get(LoginSharedActionViewModel::class.java) - loginSharedActionViewModel - .observe() - .subscribe { - handleLoginNavigation(it) - } - .disposeOnDestroy() - loginViewModel .subscribe(this) { updateWithState(it) @@ -125,63 +116,9 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable { addFragment(R.id.loginFragmentContainer, LoginSplashFragment::class.java) } - private fun handleLoginNavigation(loginNavigation: LoginNavigation) { - when (loginNavigation) { - is LoginNavigation.OpenServerSelection -> - addFragmentToBackstack(R.id.loginFragmentContainer, - LoginServerSelectionFragment::class.java, - option = { ft -> - findViewById(R.id.loginSplashLogo)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") } - findViewById(R.id.loginSplashTitle)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") } - findViewById(R.id.loginSplashSubmit)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") } - // TODO Disabled because it provokes a flickering - // ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim) - }) - is LoginNavigation.OnServerSelectionDone -> onServerSelectionDone() - is LoginNavigation.OnSignModeSelected -> onSignModeSelected() - is LoginNavigation.OnLoginFlowRetrieved -> - addFragmentToBackstack(R.id.loginFragmentContainer, - LoginSignUpSignInSelectionFragment::class.java, - option = commonOption) - is LoginNavigation.OnWebLoginError -> onWebLoginError(loginNavigation) - is LoginNavigation.OnForgetPasswordClicked -> - addFragmentToBackstack(R.id.loginFragmentContainer, - LoginResetPasswordFragment::class.java, - option = commonOption) - is LoginNavigation.OnResetPasswordSendThreePidDone -> { - supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE) - addFragmentToBackstack(R.id.loginFragmentContainer, - LoginResetPasswordMailConfirmationFragment::class.java, - option = commonOption) - } - is LoginNavigation.OnResetPasswordMailConfirmationSuccess -> { - supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE) - addFragmentToBackstack(R.id.loginFragmentContainer, - LoginResetPasswordSuccessFragment::class.java, - option = commonOption) - } - is LoginNavigation.OnResetPasswordMailConfirmationSuccessDone -> { - // Go back to the login fragment - supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE) - } - is LoginNavigation.OnSendEmailSuccess -> - addFragmentToBackstack(R.id.loginFragmentContainer, - LoginWaitForEmailFragment::class.java, - LoginWaitForEmailFragmentArgument(loginNavigation.email), - tag = FRAGMENT_REGISTRATION_STAGE_TAG, - option = commonOption) - is LoginNavigation.OnSendMsisdnSuccess -> - addFragmentToBackstack(R.id.loginFragmentContainer, - LoginGenericTextInputFormFragment::class.java, - LoginGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.ConfirmMsisdn, true, loginNavigation.msisdn), - tag = FRAGMENT_REGISTRATION_STAGE_TAG, - option = commonOption) - }.exhaustive - } - private fun handleLoginViewEvents(loginViewEvents: LoginViewEvents) { when (loginViewEvents) { - is LoginViewEvents.RegistrationFlowResult -> { + is LoginViewEvents.RegistrationFlowResult -> { // Check that all flows are supported by the application if (loginViewEvents.flowResult.missingStages.any { !it.isSupported() }) { // Display a popup to propose use web fallback @@ -202,15 +139,65 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable { } } } - is LoginViewEvents.OutdatedHomeserver -> + is LoginViewEvents.OutdatedHomeserver -> AlertDialog.Builder(this) .setTitle(R.string.login_error_outdated_homeserver_title) .setMessage(R.string.login_error_outdated_homeserver_content) .setPositiveButton(R.string.ok, null) .show() - is LoginViewEvents.Failure -> + is LoginViewEvents.Failure -> // This is handled by the Fragments Unit + is LoginViewEvents.OpenServerSelection -> + addFragmentToBackstack(R.id.loginFragmentContainer, + LoginServerSelectionFragment::class.java, + option = { ft -> + findViewById(R.id.loginSplashLogo)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") } + findViewById(R.id.loginSplashTitle)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") } + findViewById(R.id.loginSplashSubmit)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") } + // TODO Disabled because it provokes a flickering + // ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim) + }) + is LoginViewEvents.OnServerSelectionDone -> onServerSelectionDone() + is LoginViewEvents.OnSignModeSelected -> onSignModeSelected() + is LoginViewEvents.OnLoginFlowRetrieved -> + addFragmentToBackstack(R.id.loginFragmentContainer, + LoginSignUpSignInSelectionFragment::class.java, + option = commonOption) + is LoginViewEvents.OnWebLoginError -> onWebLoginError(loginViewEvents) + is LoginViewEvents.OnForgetPasswordClicked -> + addFragmentToBackstack(R.id.loginFragmentContainer, + LoginResetPasswordFragment::class.java, + option = commonOption) + is LoginViewEvents.OnResetPasswordSendThreePidDone -> { + supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE) + addFragmentToBackstack(R.id.loginFragmentContainer, + LoginResetPasswordMailConfirmationFragment::class.java, + option = commonOption) + } + is LoginViewEvents.OnResetPasswordMailConfirmationSuccess -> { + supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE) + addFragmentToBackstack(R.id.loginFragmentContainer, + LoginResetPasswordSuccessFragment::class.java, + option = commonOption) + } + is LoginViewEvents.OnResetPasswordMailConfirmationSuccessDone -> { + // Go back to the login fragment + supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE) + } + is LoginViewEvents.OnSendEmailSuccess -> + addFragmentToBackstack(R.id.loginFragmentContainer, + LoginWaitForEmailFragment::class.java, + LoginWaitForEmailFragmentArgument(loginViewEvents.email), + tag = FRAGMENT_REGISTRATION_STAGE_TAG, + option = commonOption) + is LoginViewEvents.OnSendMsisdnSuccess -> + addFragmentToBackstack(R.id.loginFragmentContainer, + LoginGenericTextInputFormFragment::class.java, + LoginGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.ConfirmMsisdn, true, loginViewEvents.msisdn), + tag = FRAGMENT_REGISTRATION_STAGE_TAG, + option = commonOption) + } } @@ -229,7 +216,7 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable { loginLoading.isVisible = loginViewState.isLoading() } - private fun onWebLoginError(onWebLoginError: LoginNavigation.OnWebLoginError) { + private fun onWebLoginError(onWebLoginError: LoginViewEvents.OnWebLoginError) { // Pop the backstack supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE) diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginFragment.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginFragment.kt index 371f669736..c2bd02b817 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginFragment.kt @@ -190,7 +190,7 @@ class LoginFragment @Inject constructor() : AbstractLoginFragment() { @OnClick(R.id.forgetPasswordButton) fun forgetPasswordClicked() { - loginSharedActionViewModel.post(LoginNavigation.OnForgetPasswordClicked) + loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OnForgetPasswordClicked)) } private fun setupPasswordReveal() { diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginGenericTextInputFormFragment.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginGenericTextInputFormFragment.kt index 3ee1cd6d64..5203e60b26 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginGenericTextInputFormFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginGenericTextInputFormFragment.kt @@ -217,7 +217,7 @@ class LoginGenericTextInputFormFragment @Inject constructor() : AbstractLoginFra TextInputFormFragmentMode.SetEmail -> { if (throwable.is401()) { // This is normal use case, we go to the mail waiting screen - loginSharedActionViewModel.post(LoginNavigation.OnSendEmailSuccess(loginViewModel.currentThreePid ?: "")) + loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OnSendEmailSuccess(loginViewModel.currentThreePid ?: ""))) } else { loginGenericTextInputFormTil.error = errorFormatter.toHumanReadable(throwable) } @@ -225,7 +225,7 @@ class LoginGenericTextInputFormFragment @Inject constructor() : AbstractLoginFra TextInputFormFragmentMode.SetMsisdn -> { if (throwable.is401()) { // This is normal use case, we go to the enter code screen - loginSharedActionViewModel.post(LoginNavigation.OnSendMsisdnSuccess(loginViewModel.currentThreePid ?: "")) + loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OnSendMsisdnSuccess(loginViewModel.currentThreePid ?: ""))) } else { loginGenericTextInputFormTil.error = errorFormatter.toHumanReadable(throwable) } diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginNavigation.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginNavigation.kt deleted file mode 100644 index 79c6409a3f..0000000000 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginNavigation.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2019 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.riotx.features.login - -import im.vector.riotx.core.platform.VectorSharedAction - -// Supported navigation actions for LoginActivity -sealed class LoginNavigation : VectorSharedAction { - object OpenServerSelection : LoginNavigation() - object OnServerSelectionDone : LoginNavigation() - object OnLoginFlowRetrieved : LoginNavigation() - object OnSignModeSelected : LoginNavigation() - object OnForgetPasswordClicked : LoginNavigation() - object OnResetPasswordSendThreePidDone : LoginNavigation() - object OnResetPasswordMailConfirmationSuccess : LoginNavigation() - object OnResetPasswordMailConfirmationSuccessDone : LoginNavigation() - - data class OnSendEmailSuccess(val email: String) : LoginNavigation() - data class OnSendMsisdnSuccess(val msisdn: String) : LoginNavigation() - - data class OnWebLoginError(val errorCode: Int, val description: String, val failingUrl: String) : LoginNavigation() -} diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginResetPasswordFragment.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginResetPasswordFragment.kt index d3a86ef769..d90cfc77e5 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginResetPasswordFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginResetPasswordFragment.kt @@ -149,7 +149,7 @@ class LoginResetPasswordFragment @Inject constructor() : AbstractLoginFragment() resetPasswordEmailTil.error = errorFormatter.toHumanReadable(state.asyncResetPassword.error) } is Success -> { - loginSharedActionViewModel.post(LoginNavigation.OnResetPasswordSendThreePidDone) + Unit } } } diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginResetPasswordMailConfirmationFragment.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginResetPasswordMailConfirmationFragment.kt index cace48b7f2..f340fb8f9f 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginResetPasswordMailConfirmationFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginResetPasswordMailConfirmationFragment.kt @@ -64,7 +64,7 @@ class LoginResetPasswordMailConfirmationFragment @Inject constructor() : Abstrac .show() } is Success -> { - loginSharedActionViewModel.post(LoginNavigation.OnResetPasswordMailConfirmationSuccess) + Unit } } } diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginResetPasswordSuccessFragment.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginResetPasswordSuccessFragment.kt index 4faeef1269..fd3c5e6377 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginResetPasswordSuccessFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginResetPasswordSuccessFragment.kt @@ -29,7 +29,7 @@ class LoginResetPasswordSuccessFragment @Inject constructor() : AbstractLoginFra @OnClick(R.id.resetPasswordSuccessSubmit) fun submit() { - loginSharedActionViewModel.post(LoginNavigation.OnResetPasswordMailConfirmationSuccessDone) + loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OnResetPasswordMailConfirmationSuccessDone)) } override fun resetViewModel() { diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginServerSelectionFragment.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginServerSelectionFragment.kt index ddd56c314e..0e234d3da8 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginServerSelectionFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginServerSelectionFragment.kt @@ -95,14 +95,13 @@ class LoginServerSelectionFragment @Inject constructor() : AbstractLoginFragment // Request login flow here loginViewModel.handle(LoginAction.UpdateHomeServer(getString(R.string.matrix_org_server_url))) } else { - loginSharedActionViewModel.post(LoginNavigation.OnServerSelectionDone) + loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OnServerSelectionDone)) } } @OnClick(R.id.loginServerIKnowMyIdSubmit) fun loginWithMatrixId() { loginViewModel.handle(LoginAction.UpdateSignMode(SignMode.SignInWithMatrixId)) - loginSharedActionViewModel.post(LoginNavigation.OnSignModeSelected) } override fun resetViewModel() { @@ -114,7 +113,7 @@ class LoginServerSelectionFragment @Inject constructor() : AbstractLoginFragment if (state.loginMode != LoginMode.Unknown) { // LoginFlow for matrix.org has been retrieved - loginSharedActionViewModel.post(LoginNavigation.OnLoginFlowRetrieved) + loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OnLoginFlowRetrieved)) } } } diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginServerUrlFormFragment.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginServerUrlFormFragment.kt index 898ee97656..92dcfcc8aa 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginServerUrlFormFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginServerUrlFormFragment.kt @@ -126,7 +126,7 @@ class LoginServerUrlFormFragment @Inject constructor() : AbstractLoginFragment() if (state.loginMode != LoginMode.Unknown) { // The home server url is valid - loginSharedActionViewModel.post(LoginNavigation.OnLoginFlowRetrieved) + loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OnLoginFlowRetrieved)) } } } diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginSharedActionViewModel.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginSharedActionViewModel.kt deleted file mode 100644 index 625208b682..0000000000 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginSharedActionViewModel.kt +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2019 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.riotx.features.login - -import im.vector.riotx.core.platform.VectorSharedActionViewModel -import javax.inject.Inject - -class LoginSharedActionViewModel @Inject constructor() : VectorSharedActionViewModel() diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginSignUpSignInSelectionFragment.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginSignUpSignInSelectionFragment.kt index 9f084299b7..f09053c883 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginSignUpSignInSelectionFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginSignUpSignInSelectionFragment.kt @@ -78,7 +78,6 @@ class LoginSignUpSignInSelectionFragment @Inject constructor() : AbstractLoginFr @OnClick(R.id.loginSignupSigninSignIn) fun signIn() { loginViewModel.handle(LoginAction.UpdateSignMode(SignMode.SignIn)) - loginSharedActionViewModel.post(LoginNavigation.OnSignModeSelected) } override fun resetViewModel() { diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginSplashFragment.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginSplashFragment.kt index 53de8c2c43..c860d02fec 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginSplashFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginSplashFragment.kt @@ -29,7 +29,7 @@ class LoginSplashFragment @Inject constructor() : AbstractLoginFragment() { @OnClick(R.id.loginSplashSubmit) fun getStarted() { - loginSharedActionViewModel.post(LoginNavigation.OpenServerSelection) + loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OpenServerSelection)) } override fun resetViewModel() { diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginViewEvents.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginViewEvents.kt index 25747df3d4..c7c2ee6273 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginViewEvents.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginViewEvents.kt @@ -23,10 +23,26 @@ import im.vector.riotx.core.platform.VectorViewEvents /** * Transient events for Login */ -sealed class LoginViewEvents: VectorViewEvents { +sealed class LoginViewEvents : VectorViewEvents { data class Loading(val message: CharSequence? = null) : LoginViewEvents() data class Failure(val throwable: Throwable) : LoginViewEvents() data class RegistrationFlowResult(val flowResult: FlowResult, val isRegistrationStarted: Boolean) : LoginViewEvents() object OutdatedHomeserver : LoginViewEvents() + + // Navigation event + + object OpenServerSelection : LoginViewEvents() + object OnServerSelectionDone : LoginViewEvents() + object OnLoginFlowRetrieved : LoginViewEvents() + object OnSignModeSelected : LoginViewEvents() + object OnForgetPasswordClicked : LoginViewEvents() + object OnResetPasswordSendThreePidDone : LoginViewEvents() + object OnResetPasswordMailConfirmationSuccess : LoginViewEvents() + object OnResetPasswordMailConfirmationSuccessDone : LoginViewEvents() + + data class OnSendEmailSuccess(val email: String) : LoginViewEvents() + data class OnSendMsisdnSuccess(val msisdn: String) : LoginViewEvents() + + data class OnWebLoginError(val errorCode: Int, val description: String, val failingUrl: String) : LoginViewEvents() } diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt index 05f841269f..47360e7c12 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt @@ -116,7 +116,8 @@ class LoginViewModel @AssistedInject constructor( is LoginAction.RegisterAction -> handleRegisterAction(action) is LoginAction.ResetAction -> handleResetAction(action) is LoginAction.SetupSsoForSessionRecovery -> handleSetupSsoForSessionRecovery(action) - } + is LoginAction.PostViewEvent -> _viewEvents.post(action.viewEvent) + }.exhaustive } private fun handleSetupSsoForSessionRecovery(action: LoginAction.SetupSsoForSessionRecovery) { @@ -328,11 +329,12 @@ class LoginViewModel @AssistedInject constructor( ) } - if (action.signMode == SignMode.SignUp) { - startRegistrationFlow() - } else if (action.signMode == SignMode.SignIn) { - startAuthenticationFlow() - } + when (action.signMode) { + SignMode.SignUp -> startRegistrationFlow() + SignMode.SignIn -> startAuthenticationFlow() + SignMode.SignInWithMatrixId -> _viewEvents.post(LoginViewEvents.OnSignModeSelected) + SignMode.Unknown -> Unit + }.exhaustive } private fun handleUpdateServerType(action: LoginAction.UpdateServerType) { @@ -373,6 +375,8 @@ class LoginViewModel @AssistedInject constructor( resetPasswordEmail = action.email ) } + + _viewEvents.post(LoginViewEvents.OnResetPasswordSendThreePidDone) } override fun onFailure(failure: Throwable) { @@ -413,6 +417,8 @@ class LoginViewModel @AssistedInject constructor( resetPasswordEmail = null ) } + + _viewEvents.post(LoginViewEvents.OnResetPasswordMailConfirmationSuccess) } override fun onFailure(failure: Throwable) { @@ -548,6 +554,8 @@ class LoginViewModel @AssistedInject constructor( private fun startAuthenticationFlow() { // Ensure Wizard is ready loginWizard + + _viewEvents.post(LoginViewEvents.OnSignModeSelected) } private fun onFlowResponse(flowResult: FlowResult) { diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginWebFragment.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginWebFragment.kt index f9b0b98f29..cf3b39ebb0 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginWebFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginWebFragment.kt @@ -173,7 +173,7 @@ class LoginWebFragment @Inject constructor( override fun onReceivedError(view: WebView, errorCode: Int, description: String, failingUrl: String) { super.onReceivedError(view, errorCode, description, failingUrl) - loginSharedActionViewModel.post(LoginNavigation.OnWebLoginError(errorCode, description, failingUrl)) + loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OnWebLoginError(errorCode, description, failingUrl))) } override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) { diff --git a/vector/src/main/java/im/vector/riotx/features/signout/soft/SoftLogoutFragment.kt b/vector/src/main/java/im/vector/riotx/features/signout/soft/SoftLogoutFragment.kt index d3288c5b2e..13b90f26e8 100644 --- a/vector/src/main/java/im/vector/riotx/features/signout/soft/SoftLogoutFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/signout/soft/SoftLogoutFragment.kt @@ -30,7 +30,7 @@ import im.vector.riotx.core.extensions.hideKeyboard import im.vector.riotx.features.login.AbstractLoginFragment import im.vector.riotx.features.login.LoginAction import im.vector.riotx.features.login.LoginMode -import im.vector.riotx.features.login.LoginNavigation +import im.vector.riotx.features.login.LoginViewEvents import kotlinx.android.synthetic.main.fragment_generic_recycler.* import javax.inject.Inject @@ -94,7 +94,7 @@ class SoftLogoutFragment @Inject constructor( } override fun signinFallbackSubmit() { - loginSharedActionViewModel.post(LoginNavigation.OnSignModeSelected) + loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OnSignModeSelected)) } override fun clearData() { @@ -124,7 +124,7 @@ class SoftLogoutFragment @Inject constructor( } override fun forgetPasswordClicked() { - loginSharedActionViewModel.post(LoginNavigation.OnForgetPasswordClicked) + loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OnForgetPasswordClicked)) } override fun revealPasswordClicked() { From c173235ee3efdcdd3139f12dcd26f403f7d186f1 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 2 Mar 2020 19:31:19 +0100 Subject: [PATCH 29/36] ktlint --- .../riotx/features/login/LoginActivity.kt | 1 - .../riotx/features/login/LoginViewModel.kt | 29 +++++++++++-------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginActivity.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginActivity.kt index 96da88f15c..99d8da490d 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginActivity.kt @@ -197,7 +197,6 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable { LoginGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.ConfirmMsisdn, true, loginViewEvents.msisdn), tag = FRAGMENT_REGISTRATION_STAGE_TAG, option = commonOption) - } } diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt index 47360e7c12..ea25523be5 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt @@ -489,19 +489,24 @@ class LoginViewModel @AssistedInject constructor( identityServerUri = wellKnownPrompt.identityServerUrl?.let { Uri.parse(it) } ) - authenticationService.directAuthentication(homeServerConnectionConfig, action.username, action.password, action.initialDeviceName, object : MatrixCallback { - override fun onSuccess(data: Session) { - onSessionCreated(data) - } + authenticationService.directAuthentication( + homeServerConnectionConfig, + action.username, + action.password, + action.initialDeviceName, + object : MatrixCallback { + override fun onSuccess(data: Session) { + onSessionCreated(data) + } - override fun onFailure(failure: Throwable) { - setState { - copy( - asyncLoginAction = Fail(failure) - ) - } - } - }) + override fun onFailure(failure: Throwable) { + setState { + copy( + asyncLoginAction = Fail(failure) + ) + } + } + }) } private fun handleLogin(action: LoginAction.LoginOrRegister) { From a70fdedce5c340d98b8ff81a7bd11677bac7f677 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 27 Apr 2020 00:07:47 +0200 Subject: [PATCH 30/36] Try to use wellKnown request, when user is entering a homeserver URL --- .../auth/DefaultAuthenticationService.kt | 83 ++++++++++++++----- 1 file changed, 64 insertions(+), 19 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticationService.kt index b2c2dc12a5..99aff475e4 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticationService.kt @@ -48,6 +48,7 @@ import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.task.launchToCallback import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers +import im.vector.matrix.android.internal.util.exhaustive import im.vector.matrix.android.internal.util.toCancelable import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch @@ -156,27 +157,71 @@ internal class DefaultAuthenticationService @Inject constructor( val authAPI = buildAuthAPI(homeServerConnectionConfig) // Ok, try to get the config.json file of a RiotWeb client - val riotConfig = executeRequest(null) { - apiCall = authAPI.getRiotConfig() - } - - if (riotConfig.defaultHomeServerUrl?.isNotBlank() == true) { - // Ok, good sign, we got a default hs url - val newHomeServerConnectionConfig = homeServerConnectionConfig.copy( - homeServerUri = Uri.parse(riotConfig.defaultHomeServerUrl) - ) - - val newAuthAPI = buildAuthAPI(newHomeServerConnectionConfig) - - val versions = executeRequest(null) { - apiCall = newAuthAPI.versions() + return runCatching { + executeRequest(null) { + apiCall = authAPI.getRiotConfig() } - - return getLoginFlowResult(newAuthAPI, versions, riotConfig.defaultHomeServerUrl) - } else { - // Config exists, but there is no default homeserver url (ex: https://riot.im/app) - throw Failure.OtherServerError("", HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) } + .map { riotConfig -> + if (riotConfig.defaultHomeServerUrl?.isNotBlank() == true) { + // Ok, good sign, we got a default hs url + val newHomeServerConnectionConfig = homeServerConnectionConfig.copy( + homeServerUri = Uri.parse(riotConfig.defaultHomeServerUrl) + ) + + val newAuthAPI = buildAuthAPI(newHomeServerConnectionConfig) + + val versions = executeRequest(null) { + apiCall = newAuthAPI.versions() + } + + getLoginFlowResult(newAuthAPI, versions, riotConfig.defaultHomeServerUrl) + } else { + // Config exists, but there is no default homeserver url (ex: https://riot.im/app) + throw Failure.OtherServerError("", HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) + } + } + .fold( + { + it + }, + { + if (it is Failure.OtherServerError + && it.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) { + // Try with wellknown + getWellknownLoginFlowInternal(homeServerConnectionConfig) + } else { + throw it + } + } + ) + } + + private suspend fun getWellknownLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult { + val domain = homeServerConnectionConfig.homeServerUri.host + ?: throw Failure.OtherServerError("", HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) + + // Create a fake userId, for the getWellknown task + val fakeUserId = "@alice:$domain" + val wellknownResult = getWellknownTask.execute(GetWellknownTask.Params(fakeUserId)) + + return when (wellknownResult) { + is WellknownResult.Prompt -> { + val newHomeServerConnectionConfig = homeServerConnectionConfig.copy( + homeServerUri = Uri.parse(wellknownResult.homerServerUrl), + identityServerUri = wellknownResult.identityServerUrl?.let { Uri.parse(it) } + ) + + val newAuthAPI = buildAuthAPI(newHomeServerConnectionConfig) + + val versions = executeRequest(null) { + apiCall = newAuthAPI.versions() + } + + getLoginFlowResult(newAuthAPI, versions, wellknownResult.homerServerUrl) + } + else -> throw Failure.OtherServerError("", HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) + }.exhaustive } private suspend fun getLoginFlowResult(authAPI: AuthAPI, versions: Versions, homeServerUrl: String): LoginFlowResult { From cf7de8bb8b6e2846a4f9b86f8b098276dc13ec7f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 27 Apr 2020 00:12:11 +0200 Subject: [PATCH 31/36] Typo --- .../matrix/android/api/auth/wellknown/WellknownResult.kt | 2 +- .../android/internal/auth/DefaultAuthenticationService.kt | 4 ++-- .../java/im/vector/riotx/features/login/LoginViewModel.kt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/wellknown/WellknownResult.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/wellknown/WellknownResult.kt index 974a65adbc..58c7cf730e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/wellknown/WellknownResult.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/wellknown/WellknownResult.kt @@ -31,7 +31,7 @@ sealed class WellknownResult { * Retrieve the specific piece of information from the user in a way which fits within the existing client user experience, * if the client is inclined to do so. Failure can take place instead if no good user experience for this is possible at this point. */ - data class Prompt(val homerServerUrl: String, + data class Prompt(val homeServerUrl: String, val identityServerUrl: String?, val wellKnown: WellKnown) : WellknownResult() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticationService.kt index 99aff475e4..997cf70e5a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticationService.kt @@ -208,7 +208,7 @@ internal class DefaultAuthenticationService @Inject constructor( return when (wellknownResult) { is WellknownResult.Prompt -> { val newHomeServerConnectionConfig = homeServerConnectionConfig.copy( - homeServerUri = Uri.parse(wellknownResult.homerServerUrl), + homeServerUri = Uri.parse(wellknownResult.homeServerUrl), identityServerUri = wellknownResult.identityServerUrl?.let { Uri.parse(it) } ) @@ -218,7 +218,7 @@ internal class DefaultAuthenticationService @Inject constructor( apiCall = newAuthAPI.versions() } - getLoginFlowResult(newAuthAPI, versions, wellknownResult.homerServerUrl) + getLoginFlowResult(newAuthAPI, versions, wellknownResult.homeServerUrl) } else -> throw Failure.OtherServerError("", HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) }.exhaustive diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt index ea25523be5..81dcfcea9f 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt @@ -485,7 +485,7 @@ class LoginViewModel @AssistedInject constructor( private fun onWellknownSuccess(action: LoginAction.LoginOrRegister, wellKnownPrompt: WellknownResult.Prompt) { val homeServerConnectionConfig = HomeServerConnectionConfig( - homeServerUri = Uri.parse(wellKnownPrompt.homerServerUrl), + homeServerUri = Uri.parse(wellKnownPrompt.homeServerUrl), identityServerUri = wellKnownPrompt.identityServerUrl?.let { Uri.parse(it) } ) From 57fca80cbb87455cc3b1b2968d961a24f2ee0b0c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 13 May 2020 13:33:12 +0200 Subject: [PATCH 32/36] Disable possibility to login using matrixId (waiting for design) --- CHANGES.md | 2 +- .../src/main/res/layout/fragment_login_server_selection.xml | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 2b36909c33..03817341bc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,7 +7,7 @@ Features ✨: Improvements 🙌: - Invite member(s) to an existing room (#1276) - Improve notification accessibility with ticker text (#1226) - - Support homeserver discovery from MXID (#476) + - Support homeserver discovery from MXID (DISABLED: waiting for design) (#476) Bugfix 🐛: - Sometimes the same device appears twice in the list of devices of a user (#1329) diff --git a/vector/src/main/res/layout/fragment_login_server_selection.xml b/vector/src/main/res/layout/fragment_login_server_selection.xml index dc04f202fa..ba74ce26f8 100644 --- a/vector/src/main/res/layout/fragment_login_server_selection.xml +++ b/vector/src/main/res/layout/fragment_login_server_selection.xml @@ -16,7 +16,9 @@ style="@style/LoginFormScrollView" tools:ignore="MissingConstraints"> - + @@ -207,6 +210,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:text="@string/login_connect_using_matrix_id_submit" + android:visibility="gone" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" From 678cf50dbdde21be489d8b64741c2a167639bd89 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 13 May 2020 13:53:01 +0200 Subject: [PATCH 33/36] Add Javadoc --- .../android/api/auth/data/Credentials.kt | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/Credentials.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/Credentials.kt index 72affe24bb..e2181a7b2a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/Credentials.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/Credentials.kt @@ -24,15 +24,36 @@ import im.vector.matrix.android.internal.util.md5 * This data class hold credentials user data. * You shouldn't have to instantiate it. * The access token should be use to authenticate user in all server requests. + * Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-login */ @JsonClass(generateAdapter = true) data class Credentials( + /** + * The fully-qualified Matrix ID that has been registered. + */ @Json(name = "user_id") val userId: String, - @Json(name = "home_server") val homeServer: String, + /** + * An access token for the account. This access token can then be used to authorize other requests. + */ @Json(name = "access_token") val accessToken: String, + /** + * Not documented + */ @Json(name = "refresh_token") val refreshToken: String?, + /** + * The server_name of the homeserver on which the account has been registered. + * @Deprecated. Clients should extract the server_name from user_id (by splitting at the first colon) if they require it. Note also that homeserver is not spelt this way. + */ + @Json(name = "home_server") val homeServer: String, + /** + * ID of the logged-in device. Will be the same as the corresponding parameter in the request, if one was specified. + */ @Json(name = "device_id") val deviceId: String?, - // Optional data that may contain info to override home server and/or identity server + /** + * Optional client configuration provided by the server. If present, clients SHOULD use the provided object to + * reconfigure themselves, optionally validating the URLs within. + * This object takes the same form as the one returned from .well-known autodiscovery. + */ @Json(name = "well_known") val wellKnown: WellKnown? = null ) From d8b1372a0f27174f5e68156a8c3bb9118579c180 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 13 May 2020 14:00:06 +0200 Subject: [PATCH 34/36] Login request does not provide the full Wellknown data. Change the model to reflect that, to avoid misunderstanding. --- .../android/api/auth/data/Credentials.kt | 2 +- .../api/auth/data/DiscoveryInformation.kt | 40 +++++++++++++++++++ .../android/internal/auth/SessionCreator.kt | 4 +- 3 files changed, 43 insertions(+), 3 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/DiscoveryInformation.kt diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/Credentials.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/Credentials.kt index e2181a7b2a..963e8e928c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/Credentials.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/Credentials.kt @@ -54,7 +54,7 @@ data class Credentials( * reconfigure themselves, optionally validating the URLs within. * This object takes the same form as the one returned from .well-known autodiscovery. */ - @Json(name = "well_known") val wellKnown: WellKnown? = null + @Json(name = "well_known") val discoveryInformation: DiscoveryInformation? = null ) internal fun Credentials.sessionId(): String { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/DiscoveryInformation.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/DiscoveryInformation.kt new file mode 100644 index 0000000000..2aa741bad3 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/DiscoveryInformation.kt @@ -0,0 +1,40 @@ +/* + * 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.matrix.android.api.auth.data + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * This is a light version of Wellknown model, used for login response + * Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-login + */ +@JsonClass(generateAdapter = true) +data class DiscoveryInformation( + /** + * Required. Used by clients to discover homeserver information. + */ + @Json(name = "m.homeserver") + val homeServer: WellKnownBaseConfig? = null, + + /** + * Used by clients to discover identity server information. + * Note: matrix.org does not send this field + */ + @Json(name = "m.identity_server") + val identityServer: WellKnownBaseConfig? = null +) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/SessionCreator.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/SessionCreator.kt index 95a9fbb506..74f7cad67d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/SessionCreator.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/SessionCreator.kt @@ -46,14 +46,14 @@ internal class DefaultSessionCreator @Inject constructor( val sessionParams = SessionParams( credentials = credentials, homeServerConnectionConfig = homeServerConnectionConfig.copy( - homeServerUri = credentials.wellKnown?.homeServer?.baseURL + homeServerUri = credentials.discoveryInformation?.homeServer?.baseURL // remove trailing "/" ?.trim { it == '/' } ?.takeIf { it.isNotBlank() } ?.also { Timber.d("Overriding homeserver url to $it") } ?.let { Uri.parse(it) } ?: homeServerConnectionConfig.homeServerUri, - identityServerUri = credentials.wellKnown?.identityServer?.baseURL + identityServerUri = credentials.discoveryInformation?.identityServer?.baseURL // remove trailing "/" ?.trim { it == '/' } ?.takeIf { it.isNotBlank() } From f717a37a4a8eefd2745549b3ab5825b5d27ab87c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 13 May 2020 15:28:05 +0200 Subject: [PATCH 35/36] Split long line --- .../java/im/vector/matrix/android/api/auth/data/Credentials.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/Credentials.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/Credentials.kt index 963e8e928c..d88cd5e74d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/Credentials.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/Credentials.kt @@ -42,7 +42,8 @@ data class Credentials( @Json(name = "refresh_token") val refreshToken: String?, /** * The server_name of the homeserver on which the account has been registered. - * @Deprecated. Clients should extract the server_name from user_id (by splitting at the first colon) if they require it. Note also that homeserver is not spelt this way. + * @Deprecated. Clients should extract the server_name from user_id (by splitting at the first colon) + * if they require it. Note also that homeserver is not spelt this way. */ @Json(name = "home_server") val homeServer: String, /** From 8d32c27ce01c6635614cc874eaed6cf21dbdfe65 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 13 May 2020 16:23:34 +0200 Subject: [PATCH 36/36] Fix crash 1364 --- CHANGES.md | 1 + .../matrix/android/internal/crypto/store/db/RealmCryptoStore.kt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 12ecb2e027..803b974413 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,6 +12,7 @@ Improvements 🙌: Bugfix 🐛: - Fix | Verify Manually by Text crashes if private SSK not known (#1337) - Sometimes the same device appears twice in the list of devices of a user (#1329) + - Random Crashes while doing sth with cross signing keys (#1364) Translations 🗣: - diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt index 17f049512c..7064663995 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt @@ -1406,7 +1406,7 @@ internal class RealmCryptoStore @Inject constructor( } else { // Just override existing, caller should check and untrust id needed val existing = CrossSigningInfoEntity.getOrCreate(realm, userId) - existing.crossSigningKeys.forEach { it.deleteFromRealm() } + existing.crossSigningKeys.deleteAllFromRealm() existing.crossSigningKeys.addAll( info.crossSigningKeys.map { crossSigningKeysMapper.map(it)