Create spaces (#5982)

* Allow creating a space with `CreateRoomParameters`

* Add 'Create space' menu item in the spaces home screen. Also, imports new strings related to spaces.

* Link the 'Create space' button with the screen to create the space

* Unify room access and visibility for `ConfigureRoom`, use the updated design

* Fix `EditRoomDetails` avatar size (68dp)

* Replace `EditableAvatarView` and `UnsavedAvatar` copmonents with `AvatarPickerView`

* `AvatarDataFetcherFactory`: Make sure we use a fallback image fetcher when the URL is not an MXC one (a local one, i.e.). This removes the previous need for a separate `UnsavedAvatarView`

* Use `AvatarPickerView` in all the screens where `EditableAvatarView` was used

* Improve naming and previews

* Update strings, remove unused ones for `RoomAccessItem`

* Make `isSpace` part of the `CreateRoomConfig`

* Ensure the content fits in the screenshots for `AvatarPickerSizesPreview`

* Add `AvatarDataFetcherFactoryTest`

* Add new feature flag for creating spaces

* Fix ripple being too large for the `Pick` state

* Tweak margins and section titles a bit

* Add preview for `HomeTopBar` with the spaces case

* Update screenshots

---------

Co-authored-by: ElementBot <android@element.io>
This commit is contained in:
Jorge Martin Espinosa
2026-01-13 14:35:49 +01:00
committed by GitHub
parent a234eb3e29
commit 03d14087e6
150 changed files with 1097 additions and 778 deletions

View File

@@ -11,6 +11,7 @@ package io.element.android.libraries.matrix.ui.media
import coil3.ImageLoader
import coil3.fetch.Fetcher
import coil3.request.Options
import coil3.toUri
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.matrix.api.media.MatrixMediaLoader
@@ -21,10 +22,19 @@ internal class AvatarDataFetcherFactory(
data: AvatarData,
options: Options,
imageLoader: ImageLoader
): Fetcher {
return CoilMediaFetcher(
mediaLoader = matrixMediaLoader,
mediaData = data.toMediaRequestData(),
)
): Fetcher? {
return when {
data.url == null -> null
data.url?.startsWith("mxc") == true -> CoilMediaFetcher(
mediaLoader = matrixMediaLoader,
mediaData = data.toMediaRequestData(),
)
else -> {
// If the URL does not use the mxc scheme, it might be a local one using `content://`, try using a fallback fetcher
data.url?.toUri()?.let { uri ->
imageLoader.components.newFetcher(uri, options, imageLoader)
}?.first
}
}
}
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright (c) 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.ui.media
import android.graphics.Bitmap
import coil3.ComponentRegistry
import coil3.ImageLoader
import coil3.asImage
import coil3.disk.DiskCache
import coil3.memory.MemoryCache
import coil3.request.Disposable
import coil3.request.ImageRequest
import coil3.request.ImageResult
import coil3.request.Options
import coil3.request.SuccessResult
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.designsystem.components.avatar.anAvatarData
import io.element.android.libraries.matrix.test.media.FakeMatrixMediaLoader
import io.mockk.mockk
import org.junit.Test
class AvatarDataFetcherFactoryTest {
@Test
fun `create - with mxc returns CoilMediaFetcher`() {
val factory = AvatarDataFetcherFactory(matrixMediaLoader = FakeMatrixMediaLoader())
val fetcher = factory.create(anAvatarData(url = "mxc://test"), Options(mockk()), imageLoader = FakeImageLoader())
assertThat(fetcher).isInstanceOf(CoilMediaFetcher::class.java)
}
@Test
fun `create - with http or https returns null, which means fallback default fetcher will be used`() {
val factory = AvatarDataFetcherFactory(matrixMediaLoader = FakeMatrixMediaLoader())
val fetcherHttp = factory.create(anAvatarData(url = "http://test"), Options(mockk()), imageLoader = FakeImageLoader())
assertThat(fetcherHttp).isNull()
val fetcherHttps = factory.create(anAvatarData(url = "https://test"), Options(mockk()), imageLoader = FakeImageLoader())
assertThat(fetcherHttps).isNull()
}
@Test
fun `create - with content scheme returns null, which means fallback default fetcher will be used`() {
val factory = AvatarDataFetcherFactory(matrixMediaLoader = FakeMatrixMediaLoader())
val fetcher = factory.create(anAvatarData(url = "content://test"), Options(mockk()), imageLoader = FakeImageLoader())
assertThat(fetcher).isNull()
}
}
private class FakeImageLoader : ImageLoader {
override val defaults: ImageRequest.Defaults = ImageRequest.Defaults.DEFAULT
override val components: ComponentRegistry = ComponentRegistry.Builder().build()
override val memoryCache: MemoryCache? = null
override val diskCache: DiskCache? = null
override fun enqueue(request: ImageRequest): Disposable {
return mockk()
}
override suspend fun execute(request: ImageRequest): ImageResult {
return SuccessResult(
image = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8).asImage(),
request = request,
)
}
override fun shutdown() {}
override fun newBuilder(): ImageLoader.Builder {
return ImageLoader.Builder(mockk())
}
}