From cb44ab547cd314feb31f97bd21fff6116be20b36 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 19 Jul 2019 18:12:42 +0200 Subject: [PATCH] Create direct room: almost finished, still need to handle showing selected users in search field --- .../java/im/vector/matrix/rx/RxSession.kt | 7 +- .../android/api/session/user/UserService.kt | 12 +++ .../session/user/DefaultUserService.kt | 24 ++++- .../internal/session/user/SearchUserAPI.kt | 35 ++++++ .../internal/session/user/UserModule.kt | 19 ++++ .../internal/session/user/model/SearchUser.kt | 27 +++++ .../session/user/model/SearchUserTask.kt | 47 ++++++++ .../session/user/model/SearchUsersParams.kt | 31 ++++++ .../session/user/model/SearchUsersResponse.kt | 14 +++ .../vector/riotx/core/di/ScreenComponent.kt | 11 +- .../vector/riotx/core/di/ViewModelModule.kt | 6 ++ .../core/platform/SimpleFragmentActivity.kt | 2 + .../riotx/core/platform/VectorBaseActivity.kt | 22 ++++ .../riotx/core/platform/VectorBaseFragment.kt | 2 +- .../riotx/core/utils/DefaultSubscriber.kt | 32 ++++++ .../settings/KeysBackupManageActivity.kt | 1 + .../riotx/features/home/HomeActivity.kt | 17 --- .../createdirect/CreateDirectRoomActions.kt | 8 +- .../createdirect/CreateDirectRoomActivity.kt | 74 ++++++++++++- .../CreateDirectRoomController.kt | 50 ++++++++- .../CreateDirectRoomDirectoryUsersFragment.kt | 93 ++++++++++++++++ .../createdirect/CreateDirectRoomFragment.kt | 38 +++++-- .../CreateDirectRoomNavigationViewModel.kt | 22 ++++ .../createdirect/CreateDirectRoomUserItem.kt | 16 ++- .../createdirect/CreateDirectRoomViewModel.kt | 67 +++++++++--- .../createdirect/CreateDirectRoomViewState.kt | 7 +- .../home/room/detail/RoomDetailViewModel.kt | 11 +- .../features/navigation/DefaultNavigator.kt | 1 - .../layout/fragment_create_direct_room.xml | 12 ++- ...ent_create_direct_room_directory_users.xml | 100 ++++++++++++++++++ .../layout/item_create_direct_room_user.xml | 29 +++-- .../res/menu/vector_create_direct_room.xml | 6 +- 32 files changed, 766 insertions(+), 77 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/SearchUserAPI.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/model/SearchUser.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/model/SearchUserTask.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/model/SearchUsersParams.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/model/SearchUsersResponse.kt create mode 100644 vector/src/main/java/im/vector/riotx/core/utils/DefaultSubscriber.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomDirectoryUsersFragment.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomNavigationViewModel.kt create mode 100644 vector/src/main/res/layout/fragment_create_direct_room_directory_users.xml 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 32d7ab54..4126ff6f 100644 --- a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt +++ b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt @@ -23,7 +23,6 @@ import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams import im.vector.matrix.android.api.session.sync.SyncState import im.vector.matrix.android.api.session.user.model.User -import io.reactivex.Completable import io.reactivex.Observable import io.reactivex.Single @@ -53,6 +52,12 @@ class RxSession(private val session: Session) { session.createRoom(roomParams, MatrixCallbackSingle(it)).toSingle(it) } + fun searchUsersDirectory(search: String, + limit: Int, + excludedUserIds: Set): Single> = Single.create { + session.searchUsersDirectory(search, limit, excludedUserIds, MatrixCallbackSingle(it)).toSingle(it) + } + } fun Session.rx(): RxSession { 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 74dc444f..292c90ef 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 @@ -17,7 +17,9 @@ package im.vector.matrix.android.api.session.user import androidx.lifecycle.LiveData +import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.session.user.model.User +import im.vector.matrix.android.api.util.Cancelable /** * This interface defines methods to get users. It's implemented at the session level. @@ -31,6 +33,16 @@ interface UserService { */ fun getUser(userId: String): User? + /** + * Search list of users on server directory. + * @param search the searched term + * @param limit the max number of users to return + * @param excludedUserIds the user ids to filter from the search + * @param callback the async callback + * @return Cancelable + */ + fun searchUsersDirectory(search: String, limit: Int, excludedUserIds: Set, callback: MatrixCallback>): Cancelable + /** * Observe a live user from a userId * @param userId the userId to look for. 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 7eaf966a..1db73c84 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 @@ -19,20 +19,25 @@ package im.vector.matrix.android.internal.session.user import androidx.lifecycle.LiveData import androidx.lifecycle.Transformations import com.zhuinden.monarchy.Monarchy +import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.session.user.UserService import im.vector.matrix.android.api.session.user.model.User +import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.internal.database.RealmLiveData import im.vector.matrix.android.internal.database.mapper.asDomain -import im.vector.matrix.android.internal.database.model.RoomSummaryEntity -import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields import im.vector.matrix.android.internal.database.model.UserEntity import im.vector.matrix.android.internal.database.model.UserEntityFields import im.vector.matrix.android.internal.database.query.where -import im.vector.matrix.android.internal.session.SessionScope +import im.vector.matrix.android.internal.session.user.model.SearchUserTask +import im.vector.matrix.android.internal.task.TaskExecutor +import im.vector.matrix.android.internal.task.configureWith +import im.vector.matrix.android.internal.task.toConfigurableTask import im.vector.matrix.android.internal.util.fetchCopied import javax.inject.Inject -internal class DefaultUserService @Inject constructor(private val monarchy: Monarchy) : UserService { +internal class DefaultUserService @Inject constructor(private val monarchy: Monarchy, + private val searchUserTask: SearchUserTask, + private val taskExecutor: TaskExecutor) : UserService { override fun getUser(userId: String): User? { val userEntity = monarchy.fetchCopied { UserEntity.where(it, userId).findFirst() } @@ -62,4 +67,15 @@ internal class DefaultUserService @Inject constructor(private val monarchy: Mona { it.asDomain() } ) } + + override fun searchUsersDirectory(search: String, + limit: Int, + excludedUserIds: Set, + callback: MatrixCallback>): Cancelable { + val params = SearchUserTask.Params(limit, search, excludedUserIds) + return searchUserTask + .configureWith(params) + .dispatchTo(callback) + .executeBy(taskExecutor) + } } \ No newline at end of file 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 new file mode 100644 index 00000000..aa4d50df --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/SearchUserAPI.kt @@ -0,0 +1,35 @@ +/* + * 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.matrix.android.internal.session.user + +import im.vector.matrix.android.internal.network.NetworkConstants.URI_API_PREFIX_PATH_R0 +import im.vector.matrix.android.internal.session.user.model.SearchUsersParams +import im.vector.matrix.android.internal.session.user.model.SearchUsersRequestResponse +import retrofit2.Call +import retrofit2.http.Body +import retrofit2.http.POST + +internal interface SearchUserAPI { + + /** + * Perform a user search. + * + * @param searchUsersParams the search params. + */ + @POST(URI_API_PREFIX_PATH_R0 + "user_directory/search") + fun searchUsers(@Body searchUsersParams: SearchUsersParams): Call +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/UserModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/UserModule.kt index 00368dfa..46ae4e38 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/UserModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/UserModule.kt @@ -18,12 +18,31 @@ package im.vector.matrix.android.internal.session.user import dagger.Binds import dagger.Module +import dagger.Provides import im.vector.matrix.android.api.session.user.UserService +import im.vector.matrix.android.internal.session.SessionScope +import im.vector.matrix.android.internal.session.sync.SyncAPI +import im.vector.matrix.android.internal.session.user.model.DefaultSearchUserTask +import im.vector.matrix.android.internal.session.user.model.SearchUserTask +import retrofit2.Retrofit @Module internal abstract class UserModule { + @Module + companion object { + @Provides + @JvmStatic + @SessionScope + fun providesSearchUserAPI(retrofit: Retrofit): SearchUserAPI { + return retrofit.create(SearchUserAPI::class.java) + } + } + @Binds abstract fun bindUserService(userService: DefaultUserService): UserService + @Binds + abstract fun bindSearchUserTask(searchUserTask: DefaultSearchUserTask): SearchUserTask + } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/model/SearchUser.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/model/SearchUser.kt new file mode 100644 index 00000000..da447830 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/model/SearchUser.kt @@ -0,0 +1,27 @@ +/* + * 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.matrix.android.internal.session.user.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +internal data class SearchUser( + @Json(name = "user_id") val userId: String, + @Json(name = "display_name") val displayName: String? = null, + @Json(name = "avatar_url") val avatarUrl: String? = null +) \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/model/SearchUserTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/model/SearchUserTask.kt new file mode 100644 index 00000000..85264dba --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/model/SearchUserTask.kt @@ -0,0 +1,47 @@ +/* + * 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.matrix.android.internal.session.user.model + +import arrow.core.Try +import im.vector.matrix.android.api.session.user.model.User +import im.vector.matrix.android.internal.network.executeRequest +import im.vector.matrix.android.internal.session.user.SearchUserAPI +import im.vector.matrix.android.internal.task.Task +import javax.inject.Inject + +internal interface SearchUserTask : Task> { + + data class Params( + val limit: Int, + val search: String, + val excludedUserIds: Set + ) +} + +internal class DefaultSearchUserTask @Inject constructor(private val searchUserAPI: SearchUserAPI) : SearchUserTask { + + override suspend fun execute(params: SearchUserTask.Params): Try> { + return executeRequest { + apiCall = searchUserAPI.searchUsers(SearchUsersParams(params.search, params.limit)) + }.map { response -> + response.users.map { + User(it.userId, it.displayName, it.avatarUrl) + } + } + } + +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/model/SearchUsersParams.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/model/SearchUsersParams.kt new file mode 100644 index 00000000..6ea689e5 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/model/SearchUsersParams.kt @@ -0,0 +1,31 @@ +/* + * 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.matrix.android.internal.session.user.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * Class representing an user search parameters + */ +@JsonClass(generateAdapter = true) +internal data class SearchUsersParams( + // the searched term + @Json(name = "search_term") val searchTerm: String, + // set a limit to the request response + @Json(name = "limit") val limit: Int +) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/model/SearchUsersResponse.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/model/SearchUsersResponse.kt new file mode 100644 index 00000000..b0a8f937 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/model/SearchUsersResponse.kt @@ -0,0 +1,14 @@ +package im.vector.matrix.android.internal.session.user.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * Class representing an users search response + */ +@JsonClass(generateAdapter = true) +internal data class SearchUsersRequestResponse( + @Json(name = "limited") val limited: Boolean = false, + @Json(name = "results") val users: List = emptyList() +) + 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 0ff348b5..90284011 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 @@ -36,6 +36,8 @@ import im.vector.riotx.features.home.HomeActivity import im.vector.riotx.features.home.HomeDetailFragment import im.vector.riotx.features.home.HomeDrawerFragment import im.vector.riotx.features.home.HomeModule +import im.vector.riotx.features.home.createdirect.CreateDirectRoomActivity +import im.vector.riotx.features.home.createdirect.CreateDirectRoomDirectoryUsersFragment import im.vector.riotx.features.home.createdirect.CreateDirectRoomFragment import im.vector.riotx.features.home.group.GroupListFragment import im.vector.riotx.features.home.room.detail.RoomDetailFragment @@ -46,6 +48,7 @@ import im.vector.riotx.features.invite.VectorInviteView import im.vector.riotx.features.login.LoginActivity import im.vector.riotx.features.media.ImageMediaViewerActivity import im.vector.riotx.features.media.VideoMediaViewerActivity +import im.vector.riotx.features.navigation.Navigator import im.vector.riotx.features.rageshake.BugReportActivity import im.vector.riotx.features.rageshake.BugReporter import im.vector.riotx.features.rageshake.RageShake @@ -74,6 +77,8 @@ interface ScreenComponent { fun rageShake(): RageShake + fun navigator(): Navigator + fun inject(activity: HomeActivity) fun inject(roomDetailFragment: RoomDetailFragment) @@ -154,7 +159,11 @@ interface ScreenComponent { fun inject(pushGatewaysFragment: PushGatewaysFragment) - fun inject(createDirectRoomFragment: CreateDirectRoomFragment) + fun inject(createDirectRoomKnownUsersFragment: CreateDirectRoomFragment) + + fun inject(createDirectRoomDirectoryUsersFragment: CreateDirectRoomDirectoryUsersFragment) + + fun inject(createDirectRoomActivity: CreateDirectRoomActivity) @Component.Factory interface Factory { 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 c21a6c8f..80410f87 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,6 +30,7 @@ import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupSettingsVie import im.vector.riotx.features.crypto.keysbackup.setup.KeysBackupSetupSharedViewModel import im.vector.riotx.features.crypto.verification.SasVerificationViewModel import im.vector.riotx.features.home.* +import im.vector.riotx.features.home.createdirect.CreateDirectRoomNavigationViewModel import im.vector.riotx.features.home.createdirect.CreateDirectRoomViewModel import im.vector.riotx.features.home.createdirect.CreateDirectRoomViewModel_AssistedFactory import im.vector.riotx.features.home.group.GroupListViewModel @@ -118,6 +119,11 @@ interface ViewModelModule { @ViewModelKey(ConfigurationViewModel::class) fun bindConfigurationViewModel(viewModel: ConfigurationViewModel): ViewModel + @Binds + @IntoMap + @ViewModelKey(CreateDirectRoomNavigationViewModel::class) + fun bindCreateDirectRoomNavigationViewModel(viewModel: CreateDirectRoomNavigationViewModel): ViewModel + /** * Below are bindings for the MvRx view models (which extend VectorViewModel). Will be the only usage in the future. */ 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 546937da..ff301389 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 @@ -18,6 +18,7 @@ package im.vector.riotx.core.platform import android.view.View import android.widget.ProgressBar import android.widget.TextView +import androidx.annotation.CallSuper import androidx.core.view.isGone import androidx.core.view.isVisible import butterknife.BindView @@ -46,6 +47,7 @@ abstract class SimpleFragmentActivity : VectorBaseActivity() { @Inject lateinit var session: Session + @CallSuper override fun injectWith(injector: ScreenComponent) { session = injector.session() } diff --git a/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseActivity.kt b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseActivity.kt index 92f72de6..e9c60942 100644 --- a/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseActivity.kt +++ b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseActivity.kt @@ -26,6 +26,7 @@ import androidx.annotation.* import androidx.appcompat.widget.Toolbar import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.view.isVisible +import androidx.fragment.app.FragmentManager import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProviders @@ -40,6 +41,7 @@ import im.vector.riotx.R import im.vector.riotx.core.di.* import im.vector.riotx.core.utils.toast import im.vector.riotx.features.configuration.VectorConfiguration +import im.vector.riotx.features.navigation.Navigator import im.vector.riotx.features.rageshake.BugReportActivity import im.vector.riotx.features.rageshake.BugReporter import im.vector.riotx.features.rageshake.RageShake @@ -70,6 +72,7 @@ abstract class VectorBaseActivity : BaseMvRxActivity(), HasScreenInjector { private lateinit var configurationViewModel: ConfigurationViewModel protected lateinit var bugReporter: BugReporter private lateinit var rageShake: RageShake + protected lateinit var navigator: Navigator private var unBinder: Unbinder? = null @@ -121,6 +124,7 @@ abstract class VectorBaseActivity : BaseMvRxActivity(), HasScreenInjector { configurationViewModel = ViewModelProviders.of(this, viewModelFactory).get(ConfigurationViewModel::class.java) bugReporter = screenComponent.bugReporter() rageShake = screenComponent.rageShake() + navigator = screenComponent.navigator() configurationViewModel.activityRestarter.observe(this, Observer { if (!it.hasBeenHandled) { // Recreate the Activity because configuration has changed @@ -262,6 +266,24 @@ abstract class VectorBaseActivity : BaseMvRxActivity(), HasScreenInjector { return super.onOptionsItemSelected(item) } + protected fun recursivelyDispatchOnBackPressed(fm: FragmentManager): Boolean { + // if (fm.backStackEntryCount == 0) + // return false + + val reverseOrder = fm.fragments.filter { it is OnBackPressed }.reversed() + for (f in reverseOrder) { + val handledByChildFragments = recursivelyDispatchOnBackPressed(f.childFragmentManager) + if (handledByChildFragments) { + return true + } + val backPressable = f as OnBackPressed + if (backPressable.onBackPressed()) { + return true + } + } + return false + } + /* ========================================================================================== * PROTECTED METHODS * ========================================================================================== */ diff --git a/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseFragment.kt b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseFragment.kt index ec5e419d..aac19d80 100644 --- a/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseFragment.kt +++ b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseFragment.kt @@ -65,7 +65,7 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), OnBackPressed, HasScreen override fun onAttach(context: Context) { screenComponent = DaggerScreenComponent.factory().create(vectorBaseActivity.getVectorComponent(), vectorBaseActivity) - navigator = vectorBaseActivity.getVectorComponent().navigator() + navigator = screenComponent.navigator() viewModelFactory = screenComponent.viewModelFactory() injectWith(injector()) super.onAttach(context) diff --git a/vector/src/main/java/im/vector/riotx/core/utils/DefaultSubscriber.kt b/vector/src/main/java/im/vector/riotx/core/utils/DefaultSubscriber.kt new file mode 100644 index 00000000..05415991 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/core/utils/DefaultSubscriber.kt @@ -0,0 +1,32 @@ +/* + * 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.core.utils + +import io.reactivex.Completable +import io.reactivex.Single +import io.reactivex.disposables.Disposable +import io.reactivex.functions.Consumer +import io.reactivex.internal.functions.Functions +import timber.log.Timber + +fun Single.subscribeLogError(): Disposable { + return subscribe(Functions.emptyConsumer(), Consumer { Timber.e(it) }) +} + +fun Completable.subscribeLogError(): Disposable { + return subscribe({}, { Timber.e(it) }) +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/settings/KeysBackupManageActivity.kt b/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/settings/KeysBackupManageActivity.kt index 32e08597..69ad2cd1 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/settings/KeysBackupManageActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/settings/KeysBackupManageActivity.kt @@ -43,6 +43,7 @@ class KeysBackupManageActivity : SimpleFragmentActivity() { @Inject lateinit var keysBackupSettingsViewModelFactory: KeysBackupSettingsViewModel.Factory override fun injectWith(injector: ScreenComponent) { + super.injectWith(injector) injector.inject(this) } 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 4ec2c0ad..07d9416c 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,7 +65,6 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable { @Inject lateinit var activeSessionHolder: ActiveSessionHolder @Inject lateinit var homeActivityViewModelFactory: HomeActivityViewModel.Factory @Inject lateinit var homeNavigator: HomeNavigator - @Inject lateinit var navigator: Navigator @Inject lateinit var vectorUncaughtExceptionHandler: VectorUncaughtExceptionHandler @Inject lateinit var pushManager: PushersManager @Inject lateinit var notificationDrawerManager: NotificationDrawerManager @@ -214,23 +213,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable { } } - private fun recursivelyDispatchOnBackPressed(fm: FragmentManager): Boolean { - // if (fm.backStackEntryCount == 0) - // return false - val reverseOrder = fm.fragments.filter { it is OnBackPressed }.reversed() - for (f in reverseOrder) { - val handledByChildFragments = recursivelyDispatchOnBackPressed(f.childFragmentManager) - if (handledByChildFragments) { - return true - } - val backPressable = f as OnBackPressed - if (backPressable.onBackPressed()) { - return true - } - } - return false - } companion object { diff --git a/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomActions.kt b/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomActions.kt index a44be66a..50f99a6d 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomActions.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomActions.kt @@ -16,11 +16,15 @@ package im.vector.riotx.features.home.createdirect +import im.vector.matrix.android.api.session.user.model.User + sealed class CreateDirectRoomActions { object CreateRoomAndInviteSelectedUsers : CreateDirectRoomActions() data class FilterKnownUsers(val value: String) : CreateDirectRoomActions() - object ClearFilterKnownUsers: CreateDirectRoomActions() - object SelectAddByMatrixId : CreateDirectRoomActions() + data class SearchDirectoryUsers(val value: String) : CreateDirectRoomActions() + object ClearFilterKnownUsers : CreateDirectRoomActions() + data class SelectUser(val user: User) : CreateDirectRoomActions() + data class RemoveSelectedUser(val user: User) : CreateDirectRoomActions() } diff --git a/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomActivity.kt b/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomActivity.kt index 4ecb5f9f..7b41c226 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomActivity.kt @@ -20,20 +20,84 @@ package im.vector.riotx.features.home.createdirect import android.content.Context import android.content.Intent +import android.os.Bundle +import android.view.View +import androidx.lifecycle.ViewModelProviders +import com.airbnb.mvrx.Fail +import com.airbnb.mvrx.Loading +import com.airbnb.mvrx.Success +import com.airbnb.mvrx.viewModel +import com.google.android.gms.common.GooglePlayServicesNotAvailableException import im.vector.riotx.R +import im.vector.riotx.core.di.ScreenComponent import im.vector.riotx.core.extensions.addFragment -import im.vector.riotx.core.platform.VectorBaseActivity +import im.vector.riotx.core.extensions.addFragmentToBackstack +import im.vector.riotx.core.extensions.observeEvent +import im.vector.riotx.core.platform.SimpleFragmentActivity +import im.vector.riotx.core.platform.WaitingViewData +import kotlinx.android.synthetic.main.activity.* +import javax.inject.Inject -class CreateDirectRoomActivity : VectorBaseActivity() { +class CreateDirectRoomActivity : SimpleFragmentActivity() { - override fun getLayoutRes() = R.layout.activity_simple + sealed class Navigation { + object UsersDirectory : Navigation() + object Close : Navigation() + object Previous : Navigation() + } - override fun initUiAndData() { + private val viewModel: CreateDirectRoomViewModel by viewModel() + lateinit var navigationViewModel: CreateDirectRoomNavigationViewModel + @Inject lateinit var createDirectRoomViewModelFactory: CreateDirectRoomViewModel.Factory + + + override fun injectWith(injector: ScreenComponent) { + super.injectWith(injector) + injector.inject(this) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + toolbar.visibility = View.GONE + navigationViewModel = ViewModelProviders.of(this, viewModelFactory).get(CreateDirectRoomNavigationViewModel::class.java) + navigationViewModel.navigateTo.observeEvent(this) { navigation -> + when (navigation) { + is Navigation.UsersDirectory -> addFragmentToBackstack(CreateDirectRoomDirectoryUsersFragment(), R.id.container) + Navigation.Close -> finish() + Navigation.Previous -> onBackPressed() + } + } if (isFirstCreation()) { - addFragment(CreateDirectRoomFragment(), R.id.simpleFragmentContainer) + addFragment(CreateDirectRoomFragment(), R.id.container) + } + viewModel.subscribe(this) { renderState(it) } + } + + private fun renderState(state: CreateDirectRoomViewState) { + when (state.createAndInviteState) { + is Loading -> renderCreationLoading() + is Success -> renderCreationSuccess(state.createAndInviteState()) + is Fail -> renderCreationFailure(state.createAndInviteState.error) } } + private fun renderCreationLoading() { + updateWaitingView(WaitingViewData(getString(R.string.room_recents_create_room))) + } + + private fun renderCreationFailure(error: Throwable) { + + } + + private fun renderCreationSuccess(roomId: String?) { + // Navigate to freshly created room + if (roomId != null) { + navigator.openRoom(this, roomId) + } + finish() + } + + companion object { fun getIntent(context: Context): Intent { return Intent(context, CreateDirectRoomActivity::class.java) diff --git a/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomController.kt b/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomController.kt index af7de9c6..8e56813a 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomController.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomController.kt @@ -19,14 +19,27 @@ package im.vector.riotx.features.home.createdirect import com.airbnb.epoxy.EpoxyController +import com.airbnb.epoxy.VisibilityState +import com.airbnb.mvrx.Fail +import com.airbnb.mvrx.Incomplete +import com.airbnb.mvrx.Loading +import com.airbnb.mvrx.Success +import com.airbnb.mvrx.Uninitialized +import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.session.user.model.User import im.vector.matrix.android.internal.util.firstLetterOfDisplayName +import im.vector.riotx.core.epoxy.errorWithRetryItem +import im.vector.riotx.core.epoxy.loadingItem +import im.vector.riotx.core.error.ErrorFormatter import im.vector.riotx.features.home.AvatarRenderer import javax.inject.Inject -class CreateDirectRoomController @Inject constructor(private val avatarRenderer: AvatarRenderer) : EpoxyController() { +class CreateDirectRoomController @Inject constructor(private val avatarRenderer: AvatarRenderer, + private val errorFormatter: ErrorFormatter) : EpoxyController() { private var state: CreateDirectRoomViewState? = null + var displayMode = CreateDirectRoomViewState.DisplayMode.KNOWN_USERS + var callback: Callback? = null init { @@ -40,10 +53,36 @@ class CreateDirectRoomController @Inject constructor(private val avatarRenderer: override fun buildModels() { val currentState = state ?: return - val knownUsers = currentState.knownUsers() ?: return + val asyncUsers = if (displayMode == CreateDirectRoomViewState.DisplayMode.DIRECTORY_USERS) { + currentState.directoryUsers + } else { + currentState.knownUsers + } + when (asyncUsers) { + is Incomplete -> renderLoading() + is Success -> renderUsers(asyncUsers(), currentState.selectedUsers) + is Fail -> renderFailure(asyncUsers.error) + } + } + private fun renderLoading() { + loadingItem { + id("loading") + } + } + + private fun renderFailure(failure: Throwable) { + errorWithRetryItem { + id("error") + text(errorFormatter.toHumanReadable(failure)) + listener { callback?.retryDirectoryUsersRequest() } + } + } + + private fun renderUsers(users: List, selectedUsers: Set) { var lastFirstLetter: String? = null - knownUsers.forEach { user -> + users.forEach { user -> + val isSelected = selectedUsers.contains(user) val currentFirstLetter = user.displayName.firstLetterOfDisplayName() val showLetter = currentFirstLetter.isNotEmpty() && lastFirstLetter != currentFirstLetter lastFirstLetter = currentFirstLetter @@ -55,6 +94,7 @@ class CreateDirectRoomController @Inject constructor(private val avatarRenderer: createDirectRoomUserItem { id(user.userId) + selected(isSelected) userId(user.userId) name(user.displayName) avatarUrl(user.avatarUrl) @@ -64,11 +104,13 @@ class CreateDirectRoomController @Inject constructor(private val avatarRenderer: } } } - } interface Callback { fun onItemClick(user: User) + fun retryDirectoryUsersRequest() { + // NO-OP + } } } \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomDirectoryUsersFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomDirectoryUsersFragment.kt new file mode 100644 index 00000000..838c7d6e --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomDirectoryUsersFragment.kt @@ -0,0 +1,93 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.features.home.createdirect + +import android.content.Context +import android.os.Bundle +import android.view.inputmethod.InputMethodManager +import androidx.lifecycle.ViewModelProviders +import com.airbnb.mvrx.activityViewModel +import com.jakewharton.rxbinding3.widget.textChanges +import im.vector.matrix.android.api.session.user.model.User +import im.vector.riotx.R +import im.vector.riotx.core.di.ScreenComponent +import im.vector.riotx.core.platform.VectorBaseFragment +import kotlinx.android.synthetic.main.fragment_create_direct_room_directory_users.* +import javax.inject.Inject + +class CreateDirectRoomDirectoryUsersFragment : VectorBaseFragment(), CreateDirectRoomController.Callback { + + override fun getLayoutResId() = R.layout.fragment_create_direct_room_directory_users + + private val viewModel: CreateDirectRoomViewModel by activityViewModel() + + @Inject lateinit var directRoomController: CreateDirectRoomController + private lateinit var navigationViewModel: CreateDirectRoomNavigationViewModel + + override fun injectWith(injector: ScreenComponent) { + injector.inject(this) + } + + override fun onActivityCreated(savedInstanceState: Bundle?) { + super.onActivityCreated(savedInstanceState) + navigationViewModel = ViewModelProviders.of(requireActivity(), viewModelFactory).get(CreateDirectRoomNavigationViewModel::class.java) + setupRecyclerView() + setupSearchByMatrixIdView() + setupCloseView() + viewModel.subscribe(this) { renderState(it) } + } + + private fun setupRecyclerView() { + recyclerView.setHasFixedSize(true) + directRoomController.callback = this + directRoomController.displayMode = CreateDirectRoomViewState.DisplayMode.DIRECTORY_USERS + recyclerView.setController(directRoomController) + } + + private fun setupSearchByMatrixIdView() { + createDirectRoomSearchById + .textChanges() + .subscribe { + viewModel.handle(CreateDirectRoomActions.SearchDirectoryUsers(it.toString())) + } + .disposeOnDestroy() + createDirectRoomSearchById.requestFocus() + val imm = context?.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager + imm?.showSoftInput(createDirectRoomSearchById, InputMethodManager.SHOW_IMPLICIT) + + } + + private fun setupCloseView() { + createDirectRoomClose.setOnClickListener { + navigationViewModel.goTo(CreateDirectRoomActivity.Navigation.Close) + } + } + + private fun renderState(state: CreateDirectRoomViewState) { + directRoomController.setData(state) + } + + override fun onItemClick(user: User) { + viewModel.handle(CreateDirectRoomActions.SelectUser(user)) + navigationViewModel.goTo(CreateDirectRoomActivity.Navigation.Previous) + } + + override fun retryDirectoryUsersRequest() { + val currentSearch = createDirectRoomSearchById.text.toString() + viewModel.handle(CreateDirectRoomActions.SearchDirectoryUsers(currentSearch)) + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomFragment.kt index eedb5617..e28346a6 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomFragment.kt @@ -20,14 +20,20 @@ package im.vector.riotx.features.home.createdirect import android.os.Bundle import android.view.MenuItem -import com.airbnb.mvrx.fragmentViewModel +import androidx.lifecycle.ViewModelProviders +import com.airbnb.mvrx.Fail +import com.airbnb.mvrx.Loading +import com.airbnb.mvrx.Success +import com.airbnb.mvrx.Uninitialized +import com.airbnb.mvrx.activityViewModel import com.jakewharton.rxbinding3.appcompat.queryTextChanges import im.vector.matrix.android.api.session.user.model.User import im.vector.riotx.R import im.vector.riotx.core.di.ScreenComponent import im.vector.riotx.core.platform.VectorBaseFragment +import im.vector.riotx.features.roomdirectory.RoomDirectoryActivity import kotlinx.android.synthetic.main.fragment_create_direct_room.* -import java.util.concurrent.TimeUnit +import kotlinx.android.synthetic.main.fragment_public_rooms.* import javax.inject.Inject class CreateDirectRoomFragment : VectorBaseFragment(), CreateDirectRoomController.Callback { @@ -36,10 +42,10 @@ class CreateDirectRoomFragment : VectorBaseFragment(), CreateDirectRoomControlle override fun getMenuRes() = R.menu.vector_create_direct_room - private val viewModel: CreateDirectRoomViewModel by fragmentViewModel() + private val viewModel: CreateDirectRoomViewModel by activityViewModel() - @Inject lateinit var createDirectRoomViewModelFactory: CreateDirectRoomViewModel.Factory @Inject lateinit var directRoomController: CreateDirectRoomController + private lateinit var navigationViewModel: CreateDirectRoomNavigationViewModel override fun injectWith(injector: ScreenComponent) { injector.inject(this) @@ -47,27 +53,38 @@ class CreateDirectRoomFragment : VectorBaseFragment(), CreateDirectRoomControlle override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) + navigationViewModel = ViewModelProviders.of(requireActivity(), viewModelFactory).get(CreateDirectRoomNavigationViewModel::class.java) + vectorBaseActivity.setSupportActionBar(createDirectRoomToolbar) setupRecyclerView() setupFilterView() + setupAddByMatrixIdView() + setupCloseView() viewModel.subscribe(this) { renderState(it) } } override fun onOptionsItemSelected(item: MenuItem): Boolean { return when (item.itemId) { - R.id.action_create_room -> { + R.id.action_create_direct_room -> { viewModel.handle(CreateDirectRoomActions.CreateRoomAndInviteSelectedUsers) true } - else -> + else -> super.onOptionsItemSelected(item) } } + private fun setupAddByMatrixIdView() { + addByMatrixId.setOnClickListener { + navigationViewModel.goTo(CreateDirectRoomActivity.Navigation.UsersDirectory) + } + } + private fun setupRecyclerView() { recyclerView.setHasFixedSize(true) // Don't activate animation as we might have way to much item animation when filtering recyclerView.itemAnimator = null directRoomController.callback = this + directRoomController.displayMode = CreateDirectRoomViewState.DisplayMode.KNOWN_USERS recyclerView.setController(directRoomController) } @@ -85,11 +102,18 @@ class CreateDirectRoomFragment : VectorBaseFragment(), CreateDirectRoomControlle .disposeOnDestroy() } + private fun setupCloseView() { + createDirectRoomClose.setOnClickListener { + requireActivity().finish() + } + } + private fun renderState(state: CreateDirectRoomViewState) { + directRoomController.setData(state) } override fun onItemClick(user: User) { - vectorBaseActivity.notImplemented("IMPLEMENT ON USER CLICKED") + viewModel.handle(CreateDirectRoomActions.SelectUser(user)) } } \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomNavigationViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomNavigationViewModel.kt new file mode 100644 index 00000000..442dc23d --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomNavigationViewModel.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.features.home.createdirect + +import im.vector.riotx.core.mvrx.NavigationViewModel +import javax.inject.Inject + +class CreateDirectRoomNavigationViewModel @Inject constructor(): NavigationViewModel() \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomUserItem.kt b/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomUserItem.kt index 57d9347e..33f7c22f 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomUserItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomUserItem.kt @@ -21,12 +21,16 @@ package im.vector.riotx.features.home.createdirect import android.view.View import android.widget.ImageView import android.widget.TextView +import androidx.core.content.ContextCompat import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass +import com.amulyakhare.textdrawable.TextDrawable import im.vector.riotx.R import im.vector.riotx.core.epoxy.VectorEpoxyHolder import im.vector.riotx.core.epoxy.VectorEpoxyModel +import im.vector.riotx.core.resources.ColorProvider import im.vector.riotx.features.home.AvatarRenderer +import im.vector.riotx.features.home.getColorFromUserId @EpoxyModelClass(layout = R.layout.item_create_direct_room_user) abstract class CreateDirectRoomUserItem : VectorEpoxyModel() { @@ -36,6 +40,7 @@ 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) } } \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomViewModel.kt index c9b976cd..87ee8005 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomViewModel.kt @@ -19,22 +19,23 @@ package im.vector.riotx.features.home.createdirect import arrow.core.Option -import com.airbnb.mvrx.FragmentViewModelContext +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.session.user.model.User import im.vector.matrix.rx.rx import im.vector.riotx.core.platform.VectorViewModel import io.reactivex.Observable import io.reactivex.functions.BiFunction -import io.reactivex.subjects.BehaviorSubject import java.util.concurrent.TimeUnit private typealias KnowUsersFilter = String +private typealias DirectoryUsersSearch = String class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted initialState: CreateDirectRoomViewState, @@ -47,35 +48,77 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted } private val knownUsersFilter = BehaviorRelay.createDefault>(Option.empty()) + private val directoryUsersSearch = BehaviorRelay.create() companion object : MvRxViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: CreateDirectRoomViewState): CreateDirectRoomViewModel? { - val fragment: CreateDirectRoomFragment = (viewModelContext as FragmentViewModelContext).fragment() - return fragment.createDirectRoomViewModelFactory.create(state) + val activity: CreateDirectRoomActivity = (viewModelContext as ActivityViewModelContext).activity() + return activity.createDirectRoomViewModelFactory.create(state) } } init { observeKnownUsers() + observeDirectoryUsers() } - fun handle(createDirectRoomActions: CreateDirectRoomActions) { - when (createDirectRoomActions) { + fun handle(action: CreateDirectRoomActions) { + when (action) { is CreateDirectRoomActions.CreateRoomAndInviteSelectedUsers -> createRoomAndInviteSelectedUsers() - is CreateDirectRoomActions.SelectAddByMatrixId -> handleSelectAddByMatrixId() - is CreateDirectRoomActions.FilterKnownUsers -> knownUsersFilter.accept(Option.just(createDirectRoomActions.value)) + is CreateDirectRoomActions.FilterKnownUsers -> knownUsersFilter.accept(Option.just(action.value)) is CreateDirectRoomActions.ClearFilterKnownUsers -> knownUsersFilter.accept(Option.empty()) + is CreateDirectRoomActions.SearchDirectoryUsers -> directoryUsersSearch.accept(action.value) + is CreateDirectRoomActions.SelectUser -> handleSelectUser(action) + is CreateDirectRoomActions.RemoveSelectedUser -> handleRemoveSelectedUser(action) } } - private fun handleSelectAddByMatrixId() { - // TODO + private fun createRoomAndInviteSelectedUsers() = withState { + val isDirect = it.selectedUsers.size == 1 + val roomParams = CreateRoomParams().apply { + invitedUserIds = ArrayList(it.selectedUsers.map { user -> user.userId }) + if (isDirect) { + setDirectMessage() + } + } + session.rx() + .createRoom(roomParams) + .execute { + copy(createAndInviteState = it) + } + .disposeOnClear() } - private fun createRoomAndInviteSelectedUsers() { - // TODO + private fun handleRemoveSelectedUser(action: CreateDirectRoomActions.RemoveSelectedUser) = withState { + val selectedUsers = it.selectedUsers.minusElement(action.user) + setState { copy(selectedUsers = selectedUsers) } + } + + private fun handleSelectUser(action: CreateDirectRoomActions.SelectUser) = withState { + val selectedUsers = if (it.selectedUsers.contains(action.user)) { + it.selectedUsers.minusElement(action.user) + } else { + it.selectedUsers.plus(action.user) + } + setState { copy(selectedUsers = selectedUsers) } + } + + private fun observeDirectoryUsers() { + directoryUsersSearch + .throttleLast(300, TimeUnit.MILLISECONDS) + .switchMapSingle { search -> + session.rx() + .searchUsersDirectory(search, 50, emptySet()) + .map { users -> + users.sortedBy { it.displayName } + } + } + .execute { async -> + copy(directoryUsers = async) + } + } private fun observeKnownUsers() { diff --git a/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomViewState.kt b/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomViewState.kt index 1ad1f888..56607802 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomViewState.kt @@ -24,14 +24,15 @@ import com.airbnb.mvrx.Uninitialized import im.vector.matrix.android.api.session.user.model.User data class CreateDirectRoomViewState( - val displayMode: DisplayMode = DisplayMode.KNOWN_USERS, val knownUsers: Async> = Uninitialized, - val filteredKnownUsers: Async> = Uninitialized + val directoryUsers: Async> = Uninitialized, + val selectedUsers: Set = emptySet(), + val createAndInviteState: Async = Uninitialized ) : MvRxState { enum class DisplayMode { KNOWN_USERS, - MATRIX_ID_USERS + DIRECTORY_USERS } } \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index a15eae5b..14de38bc 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -43,6 +43,7 @@ import im.vector.riotx.core.intent.getFilenameFromUri import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.resources.UserPreferencesProvider import im.vector.riotx.core.utils.LiveEvent +import im.vector.riotx.core.utils.subscribeLogError import im.vector.riotx.features.command.CommandParser import im.vector.riotx.features.command.ParsedCommand import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineDisplayableEvents @@ -94,7 +95,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro observeRoomSummary() observeEventDisplayedActions() observeInvitationState() - cancelableBag += room.loadRoomMembersIfNeeded() + room.rx().loadRoomMembersIfNeeded().subscribeLogError().disposeOnClear() timeline.start() setState { copy(timeline = this@RoomDetailViewModel.timeline) } } @@ -235,12 +236,12 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro } else { val messageContent: MessageContent? = state.sendMode.timelineEvent.annotations?.editSummary?.aggregatedContent.toModel() - ?: state.sendMode.timelineEvent.root.getClearContent().toModel() + ?: state.sendMode.timelineEvent.root.getClearContent().toModel() val existingBody = messageContent?.body ?: "" if (existingBody != action.text) { room.editTextMessage(state.sendMode.timelineEvent.root.eventId - ?: "", messageContent?.type - ?: MessageType.MSGTYPE_TEXT, action.text, action.autoMarkdown) + ?: "", messageContent?.type + ?: MessageType.MSGTYPE_TEXT, action.text, action.autoMarkdown) } else { Timber.w("Same message content, do not send edition") } @@ -255,7 +256,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro is SendMode.QUOTE -> { val messageContent: MessageContent? = state.sendMode.timelineEvent.annotations?.editSummary?.aggregatedContent.toModel() - ?: state.sendMode.timelineEvent.root.getClearContent().toModel() + ?: state.sendMode.timelineEvent.root.getClearContent().toModel() val textMsg = messageContent?.body val finalText = legacyRiotQuoteText(textMsg, action.text) 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 496b4e47..1428a0ac 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 @@ -26,7 +26,6 @@ import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupManageActiv import im.vector.riotx.features.crypto.keysbackup.setup.KeysBackupSetupActivity import im.vector.riotx.features.debug.DebugMenuActivity import im.vector.riotx.features.home.createdirect.CreateDirectRoomActivity -import im.vector.riotx.features.home.createdirect.CreateDirectRoomFragment 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 diff --git a/vector/src/main/res/layout/fragment_create_direct_room.xml b/vector/src/main/res/layout/fragment_create_direct_room.xml index 78955389..3899d3d2 100644 --- a/vector/src/main/res/layout/fragment_create_direct_room.xml +++ b/vector/src/main/res/layout/fragment_create_direct_room.xml @@ -10,7 +10,7 @@ android:layout_height="match_parent"> + app:layout_constraintTop_toBottomOf="@+id/createDirectRoomToolbar"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vector/src/main/res/layout/item_create_direct_room_user.xml b/vector/src/main/res/layout/item_create_direct_room_user.xml index de1afd5f..532f6c15 100644 --- a/vector/src/main/res/layout/item_create_direct_room_user.xml +++ b/vector/src/main/res/layout/item_create_direct_room_user.xml @@ -7,18 +7,33 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:background="?riotx_background" + android:foreground="?attr/selectableItemBackground" android:gravity="center_vertical" android:orientation="horizontal" android:padding="8dp"> - + app:layout_constraintTop_toTopOf="parent"> + + + + + diff --git a/vector/src/main/res/menu/vector_create_direct_room.xml b/vector/src/main/res/menu/vector_create_direct_room.xml index 42a21da9..8c6eab1c 100755 --- a/vector/src/main/res/menu/vector_create_direct_room.xml +++ b/vector/src/main/res/menu/vector_create_direct_room.xml @@ -1,11 +1,9 @@ + xmlns:app="http://schemas.android.com/apk/res-auto">