From 52eec061102e937286123a72b7fd28b74a1af7aa Mon Sep 17 00:00:00 2001 From: onurays Date: Fri, 12 Jun 2020 16:39:22 +0300 Subject: [PATCH] Updating room avatar is implemented. --- .../main/java/im/vector/matrix/rx/RxRoom.kt | 5 + .../api/session/room/state/StateService.kt | 6 + .../internal/session/SessionComponent.kt | 3 + .../session/content/UploadAvatarWorker.kt | 128 ++++++++++++++++++ .../session/room/state/DefaultStateService.kt | 55 +++++++- .../core/epoxy/profiles/ProfileActionItem.kt | 15 ++ .../epoxy/profiles/ProfileItemExtensions.kt | 8 +- .../settings/RoomSettingsAction.kt | 3 +- .../settings/RoomSettingsController.kt | 16 +++ .../settings/RoomSettingsFragment.kt | 44 ++++-- .../settings/RoomSettingsViewModel.kt | 30 ++-- .../settings/RoomSettingsViewState.kt | 2 + 12 files changed, 293 insertions(+), 22 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadAvatarWorker.kt 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 20f794860e..a6c135c13f 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 @@ -16,6 +16,7 @@ package im.vector.matrix.rx +import android.net.Uri import im.vector.matrix.android.api.query.QueryStringValue import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.room.Room @@ -109,6 +110,10 @@ class RxRoom(private val room: Room) { fun updateName(name: String): Completable = completableBuilder { room.updateName(name, it) } + + fun updateAvatar(avatarUri: Uri, fileName: String): Completable = completableBuilder { + room.updateAvatar(avatarUri, fileName, it) + } } fun Room.rx(): RxRoom { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/state/StateService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/state/StateService.kt index 91a193fba7..64066640b4 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/state/StateService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/state/StateService.kt @@ -16,6 +16,7 @@ package im.vector.matrix.android.api.session.room.state +import android.net.Uri import androidx.lifecycle.LiveData import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.query.QueryStringValue @@ -36,6 +37,11 @@ interface StateService { */ fun updateName(name: String, callback: MatrixCallback): Cancelable + /** + * Update the avatar of the room + */ + fun updateAvatar(avatarUri: Uri, fileName: String, callback: MatrixCallback): Cancelable + fun sendStateEvent(eventType: String, stateKey: String?, body: JsonDict, callback: MatrixCallback): Cancelable fun getStateEvent(eventType: String, stateKey: QueryStringValue = QueryStringValue.NoCondition): Event? diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionComponent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionComponent.kt index b95595ed23..4dc9fc0305 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionComponent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionComponent.kt @@ -32,6 +32,7 @@ import im.vector.matrix.android.internal.session.account.AccountModule import im.vector.matrix.android.internal.session.cache.CacheModule import im.vector.matrix.android.internal.session.call.CallModule import im.vector.matrix.android.internal.session.content.ContentModule +import im.vector.matrix.android.internal.session.content.UploadAvatarWorker import im.vector.matrix.android.internal.session.content.UploadContentWorker import im.vector.matrix.android.internal.session.filter.FilterModule import im.vector.matrix.android.internal.session.group.GetGroupDataWorker @@ -117,6 +118,8 @@ internal interface SessionComponent { fun inject(worker: UploadContentWorker) + fun inject(worker: UploadAvatarWorker) + fun inject(worker: SyncWorker) fun inject(worker: AddHttpPusherWorker) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadAvatarWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadAvatarWorker.kt new file mode 100644 index 0000000000..8becdfa05c --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadAvatarWorker.kt @@ -0,0 +1,128 @@ +/* + * 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.session.content + +import android.content.Context +import android.net.Uri +import androidx.work.CoroutineWorker +import androidx.work.WorkerParameters +import com.squareup.moshi.JsonClass +import im.vector.matrix.android.internal.worker.SessionWorkerParams +import im.vector.matrix.android.internal.worker.WorkerParamsFactory +import im.vector.matrix.android.internal.worker.getSessionComponent +import timber.log.Timber +import javax.inject.Inject + +/** + * Possible previous worker: None + * Possible next worker : None + */ +internal class UploadAvatarWorker(val context: Context, params: WorkerParameters) : CoroutineWorker(context, params) { + + @JsonClass(generateAdapter = true) + internal data class Params( + override val sessionId: String, + val queryUri: Uri, + val fileName: String, + override val lastFailureMessage: String? = null + ) : SessionWorkerParams + + @JsonClass(generateAdapter = true) + internal data class OutputParams( + override val sessionId: String, + val imageUrl: String? = null, + override val lastFailureMessage: String? = null + ) : SessionWorkerParams + + @Inject lateinit var fileUploader: FileUploader + + override suspend fun doWork(): Result { + val params = WorkerParamsFactory.fromData(inputData) + ?: return Result.success() + .also { Timber.e("Unable to parse work parameters") } + + Timber.v("Starting upload media work with params $params") + + if (params.lastFailureMessage != null) { + // Transmit the error + return Result.success(inputData) + .also { Timber.e("Work cancelled due to input error from parent") } + } + + // Just defensive code to ensure that we never have an uncaught exception that could break the queue + return try { + internalDoWork(params) + } catch (failure: Throwable) { + Timber.e(failure) + handleFailure(params, failure) + } + } + + private suspend fun internalDoWork(params: Params): Result { + val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success() + sessionComponent.inject(this) + + try { + val inputStream = context.contentResolver.openInputStream(params.queryUri) + ?: return Result.success( + WorkerParamsFactory.toData( + params.copy( + lastFailureMessage = "Cannot openInputStream for file: " + params.queryUri.toString() + ) + ) + ) + + inputStream.use { + return try { + Timber.v("## UploadAvatarWorker - Uploading avatar...") + val response = fileUploader.uploadByteArray(inputStream.readBytes(), params.fileName, "image/jpeg") + Timber.v("## UploadAvatarWorker - Uploadeded avatar: ${response.contentUri}") + handleSuccess(params, response.contentUri) + } catch (t: Throwable) { + Timber.e(t, "## UploadAvatarWorker - Uploading avatar failed...") + handleFailure(params, t) + } + } + } catch (e: Exception) { + Timber.e(e) + return Result.success( + WorkerParamsFactory.toData( + params.copy( + lastFailureMessage = e.localizedMessage + ) + ) + ) + } + } + + private fun handleFailure(params: Params, failure: Throwable): Result { + return Result.success( + WorkerParamsFactory.toData( + params.copy( + lastFailureMessage = failure.localizedMessage + ) + ) + ) + } + + private fun handleSuccess(params: Params, imageUrl: String): Result { + Timber.v("handleSuccess $imageUrl, work is stopped $isStopped") + + val sendParams = OutputParams(params.sessionId, imageUrl, params.lastFailureMessage) + return Result.success(WorkerParamsFactory.toData(sendParams)) + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/state/DefaultStateService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/state/DefaultStateService.kt index ed077d76b9..ebfc3398b2 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/state/DefaultStateService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/state/DefaultStateService.kt @@ -16,7 +16,10 @@ package im.vector.matrix.android.internal.session.room.state +import android.net.Uri import androidx.lifecycle.LiveData +import androidx.work.BackoffPolicy +import androidx.work.ExistingWorkPolicy import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import im.vector.matrix.android.api.MatrixCallback @@ -25,15 +28,29 @@ import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.room.state.StateService import im.vector.matrix.android.api.util.Cancelable +import im.vector.matrix.android.api.util.CancelableBag import im.vector.matrix.android.api.util.JsonDict import im.vector.matrix.android.api.util.Optional +import im.vector.matrix.android.internal.di.SessionId +import im.vector.matrix.android.internal.di.WorkManagerProvider +import im.vector.matrix.android.internal.session.content.UploadAvatarWorker import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.configureWith +import im.vector.matrix.android.internal.util.CancelableWork +import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers +import im.vector.matrix.android.internal.worker.WorkerParamsFactory +import kotlinx.coroutines.launch +import java.util.concurrent.TimeUnit + +private const val UPLOAD_AVATAR_WORK = "UPLOAD_AVATAR_WORK" internal class DefaultStateService @AssistedInject constructor(@Assisted private val roomId: String, private val stateEventDataSource: StateEventDataSource, private val taskExecutor: TaskExecutor, - private val sendStateTask: SendStateTask + private val sendStateTask: SendStateTask, + @SessionId private val sessionId: String, + private val workManagerProvider: WorkManagerProvider, + private val coroutineDispatchers: MatrixCoroutineDispatchers ) : StateService { @AssistedInject.Factory @@ -93,4 +110,40 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private stateKey = null ) } + + override fun updateAvatar(avatarUri: Uri, fileName: String, callback: MatrixCallback): Cancelable { + val cancelableBag = CancelableBag() + val workerParams = UploadAvatarWorker.Params(sessionId, avatarUri, fileName) + val workerData = WorkerParamsFactory.toData(workerParams) + + val uploadAvatarWork = workManagerProvider.matrixOneTimeWorkRequestBuilder() + .setConstraints(WorkManagerProvider.workConstraints) + .setInputData(workerData) + .setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY, TimeUnit.MILLISECONDS) + .build() + + workManagerProvider.workManager + .beginUniqueWork("${roomId}_$UPLOAD_AVATAR_WORK", ExistingWorkPolicy.REPLACE, uploadAvatarWork) + .enqueue() + + cancelableBag.add(CancelableWork(workManagerProvider.workManager, uploadAvatarWork.id)) + + taskExecutor.executorScope.launch(coroutineDispatchers.main) { + workManagerProvider.workManager.getWorkInfoByIdLiveData(uploadAvatarWork.id) + .observeForever { info -> + if (info != null && info.state.isFinished) { + val result = WorkerParamsFactory.fromData(info.outputData) + cancelableBag.add( + sendStateEvent( + eventType = EventType.STATE_ROOM_AVATAR, + body = mapOf("url" to result?.imageUrl!!), + callback = callback, + stateKey = null + ) + ) + } + } + } + return cancelableBag + } } diff --git a/vector/src/main/java/im/vector/riotx/core/epoxy/profiles/ProfileActionItem.kt b/vector/src/main/java/im/vector/riotx/core/epoxy/profiles/ProfileActionItem.kt index a015358d8b..80b78d0d70 100644 --- a/vector/src/main/java/im/vector/riotx/core/epoxy/profiles/ProfileActionItem.kt +++ b/vector/src/main/java/im/vector/riotx/core/epoxy/profiles/ProfileActionItem.kt @@ -25,10 +25,12 @@ import androidx.core.view.isVisible import androidx.core.widget.ImageViewCompat import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass +import im.vector.matrix.android.api.util.MatrixItem import im.vector.riotx.R import im.vector.riotx.core.epoxy.VectorEpoxyHolder import im.vector.riotx.core.epoxy.VectorEpoxyModel import im.vector.riotx.core.extensions.setTextOrHide +import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.themes.ThemeUtils @EpoxyModelClass(layout = R.layout.item_profile_action) @@ -51,6 +53,12 @@ abstract class ProfileActionItem : VectorEpoxyModel() @EpoxyAttribute var accessoryRes: Int = 0 + @EpoxyAttribute + var accessoryMatrixItem: MatrixItem? = null + + @EpoxyAttribute + var avatarRenderer: AvatarRenderer? = null + @EpoxyAttribute var editable: Boolean = true @@ -93,6 +101,13 @@ abstract class ProfileActionItem : VectorEpoxyModel() holder.secondaryAccessory.isVisible = false } + if (accessoryMatrixItem != null) { + avatarRenderer?.render(accessoryMatrixItem!!, holder.secondaryAccessory) + holder.secondaryAccessory.isVisible = true + } else { + holder.secondaryAccessory.isVisible = false + } + if (editableRes != 0 && editable) { val tintColorSecondary = if (destructive) { tintColor diff --git a/vector/src/main/java/im/vector/riotx/core/epoxy/profiles/ProfileItemExtensions.kt b/vector/src/main/java/im/vector/riotx/core/epoxy/profiles/ProfileItemExtensions.kt index 693efc5418..1f3aff0de6 100644 --- a/vector/src/main/java/im/vector/riotx/core/epoxy/profiles/ProfileItemExtensions.kt +++ b/vector/src/main/java/im/vector/riotx/core/epoxy/profiles/ProfileItemExtensions.kt @@ -19,8 +19,10 @@ package im.vector.riotx.core.epoxy.profiles import androidx.annotation.DrawableRes import com.airbnb.epoxy.EpoxyController +import im.vector.matrix.android.api.util.MatrixItem import im.vector.riotx.core.epoxy.ClickListener import im.vector.riotx.core.epoxy.dividerItem +import im.vector.riotx.features.home.AvatarRenderer fun EpoxyController.buildProfileSection(title: String) { profileSectionItem { @@ -41,7 +43,9 @@ fun EpoxyController.buildProfileAction( destructive: Boolean = false, divider: Boolean = true, action: ClickListener? = null, - @DrawableRes accessory: Int = 0 + @DrawableRes accessory: Int = 0, + accessoryMatrixItem: MatrixItem? = null, + avatarRenderer: AvatarRenderer? = null ) { profileActionItem { iconRes(icon) @@ -53,6 +57,8 @@ fun EpoxyController.buildProfileAction( destructive(destructive) title(title) accessoryRes(accessory) + accessoryMatrixItem(accessoryMatrixItem) + avatarRenderer(avatarRenderer) listener { _ -> action?.invoke() } diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsAction.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsAction.kt index 1108c6813b..f740c22d6d 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsAction.kt @@ -17,11 +17,12 @@ package im.vector.riotx.features.roomprofile.settings import im.vector.riotx.core.platform.VectorViewModelAction +import im.vector.riotx.multipicker.entity.MultiPickerImageType sealed class RoomSettingsAction : VectorViewModelAction { data class SetRoomName(val newName: String) : RoomSettingsAction() data class SetRoomTopic(val newTopic: String) : RoomSettingsAction() - data class SetRoomAvatar(val newAvatarUrl: String) : RoomSettingsAction() + data class SetRoomAvatar(val image: MultiPickerImageType) : RoomSettingsAction() object EnableEncryption : RoomSettingsAction() object Save : RoomSettingsAction() } diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsController.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsController.kt index d0ee589da5..bb87c256e8 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsController.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsController.kt @@ -17,16 +17,19 @@ package im.vector.riotx.features.roomprofile.settings import com.airbnb.epoxy.TypedEpoxyController +import im.vector.matrix.android.api.util.toMatrixItem import im.vector.riotx.R import im.vector.riotx.core.epoxy.profiles.buildProfileAction import im.vector.riotx.core.epoxy.profiles.buildProfileSection import im.vector.riotx.core.resources.ColorProvider import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.features.form.formEditTextItem +import im.vector.riotx.features.home.AvatarRenderer import javax.inject.Inject // TODO Add other feature here (waiting for design) class RoomSettingsController @Inject constructor( + private val avatarRenderer: AvatarRenderer, private val stringProvider: StringProvider, colorProvider: ColorProvider ) : TypedEpoxyController() { @@ -35,6 +38,7 @@ class RoomSettingsController @Inject constructor( fun onEnableEncryptionClicked() fun onNameChanged(name: String) fun onTopicChanged(topic: String) + fun onPhotoClicked() } private val dividerColor = colorProvider.getColorFromAttribute(R.attr.vctr_list_divider_color) @@ -74,6 +78,18 @@ class RoomSettingsController @Inject constructor( } } + buildProfileAction( + id = "photo", + title = stringProvider.getString(R.string.room_settings_room_photo), + subtitle = "", + dividerColor = dividerColor, + divider = true, + editable = true, + accessoryMatrixItem = roomSummary.toMatrixItem(), + avatarRenderer = avatarRenderer, + action = { callback?.onPhotoClicked() } + ) + if (roomSummary.isEncrypted) { buildProfileAction( id = "encryption", diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsFragment.kt index bb97409e74..3733b311ac 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsFragment.kt @@ -16,6 +16,8 @@ package im.vector.riotx.features.roomprofile.settings +import android.app.Activity +import android.content.Intent import android.os.Bundle import android.view.Menu import android.view.MenuItem @@ -34,6 +36,8 @@ import im.vector.riotx.core.platform.VectorBaseFragment import im.vector.riotx.core.utils.toast import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.roomprofile.RoomProfileArgs +import im.vector.riotx.multipicker.MultiPicker +import im.vector.riotx.multipicker.entity.MultiPickerImageType import kotlinx.android.synthetic.main.fragment_room_setting_generic.* import kotlinx.android.synthetic.main.merge_overlay_waiting_view.* import javax.inject.Inject @@ -62,7 +66,7 @@ class RoomSettingsFragment @Inject constructor( viewModel.observeViewEvents { when (it) { is RoomSettingsViewEvents.Failure -> showFailure(it.throwable) - is RoomSettingsViewEvents.Success -> showSuccess() + is RoomSettingsViewEvents.Success -> showSuccess() }.exhaustive } } @@ -95,6 +99,17 @@ class RoomSettingsFragment @Inject constructor( renderRoomSummary(viewState) } + private fun renderRoomSummary(state: RoomSettingsViewState) { + waiting_view.isVisible = state.isLoading + + state.roomSummary()?.let { + roomSettingsToolbarTitleView.text = it.displayName + avatarRenderer.render(it.toMatrixItem(), roomSettingsToolbarAvatarImageView) + } + + invalidateOptionsMenu() + } + override fun onEnableEncryptionClicked() { AlertDialog.Builder(requireActivity()) .setTitle(R.string.room_settings_enable_encryption_dialog_title) @@ -114,14 +129,27 @@ class RoomSettingsFragment @Inject constructor( viewModel.handle(RoomSettingsAction.SetRoomTopic(topic)) } - private fun renderRoomSummary(state: RoomSettingsViewState) { - waiting_view.isVisible = state.isLoading + override fun onPhotoClicked() { + MultiPicker.get(MultiPicker.IMAGE).single().startWith(this) + } - state.roomSummary()?.let { - roomSettingsToolbarTitleView.text = it.displayName - avatarRenderer.render(it.toMatrixItem(), roomSettingsToolbarAvatarImageView) + private fun onRoomPhotoSelected(selectedImage: MultiPickerImageType) { + viewModel.handle(RoomSettingsAction.SetRoomAvatar(selectedImage)) + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + if (resultCode == Activity.RESULT_OK) { + when (requestCode) { + MultiPicker.REQUEST_CODE_PICK_IMAGE -> { + MultiPicker + .get(MultiPicker.IMAGE) + .getSelectedFiles(requireContext(), requestCode, resultCode, data) + .firstOrNull()?.let { + onRoomPhotoSelected(it) + } + } + } } - - invalidateOptionsMenu() + super.onActivityResult(requestCode, resultCode, data) } } diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewModel.kt index deca6858e2..1b0d2da749 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewModel.kt @@ -28,6 +28,7 @@ import im.vector.matrix.rx.unwrap import im.vector.riotx.core.platform.VectorViewModel import io.reactivex.Completable import io.reactivex.Observable +import java.util.UUID class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: RoomSettingsViewState, private val session: Session) @@ -69,17 +70,17 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: override fun handle(action: RoomSettingsAction) { when (action) { is RoomSettingsAction.EnableEncryption -> handleEnableEncryption() - is RoomSettingsAction.SetRoomName -> setState { - copy( - newName = action.newName, - showSaveAction = shouldShowSaveAction(this) - ) + is RoomSettingsAction.SetRoomName -> { + setState { copy(newName = action.newName) } + setState { copy(showSaveAction = shouldShowSaveAction(this)) } } - is RoomSettingsAction.SetRoomTopic -> setState { - copy( - newTopic = action.newTopic, - showSaveAction = shouldShowSaveAction(this) - ) + is RoomSettingsAction.SetRoomTopic -> { + setState { copy(newTopic = action.newTopic) } + setState { copy(showSaveAction = shouldShowSaveAction(this)) } + } + is RoomSettingsAction.SetRoomAvatar -> { + setState { copy(newAvatar = action.image) } + setState { copy(showSaveAction = shouldShowSaveAction(this)) } } is RoomSettingsAction.Save -> saveSettings() } @@ -88,7 +89,8 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: private fun shouldShowSaveAction(state: RoomSettingsViewState): Boolean { val summary = state.roomSummary.invoke() return summary?.displayName != state.newName || - summary?.topic != state.newTopic + summary?.topic != state.newTopic || + state.newAvatar != null } private fun saveSettings() = withState { state -> @@ -105,12 +107,18 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: operationList.add(room.rx().updateTopic(state.newTopic ?: "")) } + if (state.newAvatar != null) { + operationList.add(room.rx().updateAvatar(state.newAvatar.contentUri, state.newAvatar.displayName ?: UUID.randomUUID().toString())) + } + Observable .fromIterable(operationList) .flatMapCompletable { it } .subscribe( { postLoading(false) + setState { copy(newAvatar = null) } + setState { copy(showSaveAction = shouldShowSaveAction(this)) } _viewEvents.post(RoomSettingsViewEvents.Success) }, { diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewState.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewState.kt index 0b288fd829..4ac870028c 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewState.kt @@ -21,6 +21,7 @@ import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.Uninitialized import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.riotx.features.roomprofile.RoomProfileArgs +import im.vector.riotx.multipicker.entity.MultiPickerImageType data class RoomSettingsViewState( val roomId: String, @@ -28,6 +29,7 @@ data class RoomSettingsViewState( val isLoading: Boolean = false, val newName: String? = null, val newTopic: String? = null, + val newAvatar: MultiPickerImageType? = null, val showSaveAction: Boolean = false ) : MvRxState {