From 27f6f5cd3b2ef2a9b86f983d79e2e42fdabd20ed Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 11 May 2023 08:32:08 +0200 Subject: [PATCH] upload avatar within the room creation --- features/createroom/impl/build.gradle.kts | 1 + .../configureroom/ConfigureRoomPresenter.kt | 21 ++++++++++++++++--- .../libraries/matrix/api/MatrixClient.kt | 4 +++- .../libraries/matrix/impl/RustMatrixClient.kt | 21 ++++++++++++------- .../libraries/matrix/test/FakeMatrixClient.kt | 12 +++++++---- 5 files changed, 44 insertions(+), 15 deletions(-) diff --git a/features/createroom/impl/build.gradle.kts b/features/createroom/impl/build.gradle.kts index 08cd6fa0e8..2f8894b402 100644 --- a/features/createroom/impl/build.gradle.kts +++ b/features/createroom/impl/build.gradle.kts @@ -47,6 +47,7 @@ dependencies { implementation(projects.libraries.uiStrings) implementation(projects.features.userlist.api) implementation(projects.libraries.mediapickers.api) + implementation(projects.libraries.mediaupload.api) implementation(libs.coil.compose) api(projects.features.createroom.api) diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenter.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenter.kt index 3117be0657..0d60158b8d 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenter.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenter.kt @@ -16,6 +16,7 @@ package io.element.android.features.createroom.impl.configureroom +import android.net.Uri import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState import androidx.compose.runtime.collectAsState @@ -30,12 +31,16 @@ import io.element.android.features.createroom.impl.configureroom.avatar.AvatarAc import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.execute +import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters import io.element.android.libraries.matrix.api.createroom.RoomPreset import io.element.android.libraries.matrix.api.createroom.RoomVisibility import io.element.android.libraries.mediapickers.api.PickerProvider +import io.element.android.libraries.mediaupload.api.MediaPreProcessor +import io.element.android.libraries.mediaupload.api.MediaType +import io.element.android.libraries.mediaupload.api.MediaUploadInfo import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -45,6 +50,7 @@ class ConfigureRoomPresenter @Inject constructor( private val dataStore: CreateRoomDataStore, private val matrixClient: MatrixClient, private val mediaPickerProvider: PickerProvider, + private val mediaPreProcessor: MediaPreProcessor, ) : Presenter { @Composable @@ -112,9 +118,12 @@ class ConfigureRoomPresenter @Inject constructor( ) } - private fun CoroutineScope.createRoom(config: CreateRoomConfig, createRoomAction: MutableState>) = launch { + private fun CoroutineScope.createRoom( + config: CreateRoomConfig, + createRoomAction: MutableState> + ) = launch { + val mxc = config.avatarUri?.let { uploadAvatar(it) } suspend { - // TODO pre-process and upload the avatar before creating the room val params = CreateRoomParameters( name = config.roomName, topic = config.topic, @@ -123,9 +132,15 @@ class ConfigureRoomPresenter @Inject constructor( visibility = if (config.privacy == RoomPrivacy.Public) RoomVisibility.PUBLIC else RoomVisibility.PRIVATE, preset = if (config.privacy == RoomPrivacy.Public) RoomPreset.PUBLIC_CHAT else RoomPreset.PRIVATE_CHAT, invite = config.invites.map { it.userId }, - avatar = config.avatarUri?.toString(), + avatar = mxc, ) matrixClient.createRoom(params).getOrThrow() }.execute(createRoomAction) } + + private suspend fun uploadAvatar(avatarUri: Uri): String? { + val preprocessed = mediaPreProcessor.process(avatarUri, MediaType.Image).getOrThrow() as? MediaUploadInfo.Image + val byteArray = preprocessed?.file?.readBytes() + return byteArray?.let { matrixClient.uploadMedia(MimeTypes.Jpeg, it) }?.getOrThrow() + } } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt index dce2b15485..6d357ad912 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt @@ -42,6 +42,7 @@ interface MatrixClient : Closeable { suspend fun createRoom(createRoomParams: CreateRoomParameters): Result suspend fun createDM(userId: UserId): Result suspend fun getProfile(userId: UserId): Result + suspend fun searchUsers(searchTerm: String, limit: Long): Result fun startSync() fun stopSync() fun mediaResolver(): MediaResolver @@ -58,9 +59,10 @@ interface MatrixClient : Closeable { height: Long ): Result + suspend fun uploadMedia(mimeType: String, data: ByteArray): Result + fun onSlidingSyncUpdate() fun roomMembershipObserver(): RoomMembershipObserver - suspend fun searchUsers(searchTerm: String, limit: Long): Result } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt index cce7ac90c7..0381869503 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt @@ -280,6 +280,13 @@ class RustMatrixClient constructor( } } + override suspend fun searchUsers(searchTerm: String, limit: Long): Result = + withContext(dispatchers.io) { + runCatching { + client.searchUsers(searchTerm, limit.toULong()).let(UserSearchResultMapper::map) + } + } + override fun mediaResolver(): MediaResolver = mediaResolver override fun sessionVerificationService(): SessionVerificationService = verificationService @@ -368,6 +375,13 @@ class RustMatrixClient constructor( } } + @OptIn(ExperimentalUnsignedTypes::class) + override suspend fun uploadMedia(mimeType: String, data: ByteArray): Result = withContext(dispatchers.io) { + runCatching { + client.uploadMedia(mimeType, data.toUByteArray().toList()) + } + } + override fun onSlidingSyncUpdate() { if (!verificationService.isReady.value) { try { @@ -380,13 +394,6 @@ class RustMatrixClient constructor( override fun roomMembershipObserver(): RoomMembershipObserver = roomMembershipObserver - override suspend fun searchUsers(searchTerm: String, limit: Long): Result = - withContext(dispatchers.io) { - runCatching { - client.searchUsers(searchTerm, limit.toULong()).let(UserSearchResultMapper::map) - } - } - private fun File.deleteSessionDirectory(userID: String): Boolean { // Rust sanitises the user ID replacing invalid characters with an _ val sanitisedUserID = userID.replace(":", "_") diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt index 9d3ce40da1..51088115ed 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt @@ -91,6 +91,10 @@ class FakeMatrixClient( return getProfileResults[userId] ?: Result.failure(IllegalStateException("No profile found for $userId")) } + override suspend fun searchUsers(searchTerm: String, limit: Long): Result { + return searchUserResults[searchTerm] ?: Result.failure(IllegalStateException("No response defined for $searchTerm")) + } + override fun startSync() = Unit override fun stopSync() = Unit @@ -122,6 +126,10 @@ class FakeMatrixClient( return Result.success(ByteArray(0)) } + override suspend fun uploadMedia(mimeType: String, data: ByteArray): Result { + return Result.success("") + } + override fun sessionVerificationService(): SessionVerificationService = sessionVerificationService override fun pushersService(): PushersService = pushersService @@ -134,10 +142,6 @@ class FakeMatrixClient( return RoomMembershipObserver() } - override suspend fun searchUsers(searchTerm: String, limit: Long): Result { - return searchUserResults[searchTerm] ?: Result.failure(IllegalStateException("No response defined for $searchTerm")) - } - // Mocks fun givenLogoutError(failure: Throwable?) {