upload avatar within the room creation

This commit is contained in:
Florian Renaud 2023-05-11 08:32:08 +02:00
parent 4a444c24c4
commit 5a0665a27a
5 changed files with 44 additions and 15 deletions

View file

@ -47,6 +47,7 @@ dependencies {
implementation(projects.libraries.uiStrings) implementation(projects.libraries.uiStrings)
implementation(projects.features.userlist.api) implementation(projects.features.userlist.api)
implementation(projects.libraries.mediapickers.api) implementation(projects.libraries.mediapickers.api)
implementation(projects.libraries.mediaupload.api)
implementation(libs.coil.compose) implementation(libs.coil.compose)
api(projects.features.createroom.api) api(projects.features.createroom.api)

View file

@ -16,6 +16,7 @@
package io.element.android.features.createroom.impl.configureroom package io.element.android.features.createroom.impl.configureroom
import android.net.Uri
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.collectAsState 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.Async
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.architecture.execute 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.MatrixClient
import io.element.android.libraries.matrix.api.core.RoomId 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.CreateRoomParameters
import io.element.android.libraries.matrix.api.createroom.RoomPreset import io.element.android.libraries.matrix.api.createroom.RoomPreset
import io.element.android.libraries.matrix.api.createroom.RoomVisibility import io.element.android.libraries.matrix.api.createroom.RoomVisibility
import io.element.android.libraries.mediapickers.api.PickerProvider 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.collections.immutable.toImmutableList
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -45,6 +50,7 @@ class ConfigureRoomPresenter @Inject constructor(
private val dataStore: CreateRoomDataStore, private val dataStore: CreateRoomDataStore,
private val matrixClient: MatrixClient, private val matrixClient: MatrixClient,
private val mediaPickerProvider: PickerProvider, private val mediaPickerProvider: PickerProvider,
private val mediaPreProcessor: MediaPreProcessor,
) : Presenter<ConfigureRoomState> { ) : Presenter<ConfigureRoomState> {
@Composable @Composable
@ -112,9 +118,12 @@ class ConfigureRoomPresenter @Inject constructor(
) )
} }
private fun CoroutineScope.createRoom(config: CreateRoomConfig, createRoomAction: MutableState<Async<RoomId>>) = launch { private fun CoroutineScope.createRoom(
config: CreateRoomConfig,
createRoomAction: MutableState<Async<RoomId>>
) = launch {
val mxc = config.avatarUri?.let { uploadAvatar(it) }
suspend { suspend {
// TODO pre-process and upload the avatar before creating the room
val params = CreateRoomParameters( val params = CreateRoomParameters(
name = config.roomName, name = config.roomName,
topic = config.topic, topic = config.topic,
@ -123,9 +132,15 @@ class ConfigureRoomPresenter @Inject constructor(
visibility = if (config.privacy == RoomPrivacy.Public) RoomVisibility.PUBLIC else RoomVisibility.PRIVATE, visibility = if (config.privacy == RoomPrivacy.Public) RoomVisibility.PUBLIC else RoomVisibility.PRIVATE,
preset = if (config.privacy == RoomPrivacy.Public) RoomPreset.PUBLIC_CHAT else RoomPreset.PRIVATE_CHAT, preset = if (config.privacy == RoomPrivacy.Public) RoomPreset.PUBLIC_CHAT else RoomPreset.PRIVATE_CHAT,
invite = config.invites.map { it.userId }, invite = config.invites.map { it.userId },
avatar = config.avatarUri?.toString(), avatar = mxc,
) )
matrixClient.createRoom(params).getOrThrow() matrixClient.createRoom(params).getOrThrow()
}.execute(createRoomAction) }.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()
}
} }

View file

