Add Avatar: SDK

Also add remove avatar action, and add Crop UX
This commit is contained in:
Benoit Marty 2020-10-20 14:58:20 +02:00 committed by Benoit Marty
parent 1f9712d8a2
commit 4b8c31d806
12 changed files with 100 additions and 23 deletions

View File

@ -16,6 +16,7 @@
package org.matrix.android.sdk.api.session.room.model.create
import android.net.Uri
import org.matrix.android.sdk.api.session.identity.ThreePid
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
@ -51,6 +52,11 @@ class CreateRoomParams {
*/
var topic: String? = null
/**
* If this is not null, the image uri will be sent to the media server and will be set as a room avatar.
*/
var avatarUri: Uri? = null
/**
* A list of user IDs to invite to the room.
* This will tell the server to invite everyone in the list to the newly created room.

View File

@ -16,10 +16,10 @@
package org.matrix.android.sdk.internal.session.room.create
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.identity.IdentityServiceError
import org.matrix.android.sdk.api.session.identity.toMedium
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
@ -27,11 +27,13 @@ import org.matrix.android.sdk.internal.crypto.DeviceListManager
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
import org.matrix.android.sdk.internal.di.AuthenticatedIdentity
import org.matrix.android.sdk.internal.network.token.AccessTokenProvider
import org.matrix.android.sdk.internal.session.content.FileUploader
import org.matrix.android.sdk.internal.session.identity.EnsureIdentityTokenTask
import org.matrix.android.sdk.internal.session.identity.data.IdentityStore
import org.matrix.android.sdk.internal.session.identity.data.getIdentityServerUrlWithoutProtocol
import org.matrix.android.sdk.internal.session.room.membership.threepid.ThreePidInviteBody
import java.security.InvalidParameterException
import java.util.UUID
import javax.inject.Inject
internal class CreateRoomBodyBuilder @Inject constructor(
@ -39,6 +41,7 @@ internal class CreateRoomBodyBuilder @Inject constructor(
private val crossSigningService: CrossSigningService,
private val deviceListManager: DeviceListManager,
private val identityStore: IdentityStore,
private val fileUploader: FileUploader,
@AuthenticatedIdentity
private val accessTokenProvider: AccessTokenProvider
) {
@ -66,7 +69,8 @@ internal class CreateRoomBodyBuilder @Inject constructor(
val initialStates = listOfNotNull(
buildEncryptionWithAlgorithmEvent(params),
buildHistoryVisibilityEvent(params)
buildHistoryVisibilityEvent(params),
buildAvatarEvent(params)
)
.takeIf { it.isNotEmpty() }
@ -85,15 +89,33 @@ internal class CreateRoomBodyBuilder @Inject constructor(
)
}
private suspend fun buildAvatarEvent(params: CreateRoomParams): Event? {
return params.avatarUri?.let { avatarUri ->
// First upload the image, ignoring any error
tryOrNull {
fileUploader.uploadFromUri(
uri = avatarUri,
filename = UUID.randomUUID().toString(),
mimeType = "image/jpeg")
}
?.let { response ->
Event(
type = EventType.STATE_ROOM_AVATAR,
stateKey = "",
content = mapOf("url" to response.contentUri)
)
}
}
}
private fun buildHistoryVisibilityEvent(params: CreateRoomParams): Event? {
return params.historyVisibility
?.let {
val contentMap = mapOf("history_visibility" to it)
Event(
type = EventType.STATE_ROOM_HISTORY_VISIBILITY,
stateKey = "",
content = contentMap.toContent())
content = mapOf("history_visibility" to it)
)
}
}
@ -111,12 +133,10 @@ internal class CreateRoomBodyBuilder @Inject constructor(
if (it != MXCRYPTO_ALGORITHM_MEGOLM) {
throw InvalidParameterException("Unsupported algorithm: $it")
}
val contentMap = mapOf("algorithm" to it)
Event(
type = EventType.STATE_ROOM_ENCRYPTION,
stateKey = "",
content = contentMap.toContent()
content = mapOf("algorithm" to it)
)
}
}

View File

@ -16,7 +16,9 @@
package im.vector.app.features.form
import android.net.Uri
import android.view.View
import android.widget.ImageView
import androidx.core.view.isVisible
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import com.airbnb.epoxy.EpoxyModelWithHolder
@ -43,6 +45,9 @@ abstract class FormEditableAvatarItem : EpoxyModelWithHolder<FormEditableAvatarI
@EpoxyAttribute
var clickListener: ClickListener? = null
@EpoxyAttribute
var deleteListener: ClickListener? = null
override fun bind(holder: Holder) {
super.bind(holder)
holder.image.onClick(clickListener?.takeIf { enabled })
@ -51,9 +56,12 @@ abstract class FormEditableAvatarItem : EpoxyModelWithHolder<FormEditableAvatarI
.apply(RequestOptions.circleCropTransform())
.placeholder(R.drawable.bg_accent)
.into(holder.image)
holder.delete.isVisible = imageUri != null
holder.delete.onClick(deleteListener?.takeIf { enabled })
}
class Holder : VectorEpoxyHolder() {
val image by bind<ImageView>(R.id.itemEditableAvatarImage)
val delete by bind<View>(R.id.itemEditableAvatarDelete)
}
}

View File

@ -122,7 +122,7 @@ class BigImageViewerActivity : VectorBaseActivity() {
val destinationFile = File(cacheDir, "${image.displayName}_edited_image_${System.currentTimeMillis()}")
val uri = image.contentUri
createUCropWithDefaultSettings(this, uri, destinationFile.toUri(), image.displayName)
.apply { withAspectRatio(1f, 1f) }
.withAspectRatio(1f, 1f)
.start(this)
}

View File

@ -16,11 +16,11 @@
package im.vector.app.features.roomdirectory.createroom
import android.net.Uri
import im.vector.app.core.platform.VectorViewModelAction
import im.vector.lib.multipicker.entity.MultiPickerImageType
sealed class CreateRoomAction : VectorViewModelAction {
data class SetAvatar(val image: MultiPickerImageType) : CreateRoomAction()
data class SetAvatar(val imageUri: Uri?) : CreateRoomAction()
data class SetName(val name: String) : CreateRoomAction()
data class SetTopic(val topic: String) : CreateRoomAction()
data class SetIsPublic(val isPublic: Boolean) : CreateRoomAction()

View File

@ -73,8 +73,9 @@ class CreateRoomController @Inject constructor(private val stringProvider: Strin
formEditableAvatarItem {
id("avatar")
enabled(enableFormElement)
imageUri(viewState.avatar?.contentUri)
imageUri(viewState.avatarUri)
clickListener { listener?.onAvatarChange() }
deleteListener { listener?.onAvatarDelete() }
}
settingsSectionTitleItem {
id("nameSection")
@ -156,6 +157,7 @@ class CreateRoomController @Inject constructor(private val stringProvider: Strin
}
interface Listener {
fun onAvatarDelete()
fun onAvatarChange()
fun onNameChange(newName: String)
fun onTopicChange(newTopic: String)

View File

@ -16,22 +16,27 @@
package im.vector.app.features.roomdirectory.createroom
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.view.MenuItem
import android.view.View
import androidx.core.net.toUri
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.withState
import com.yalantis.ucrop.UCrop
import im.vector.app.R
import im.vector.app.core.dialogs.GalleryOrCameraDialogHelper
import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.features.media.createUCropWithDefaultSettings
import im.vector.app.features.roomdirectory.RoomDirectorySharedAction
import im.vector.app.features.roomdirectory.RoomDirectorySharedActionViewModel
import im.vector.lib.multipicker.entity.MultiPickerImageType
import kotlinx.android.synthetic.main.fragment_create_room.*
import timber.log.Timber
import java.io.File
import javax.inject.Inject
class CreateRoomFragment @Inject constructor(
@ -68,12 +73,33 @@ class CreateRoomFragment @Inject constructor(
createRoomController.listener = this
}
override fun onAvatarDelete() {
viewModel.handle(CreateRoomAction.SetAvatar(null))
}
override fun onAvatarChange() {
galleryOrCameraDialogHelper.show()
}
override fun onImageReady(image: MultiPickerImageType) {
viewModel.handle(CreateRoomAction.SetAvatar(image))
val destinationFile = File(requireContext().cacheDir, "${image.displayName}_edited_image_${System.currentTimeMillis()}")
val uri = image.contentUri
createUCropWithDefaultSettings(requireContext(), uri, destinationFile.toUri(), image.displayName)
.withAspectRatio(1f, 1f)
.start(requireContext(), this)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
// TODO handle this one (Ucrop lib)
@Suppress("DEPRECATION")
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == Activity.RESULT_OK) {
when (requestCode) {
UCrop.REQUEST_CROP ->
viewModel.handle(CreateRoomAction.SetAvatar(data?.let { UCrop.getOutput(it) }))
}
}
}
override fun onNameChange(newName: String) {

View File

@ -101,7 +101,7 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: Cr
}.exhaustive
}
private fun setAvatar(action: CreateRoomAction.SetAvatar) = setState { copy(avatar = action.image) }
private fun setAvatar(action: CreateRoomAction.SetAvatar) = setState { copy(avatarUri = action.imageUri) }
private fun setName(action: CreateRoomAction.SetName) = setState { copy(roomName = action.name) }
@ -131,6 +131,7 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: Cr
.apply {
name = state.roomName.takeIf { it.isNotBlank() }
topic = state.roomTopic.takeIf { it.isNotBlank() }
avatarUri = state.avatarUri
// Directory visibility
visibility = if (state.isInRoomDirectory) RoomDirectoryVisibility.PUBLIC else RoomDirectoryVisibility.PRIVATE
// Public room

View File

@ -16,13 +16,13 @@
package im.vector.app.features.roomdirectory.createroom
import android.net.Uri
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.Uninitialized
import im.vector.lib.multipicker.entity.MultiPickerImageType
data class CreateRoomViewState(
val avatar: MultiPickerImageType? = null,
val avatarUri: Uri? = null,
val roomName: String = "",
val roomTopic: String = "",
val isPublic: Boolean = false,

View File

@ -282,7 +282,7 @@ class RoomProfileFragment @Inject constructor(
val destinationFile = File(requireContext().cacheDir, "${image.displayName}_edited_image_${System.currentTimeMillis()}")
val uri = image.contentUri
createUCropWithDefaultSettings(requireContext(), uri, destinationFile.toUri(), image.displayName)
.apply { withAspectRatio(1f, 1f) }
.withAspectRatio(1f, 1f)
.start(requireContext(), this)
}

View File

@ -312,7 +312,7 @@ class VectorSettingsGeneralFragment :
val destinationFile = File(requireContext().cacheDir, "${image.displayName}_edited_image_${System.currentTimeMillis()}")
val uri = image.contentUri
createUCropWithDefaultSettings(requireContext(), uri, destinationFile.toUri(), image.displayName)
.apply { withAspectRatio(1f, 1f) }
.withAspectRatio(1f, 1f)
.start(requireContext(), this)
}

View File

@ -10,18 +10,32 @@
<ImageView
android:id="@+id/itemEditableAvatarImage"
android:layout_width="112dp"
android:layout_height="112dp"
android:layout_width="128dp"
android:layout_height="128dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@tools:sample/avatars" />
<ImageView
android:id="@+id/itemEditableAvatarDelete"
android:layout_width="32dp"
android:layout_height="32dp"
android:background="@drawable/circle"
android:scaleType="center"
android:src="@drawable/ic_delete"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="@+id/itemEditableAvatarImage"
app:layout_constraintTop_toTopOf="@+id/itemEditableAvatarImage"
app:tint="@color/riotx_destructive_accent"
tools:ignore="MissingPrefix"
tools:visibility="visible" />
<ImageView
android:id="@+id/itemEditableAvatarPicto"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_width="32dp"
android:layout_height="32dp"
android:background="@drawable/circle"
android:scaleType="center"
android:src="@drawable/ic_add_image"