mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-12-22 23:58:47 +01:00
Updating room avatar is implemented.
This commit is contained in:
parent
a6e4a328b3
commit
52eec06110
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package im.vector.matrix.rx
|
package im.vector.matrix.rx
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
import im.vector.matrix.android.api.query.QueryStringValue
|
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.events.model.Event
|
||||||
import im.vector.matrix.android.api.session.room.Room
|
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<Unit> {
|
fun updateName(name: String): Completable = completableBuilder<Unit> {
|
||||||
room.updateName(name, it)
|
room.updateName(name, it)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun updateAvatar(avatarUri: Uri, fileName: String): Completable = completableBuilder<Unit> {
|
||||||
|
room.updateAvatar(avatarUri, fileName, it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Room.rx(): RxRoom {
|
fun Room.rx(): RxRoom {
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.api.session.room.state
|
package im.vector.matrix.android.api.session.room.state
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.query.QueryStringValue
|
import im.vector.matrix.android.api.query.QueryStringValue
|
||||||
@ -36,6 +37,11 @@ interface StateService {
|
|||||||
*/
|
*/
|
||||||
fun updateName(name: String, callback: MatrixCallback<Unit>): Cancelable
|
fun updateName(name: String, callback: MatrixCallback<Unit>): Cancelable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the avatar of the room
|
||||||
|
*/
|
||||||
|
fun updateAvatar(avatarUri: Uri, fileName: String, callback: MatrixCallback<Unit>): Cancelable
|
||||||
|
|
||||||
fun sendStateEvent(eventType: String, stateKey: String?, body: JsonDict, callback: MatrixCallback<Unit>): Cancelable
|
fun sendStateEvent(eventType: String, stateKey: String?, body: JsonDict, callback: MatrixCallback<Unit>): Cancelable
|
||||||
|
|
||||||
fun getStateEvent(eventType: String, stateKey: QueryStringValue = QueryStringValue.NoCondition): Event?
|
fun getStateEvent(eventType: String, stateKey: QueryStringValue = QueryStringValue.NoCondition): Event?
|
||||||
|
@ -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.cache.CacheModule
|
||||||
import im.vector.matrix.android.internal.session.call.CallModule
|
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.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.content.UploadContentWorker
|
||||||
import im.vector.matrix.android.internal.session.filter.FilterModule
|
import im.vector.matrix.android.internal.session.filter.FilterModule
|
||||||
import im.vector.matrix.android.internal.session.group.GetGroupDataWorker
|
import im.vector.matrix.android.internal.session.group.GetGroupDataWorker
|
||||||
@ -117,6 +118,8 @@ internal interface SessionComponent {
|
|||||||
|
|
||||||
fun inject(worker: UploadContentWorker)
|
fun inject(worker: UploadContentWorker)
|
||||||
|
|
||||||
|
fun inject(worker: UploadAvatarWorker)
|
||||||
|
|
||||||
fun inject(worker: SyncWorker)
|
fun inject(worker: SyncWorker)
|
||||||
|
|
||||||
fun inject(worker: AddHttpPusherWorker)
|
fun inject(worker: AddHttpPusherWorker)
|
||||||
|
@ -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<Params>(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))
|
||||||
|
}
|
||||||
|
}
|
@ -16,7 +16,10 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.internal.session.room.state
|
package im.vector.matrix.android.internal.session.room.state
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.work.BackoffPolicy
|
||||||
|
import androidx.work.ExistingWorkPolicy
|
||||||
import com.squareup.inject.assisted.Assisted
|
import com.squareup.inject.assisted.Assisted
|
||||||
import com.squareup.inject.assisted.AssistedInject
|
import com.squareup.inject.assisted.AssistedInject
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
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.events.model.EventType
|
||||||
import im.vector.matrix.android.api.session.room.state.StateService
|
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.Cancelable
|
||||||
|
import im.vector.matrix.android.api.util.CancelableBag
|
||||||
import im.vector.matrix.android.api.util.JsonDict
|
import im.vector.matrix.android.api.util.JsonDict
|
||||||
import im.vector.matrix.android.api.util.Optional
|
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.TaskExecutor
|
||||||
import im.vector.matrix.android.internal.task.configureWith
|
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,
|
internal class DefaultStateService @AssistedInject constructor(@Assisted private val roomId: String,
|
||||||
private val stateEventDataSource: StateEventDataSource,
|
private val stateEventDataSource: StateEventDataSource,
|
||||||
private val taskExecutor: TaskExecutor,
|
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 {
|
) : StateService {
|
||||||
|
|
||||||
@AssistedInject.Factory
|
@AssistedInject.Factory
|
||||||
@ -93,4 +110,40 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
|
|||||||
stateKey = null
|
stateKey = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun updateAvatar(avatarUri: Uri, fileName: String, callback: MatrixCallback<Unit>): Cancelable {
|
||||||
|
val cancelableBag = CancelableBag()
|
||||||
|
val workerParams = UploadAvatarWorker.Params(sessionId, avatarUri, fileName)
|
||||||
|
val workerData = WorkerParamsFactory.toData(workerParams)
|
||||||
|
|
||||||
|
val uploadAvatarWork = workManagerProvider.matrixOneTimeWorkRequestBuilder<UploadAvatarWorker>()
|
||||||
|
.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<UploadAvatarWorker.OutputParams>(info.outputData)
|
||||||
|
cancelableBag.add(
|
||||||
|
sendStateEvent(
|
||||||
|
eventType = EventType.STATE_ROOM_AVATAR,
|
||||||
|
body = mapOf("url" to result?.imageUrl!!),
|
||||||
|
callback = callback,
|
||||||
|
stateKey = null
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cancelableBag
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,10 +25,12 @@ import androidx.core.view.isVisible
|
|||||||
import androidx.core.widget.ImageViewCompat
|
import androidx.core.widget.ImageViewCompat
|
||||||
import com.airbnb.epoxy.EpoxyAttribute
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
import com.airbnb.epoxy.EpoxyModelClass
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
|
import im.vector.matrix.android.api.util.MatrixItem
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
||||||
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
||||||
import im.vector.riotx.core.extensions.setTextOrHide
|
import im.vector.riotx.core.extensions.setTextOrHide
|
||||||
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
import im.vector.riotx.features.themes.ThemeUtils
|
import im.vector.riotx.features.themes.ThemeUtils
|
||||||
|
|
||||||
@EpoxyModelClass(layout = R.layout.item_profile_action)
|
@EpoxyModelClass(layout = R.layout.item_profile_action)
|
||||||
@ -51,6 +53,12 @@ abstract class ProfileActionItem : VectorEpoxyModel<ProfileActionItem.Holder>()
|
|||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
var accessoryRes: Int = 0
|
var accessoryRes: Int = 0
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var accessoryMatrixItem: MatrixItem? = null
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var avatarRenderer: AvatarRenderer? = null
|
||||||
|
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
var editable: Boolean = true
|
var editable: Boolean = true
|
||||||
|
|
||||||
@ -93,6 +101,13 @@ abstract class ProfileActionItem : VectorEpoxyModel<ProfileActionItem.Holder>()
|
|||||||
holder.secondaryAccessory.isVisible = false
|
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) {
|
if (editableRes != 0 && editable) {
|
||||||
val tintColorSecondary = if (destructive) {
|
val tintColorSecondary = if (destructive) {
|
||||||
tintColor
|
tintColor
|
||||||
|
@ -19,8 +19,10 @@ package im.vector.riotx.core.epoxy.profiles
|
|||||||
|
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import com.airbnb.epoxy.EpoxyController
|
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.ClickListener
|
||||||
import im.vector.riotx.core.epoxy.dividerItem
|
import im.vector.riotx.core.epoxy.dividerItem
|
||||||
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
|
|
||||||
fun EpoxyController.buildProfileSection(title: String) {
|
fun EpoxyController.buildProfileSection(title: String) {
|
||||||
profileSectionItem {
|
profileSectionItem {
|
||||||
@ -41,7 +43,9 @@ fun EpoxyController.buildProfileAction(
|
|||||||
destructive: Boolean = false,
|
destructive: Boolean = false,
|
||||||
divider: Boolean = true,
|
divider: Boolean = true,
|
||||||
action: ClickListener? = null,
|
action: ClickListener? = null,
|
||||||
@DrawableRes accessory: Int = 0
|
@DrawableRes accessory: Int = 0,
|
||||||
|
accessoryMatrixItem: MatrixItem? = null,
|
||||||
|
avatarRenderer: AvatarRenderer? = null
|
||||||
) {
|
) {
|
||||||
profileActionItem {
|
profileActionItem {
|
||||||
iconRes(icon)
|
iconRes(icon)
|
||||||
@ -53,6 +57,8 @@ fun EpoxyController.buildProfileAction(
|
|||||||
destructive(destructive)
|
destructive(destructive)
|
||||||
title(title)
|
title(title)
|
||||||
accessoryRes(accessory)
|
accessoryRes(accessory)
|
||||||
|
accessoryMatrixItem(accessoryMatrixItem)
|
||||||
|
avatarRenderer(avatarRenderer)
|
||||||
listener { _ ->
|
listener { _ ->
|
||||||
action?.invoke()
|
action?.invoke()
|
||||||
}
|
}
|
||||||
|
@ -17,11 +17,12 @@
|
|||||||
package im.vector.riotx.features.roomprofile.settings
|
package im.vector.riotx.features.roomprofile.settings
|
||||||
|
|
||||||
import im.vector.riotx.core.platform.VectorViewModelAction
|
import im.vector.riotx.core.platform.VectorViewModelAction
|
||||||
|
import im.vector.riotx.multipicker.entity.MultiPickerImageType
|
||||||
|
|
||||||
sealed class RoomSettingsAction : VectorViewModelAction {
|
sealed class RoomSettingsAction : VectorViewModelAction {
|
||||||
data class SetRoomName(val newName: String) : RoomSettingsAction()
|
data class SetRoomName(val newName: String) : RoomSettingsAction()
|
||||||
data class SetRoomTopic(val newTopic: 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 EnableEncryption : RoomSettingsAction()
|
||||||
object Save : RoomSettingsAction()
|
object Save : RoomSettingsAction()
|
||||||
}
|
}
|
||||||
|
@ -17,16 +17,19 @@
|
|||||||
package im.vector.riotx.features.roomprofile.settings
|
package im.vector.riotx.features.roomprofile.settings
|
||||||
|
|
||||||
import com.airbnb.epoxy.TypedEpoxyController
|
import com.airbnb.epoxy.TypedEpoxyController
|
||||||
|
import im.vector.matrix.android.api.util.toMatrixItem
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.epoxy.profiles.buildProfileAction
|
import im.vector.riotx.core.epoxy.profiles.buildProfileAction
|
||||||
import im.vector.riotx.core.epoxy.profiles.buildProfileSection
|
import im.vector.riotx.core.epoxy.profiles.buildProfileSection
|
||||||
import im.vector.riotx.core.resources.ColorProvider
|
import im.vector.riotx.core.resources.ColorProvider
|
||||||
import im.vector.riotx.core.resources.StringProvider
|
import im.vector.riotx.core.resources.StringProvider
|
||||||
import im.vector.riotx.features.form.formEditTextItem
|
import im.vector.riotx.features.form.formEditTextItem
|
||||||
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
// TODO Add other feature here (waiting for design)
|
// TODO Add other feature here (waiting for design)
|
||||||
class RoomSettingsController @Inject constructor(
|
class RoomSettingsController @Inject constructor(
|
||||||
|
private val avatarRenderer: AvatarRenderer,
|
||||||
private val stringProvider: StringProvider,
|
private val stringProvider: StringProvider,
|
||||||
colorProvider: ColorProvider
|
colorProvider: ColorProvider
|
||||||
) : TypedEpoxyController<RoomSettingsViewState>() {
|
) : TypedEpoxyController<RoomSettingsViewState>() {
|
||||||
@ -35,6 +38,7 @@ class RoomSettingsController @Inject constructor(
|
|||||||
fun onEnableEncryptionClicked()
|
fun onEnableEncryptionClicked()
|
||||||
fun onNameChanged(name: String)
|
fun onNameChanged(name: String)
|
||||||
fun onTopicChanged(topic: String)
|
fun onTopicChanged(topic: String)
|
||||||
|
fun onPhotoClicked()
|
||||||
}
|
}
|
||||||
|
|
||||||
private val dividerColor = colorProvider.getColorFromAttribute(R.attr.vctr_list_divider_color)
|
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) {
|
if (roomSummary.isEncrypted) {
|
||||||
buildProfileAction(
|
buildProfileAction(
|
||||||
id = "encryption",
|
id = "encryption",
|
||||||
|
@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
package im.vector.riotx.features.roomprofile.settings
|
package im.vector.riotx.features.roomprofile.settings
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
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.core.utils.toast
|
||||||
import im.vector.riotx.features.home.AvatarRenderer
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
import im.vector.riotx.features.roomprofile.RoomProfileArgs
|
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.fragment_room_setting_generic.*
|
||||||
import kotlinx.android.synthetic.main.merge_overlay_waiting_view.*
|
import kotlinx.android.synthetic.main.merge_overlay_waiting_view.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@ -62,7 +66,7 @@ class RoomSettingsFragment @Inject constructor(
|
|||||||
viewModel.observeViewEvents {
|
viewModel.observeViewEvents {
|
||||||
when (it) {
|
when (it) {
|
||||||
is RoomSettingsViewEvents.Failure -> showFailure(it.throwable)
|
is RoomSettingsViewEvents.Failure -> showFailure(it.throwable)
|
||||||
is RoomSettingsViewEvents.Success -> showSuccess()
|
is RoomSettingsViewEvents.Success -> showSuccess()
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -95,6 +99,17 @@ class RoomSettingsFragment @Inject constructor(
|
|||||||
renderRoomSummary(viewState)
|
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() {
|
override fun onEnableEncryptionClicked() {
|
||||||
AlertDialog.Builder(requireActivity())
|
AlertDialog.Builder(requireActivity())
|
||||||
.setTitle(R.string.room_settings_enable_encryption_dialog_title)
|
.setTitle(R.string.room_settings_enable_encryption_dialog_title)
|
||||||
@ -114,14 +129,27 @@ class RoomSettingsFragment @Inject constructor(
|
|||||||
viewModel.handle(RoomSettingsAction.SetRoomTopic(topic))
|
viewModel.handle(RoomSettingsAction.SetRoomTopic(topic))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renderRoomSummary(state: RoomSettingsViewState) {
|
override fun onPhotoClicked() {
|
||||||
waiting_view.isVisible = state.isLoading
|
MultiPicker.get(MultiPicker.IMAGE).single().startWith(this)
|
||||||
|
}
|
||||||
|
|
||||||
state.roomSummary()?.let {
|
private fun onRoomPhotoSelected(selectedImage: MultiPickerImageType) {
|
||||||
roomSettingsToolbarTitleView.text = it.displayName
|
viewModel.handle(RoomSettingsAction.SetRoomAvatar(selectedImage))
|
||||||
avatarRenderer.render(it.toMatrixItem(), roomSettingsToolbarAvatarImageView)
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
invalidateOptionsMenu()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,7 @@ import im.vector.matrix.rx.unwrap
|
|||||||
import im.vector.riotx.core.platform.VectorViewModel
|
import im.vector.riotx.core.platform.VectorViewModel
|
||||||
import io.reactivex.Completable
|
import io.reactivex.Completable
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: RoomSettingsViewState,
|
class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: RoomSettingsViewState,
|
||||||
private val session: Session)
|
private val session: Session)
|
||||||
@ -69,17 +70,17 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState:
|
|||||||
override fun handle(action: RoomSettingsAction) {
|
override fun handle(action: RoomSettingsAction) {
|
||||||
when (action) {
|
when (action) {
|
||||||
is RoomSettingsAction.EnableEncryption -> handleEnableEncryption()
|
is RoomSettingsAction.EnableEncryption -> handleEnableEncryption()
|
||||||
is RoomSettingsAction.SetRoomName -> setState {
|
is RoomSettingsAction.SetRoomName -> {
|
||||||
copy(
|
setState { copy(newName = action.newName) }
|
||||||
newName = action.newName,
|
setState { copy(showSaveAction = shouldShowSaveAction(this)) }
|
||||||
showSaveAction = shouldShowSaveAction(this)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
is RoomSettingsAction.SetRoomTopic -> setState {
|
is RoomSettingsAction.SetRoomTopic -> {
|
||||||
copy(
|
setState { copy(newTopic = action.newTopic) }
|
||||||
newTopic = action.newTopic,
|
setState { copy(showSaveAction = shouldShowSaveAction(this)) }
|
||||||
showSaveAction = shouldShowSaveAction(this)
|
}
|
||||||
)
|
is RoomSettingsAction.SetRoomAvatar -> {
|
||||||
|
setState { copy(newAvatar = action.image) }
|
||||||
|
setState { copy(showSaveAction = shouldShowSaveAction(this)) }
|
||||||
}
|
}
|
||||||
is RoomSettingsAction.Save -> saveSettings()
|
is RoomSettingsAction.Save -> saveSettings()
|
||||||
}
|
}
|
||||||
@ -88,7 +89,8 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState:
|
|||||||
private fun shouldShowSaveAction(state: RoomSettingsViewState): Boolean {
|
private fun shouldShowSaveAction(state: RoomSettingsViewState): Boolean {
|
||||||
val summary = state.roomSummary.invoke()
|
val summary = state.roomSummary.invoke()
|
||||||
return summary?.displayName != state.newName ||
|
return summary?.displayName != state.newName ||
|
||||||
summary?.topic != state.newTopic
|
summary?.topic != state.newTopic ||
|
||||||
|
state.newAvatar != null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun saveSettings() = withState { state ->
|
private fun saveSettings() = withState { state ->
|
||||||
@ -105,12 +107,18 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState:
|
|||||||
operationList.add(room.rx().updateTopic(state.newTopic ?: ""))
|
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
|
Observable
|
||||||
.fromIterable(operationList)
|
.fromIterable(operationList)
|
||||||
.flatMapCompletable { it }
|
.flatMapCompletable { it }
|
||||||
.subscribe(
|
.subscribe(
|
||||||
{
|
{
|
||||||
postLoading(false)
|
postLoading(false)
|
||||||
|
setState { copy(newAvatar = null) }
|
||||||
|
setState { copy(showSaveAction = shouldShowSaveAction(this)) }
|
||||||
_viewEvents.post(RoomSettingsViewEvents.Success)
|
_viewEvents.post(RoomSettingsViewEvents.Success)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -21,6 +21,7 @@ import com.airbnb.mvrx.MvRxState
|
|||||||
import com.airbnb.mvrx.Uninitialized
|
import com.airbnb.mvrx.Uninitialized
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
import im.vector.riotx.features.roomprofile.RoomProfileArgs
|
import im.vector.riotx.features.roomprofile.RoomProfileArgs
|
||||||
|
import im.vector.riotx.multipicker.entity.MultiPickerImageType
|
||||||
|
|
||||||
data class RoomSettingsViewState(
|
data class RoomSettingsViewState(
|
||||||
val roomId: String,
|
val roomId: String,
|
||||||
@ -28,6 +29,7 @@ data class RoomSettingsViewState(
|
|||||||
val isLoading: Boolean = false,
|
val isLoading: Boolean = false,
|
||||||
val newName: String? = null,
|
val newName: String? = null,
|
||||||
val newTopic: String? = null,
|
val newTopic: String? = null,
|
||||||
|
val newAvatar: MultiPickerImageType? = null,
|
||||||
val showSaveAction: Boolean = false
|
val showSaveAction: Boolean = false
|
||||||
) : MvRxState {
|
) : MvRxState {
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user