@ -42,6 +42,7 @@ interface MatrixClient : Closeable {
suspend fun createRoom(createRoomParams: CreateRoomParameters): Result<RoomId> suspend fun createRoom(createRoomParams: CreateRoomParameters): Result<RoomId>
suspend fun createDM(userId: UserId): Result<RoomId> suspend fun createDM(userId: UserId): Result<RoomId>
suspend fun getProfile(userId: UserId): Result<MatrixUser> suspend fun getProfile(userId: UserId): Result<MatrixUser>
suspend fun searchUsers(searchTerm: String, limit: Long): Result<MatrixSearchUserResults>
fun startSync() fun startSync()
fun stopSync() fun stopSync()
fun mediaResolver(): MediaResolver fun mediaResolver(): MediaResolver
@ -58,9 +59,10 @@ interface MatrixClient : Closeable {
height: Long height: Long
): Result<ByteArray> ): Result<ByteArray>
suspend fun uploadMedia(mimeType: String, data: ByteArray): Result<String>
fun onSlidingSyncUpdate() fun onSlidingSyncUpdate()
fun roomMembershipObserver(): RoomMembershipObserver fun roomMembershipObserver(): RoomMembershipObserver
suspend fun searchUsers(searchTerm: String, limit: Long): Result<MatrixSearchUserResults>
} }

View file

@ -280,6 +280,13 @@ class RustMatrixClient constructor(
} }
} }
override suspend fun searchUsers(searchTerm: String, limit: Long): Result<MatrixSearchUserResults> =
withContext(dispatchers.io) {
runCatching {
client.searchUsers(searchTerm, limit.toULong()).let(UserSearchResultMapper::map)
}
}
override fun mediaResolver(): MediaResolver = mediaResolver override fun mediaResolver(): MediaResolver = mediaResolver
override fun sessionVerificationService(): SessionVerificationService = verificationService override fun sessionVerificationService(): SessionVerificationService = verificationService
@ -368,6 +375,13 @@ class RustMatrixClient constructor(
} }
} }
@OptIn(ExperimentalUnsignedTypes::class)
override suspend fun uploadMedia(mimeType: String, data: ByteArray): Result<String> = withContext(dispatchers.io) {
runCatching {
client.uploadMedia(mimeType, data.toUByteArray().toList())
}
}
override fun onSlidingSyncUpdate() { override fun onSlidingSyncUpdate() {
if (!verificationService.isReady.value) { if (!verificationService.isReady.value) {
try { try {
@ -380,13 +394,6 @@ class RustMatrixClient constructor(
override fun roomMembershipObserver(): RoomMembershipObserver = roomMembershipObserver override fun roomMembershipObserver(): RoomMembershipObserver = roomMembershipObserver
override suspend fun searchUsers(searchTerm: String, limit: Long): Result<MatrixSearchUserResults> =
withContext(dispatchers.io) {
runCatching {
client.searchUsers(searchTerm, limit.toULong()).let(UserSearchResultMapper::map)
}
}
private fun File.deleteSessionDirectory(userID: String): Boolean { private fun File.deleteSessionDirectory(userID: String): Boolean {
// Rust sanitises the user ID replacing invalid characters with an _ // Rust sanitises the user ID replacing invalid characters with an _
val sanitisedUserID = userID.replace(":", "_") val sanitisedUserID = userID.replace(":", "_")

View file

@ -91,6 +91,10 @@ class FakeMatrixClient(
return getProfileResults[userId] ?: Result.failure(IllegalStateException("No profile found for $userId")) return getProfileResults[userId] ?: Result.failure(IllegalStateException("No profile found for $userId"))
} }
override suspend fun searchUsers(searchTerm: String, limit: Long): Result<MatrixSearchUserResults> {
return searchUserResults[searchTerm] ?: Result.failure(IllegalStateException("No response defined for $searchTerm"))
}
override fun startSync() = Unit override fun startSync() = Unit
override fun stopSync() = Unit override fun stopSync() = Unit
@ -122,6 +126,10 @@ class FakeMatrixClient(
return Result.success(ByteArray(0)) return Result.success(ByteArray(0))
} }
override suspend fun uploadMedia(mimeType: String, data: ByteArray): Result<String> {
return Result.success("")
}
override fun sessionVerificationService(): SessionVerificationService = sessionVerificationService override fun sessionVerificationService(): SessionVerificationService = sessionVerificationService
override fun pushersService(): PushersService = pushersService override fun pushersService(): PushersService = pushersService
@ -134,10 +142,6 @@ class FakeMatrixClient(
return RoomMembershipObserver() return RoomMembershipObserver()
} }
override suspend fun searchUsers(searchTerm: String, limit: Long): Result<MatrixSearchUserResults> {
return searchUserResults[searchTerm] ?: Result.failure(IllegalStateException("No response defined for $searchTerm"))
}
// Mocks // Mocks
fun givenLogoutError(failure: Throwable?) { fun givenLogoutError(failure: Throwable?) {