Space-filter fix for bridge spaces, drag reorder, collapsed Home, call profiles, rail labels
Sonar / Sonar Quality Checks (push) Has been cancelled
Code Quality Checks / Search for forbidden patterns (push) Has been cancelled
Code Quality Checks / Search for invalid screenshot files (push) Has been cancelled
Code Quality Checks / Project Check Suite (push) Has been cancelled
Code Quality Checks / Search for invalid dependencies (push) Has been cancelled
Code Quality Checks / Konsist tests (push) Has been cancelled
Code Quality Checks / Compose tests (push) Has been cancelled
Code Quality Checks / Android lint check (push) Has been cancelled
Code Quality Checks / Detekt checks (push) Has been cancelled
Code Quality Checks / Ktlint checks (push) Has been cancelled
Code Quality Checks / Doc checks (push) Has been cancelled
Code Quality Checks / Check shell scripts (push) Has been cancelled
Code Quality Checks / Run zizmor (push) Has been cancelled
Create release App Bundle and APKs / Create App Bundle (Gplay) (push) Has been cancelled
Create release App Bundle and APKs / Create App Bundle Enterprise (push) Has been cancelled
Create release App Bundle and APKs / Create APKs (FDroid) (push) Has been cancelled
Test / Runs unit tests (push) Has been cancelled
Post-release / post-release (push) Has been cancelled

- Space selection now unions the space's real child list (spaceRoomList) into
  the SDK descendants filter: mautrix-telegram spaces (children are DM portals)
  showed an empty room list, and DMs vanished inside every space
- Long-press-drag the avatar to manually reorder rooms within a section,
  persisted per space (desktop parity); long-press elsewhere keeps the
  context menu
- Home starts with Text/Voice sections collapsed (unread rooms still poke
  through) so DMs stay in reach; inside a space sections start expanded
- Call participant rows show resolved display names + real avatars
- Space rail items get name labels (identical bridge logos were
  indistinguishable)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-26 12:16:57 -07:00
parent cd4b1b7216
commit 8d45f776e8
10 changed files with 185 additions and 30 deletions
+1
View File
@@ -56,6 +56,7 @@ dependencies {
implementation(projects.services.analytics.api)
implementation(libs.androidx.datastore.preferences)
implementation(libs.haze)
implementation(libs.reorderable)
implementation(libs.haze.materials)
implementation(projects.features.preferences.impl)
implementation(projects.features.reportroom.api)
@@ -32,8 +32,11 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarType
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.ImmutableMap
import kotlinx.collections.immutable.persistentMapOf
private const val MAX_VOICE_MEMBERS_SHOWN = 8
@@ -79,10 +82,13 @@ internal fun BlapSectionHeader(
internal fun BlapVoiceMembersRow(
participants: ImmutableList<UserId>,
modifier: Modifier = Modifier,
users: ImmutableMap<UserId, MatrixUser> = persistentMapOf(),
) {
Column(modifier = modifier.padding(start = 64.dp, bottom = 8.dp)) {
participants.take(MAX_VOICE_MEMBERS_SHOWN).forEach { userId ->
val localpart = userId.value.removePrefix("@").substringBefore(":")
val user = users[userId]
val name = user?.displayName?.takeIf { it.isNotBlank() }
?: userId.value.removePrefix("@").substringBefore(":")
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(vertical = 2.dp),
@@ -90,15 +96,15 @@ internal fun BlapVoiceMembersRow(
Avatar(
avatarData = AvatarData(
id = userId.value,
name = localpart,
url = null,
size = AvatarSize.TimelineReadReceipt,
name = name,
url = user?.avatarUrl,
size = AvatarSize.TimelineThreadLatestEventSender,
),
avatarType = AvatarType.User,
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = localpart,
text = name,
style = ElementTheme.typography.fontBodySmRegular,
color = ElementTheme.colors.textSecondary,
)
@@ -13,6 +13,7 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
@@ -28,6 +29,8 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons
@@ -38,6 +41,7 @@ import io.element.android.features.home.impl.spacefilters.selectedFilter
import io.element.android.libraries.designsystem.components.avatar.Avatar
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.components.avatar.AvatarType
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.matrix.ui.model.getAvatarData
/**
@@ -65,6 +69,7 @@ fun BlapSpaceRail(
item(key = "blap_rail_home") {
RailItem(
isSelected = selectedRoomId == null,
label = "Home",
onClick = { onSelect(null) },
) {
Icon(
@@ -79,6 +84,9 @@ fun BlapSpaceRail(
val isSelected = filter.spaceRoom.roomId == selectedRoomId
RailItem(
isSelected = isSelected,
// Bridge spaces often share one logo (two Discord servers look identical) —
// the name label is what tells them apart.
label = filter.spaceRoom.displayName,
onClick = { onSelect(filter) },
) {
Avatar(
@@ -92,14 +100,41 @@ fun BlapSpaceRail(
@Composable
private fun RailItem(
isSelected: Boolean,
label: String,
onClick: () -> Unit,
content: @Composable () -> Unit,
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 4.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
RailItemAvatar(isSelected = isSelected, onClick = onClick, content = content)
Text(
text = label,
style = ElementTheme.typography.fontBodyXsRegular,
color = if (isSelected) ElementTheme.colors.textPrimary else ElementTheme.colors.textSecondary,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
textAlign = TextAlign.Center,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 2.dp),
)
}
}
@Composable
private fun RailItemAvatar(
isSelected: Boolean,
onClick: () -> Unit,
content: @Composable () -> Unit,
) {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 4.dp),
.fillMaxWidth(),
contentAlignment = Alignment.Center,
) {
// Discord-style selection pill hugging the left edge of the rail.
@@ -11,6 +11,7 @@ package io.element.android.features.home.impl.components
import androidx.annotation.StringRes
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.PaddingValues
@@ -32,6 +33,7 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.features.home.impl.R
import io.element.android.features.home.impl.contentType
@@ -58,6 +60,8 @@ import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.utils.OnVisibleRangeChangeEffect
import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.collections.immutable.ImmutableList
import sh.calvin.reorderable.ReorderableItem
import sh.calvin.reorderable.rememberReorderableLazyListState
@Composable
fun RoomListContentView(
@@ -228,8 +232,25 @@ private fun RoomsViewList(
// Blap: Discord-style sections. Invites/knocks render on top, ungrouped; then
// Text / Voice / DM sections (desktop order, Home included) with collapsible
// headers and inline voice members.
val (blapSpecial, blapSections) = remember(state.summaries, state.blapVoiceRoomIds, state.blapCollapsedSections) {
buildBlapSections(state.summaries, state.blapVoiceRoomIds, state.blapCollapsedSections)
val (blapSpecial, blapSections) = remember(state.summaries, state.blapVoiceRoomIds, state.blapCollapsedSections, state.blapManualOrder) {
buildBlapSections(state.summaries, state.blapVoiceRoomIds, state.blapCollapsedSections, state.blapManualOrder)
}
// Blap: long-press-drag manual reordering, constrained to within a section.
val blapSectionByRoom = remember(blapSections) {
blapSections.flatMap { entry -> entry.rooms.map { it.roomId.value to entry.section } }.toMap()
}
val blapFlatRoomIds = remember(blapSections) { blapSections.flatMap { entry -> entry.rooms.map { it.roomId.value } } }
val blapReorderableState = rememberReorderableLazyListState(lazyListState) { from, to ->
val fromKey = from.key as? String ?: return@rememberReorderableLazyListState
val toKey = to.key as? String ?: return@rememberReorderableLazyListState
val fromSection = blapSectionByRoom[fromKey] ?: return@rememberReorderableLazyListState
if (fromSection != blapSectionByRoom[toKey]) return@rememberReorderableLazyListState
val ids = blapFlatRoomIds.toMutableList()
val fromIndex = ids.indexOf(fromKey)
val toIndex = ids.indexOf(toKey)
if (fromIndex < 0 || toIndex < 0) return@rememberReorderableLazyListState
ids.add(toIndex, ids.removeAt(fromIndex))
eventSink(RoomListEvent.BlapSetRoomOrder(ids.map { RoomId(it) }))
}
LazyColumn(
state = lazyListState,
@@ -296,7 +317,7 @@ private fun RoomsViewList(
}
}
blapSections.forEach { entry ->
item(contentType = "blap_section_header") {
item(key = "blap_header_" + entry.section.key, contentType = "blap_section_header") {
BlapSectionHeader(
section = entry.section,
isCollapsed = entry.isCollapsed,
@@ -305,23 +326,38 @@ private fun RoomsViewList(
}
itemsIndexed(
items = entry.rooms,
key = { _, room -> room.roomId.value },
contentType = { _, room -> room.contentType() },
) { index, room ->
RoomSummaryRow(
room = room,
hideInviteAvatars = hideInvitesAvatars,
isInviteSeen = false,
showUnreadCount = state.showUnreadCount,
onClick = onRoomClick,
eventSink = eventSink,
)
// Any room with an active call shows who's in it — not just classified
// voice rooms (classification lags until the space was opened once).
if (room.activeRoomCallParticipants.isNotEmpty()) {
BlapVoiceMembersRow(participants = room.activeRoomCallParticipants)
}
if (index != entry.rooms.lastIndex) {
HorizontalDivider()
ReorderableItem(blapReorderableState, key = room.roomId.value) { isDragging ->
Column(
modifier = if (isDragging) {
Modifier.background(ElementTheme.colors.bgSubtleSecondary)
} else {
Modifier
},
) {
RoomSummaryRow(
room = room,
hideInviteAvatars = hideInvitesAvatars,
isInviteSeen = false,
showUnreadCount = state.showUnreadCount,
onClick = onRoomClick,
blapAvatarModifier = Modifier.longPressDraggableHandle(),
eventSink = eventSink,
)
// Any room with an active call shows who's in it — not just classified
// voice rooms (classification lags until the space was opened once).
if (room.activeRoomCallParticipants.isNotEmpty()) {
BlapVoiceMembersRow(
participants = room.activeRoomCallParticipants,
users = state.blapCallUsers,
)
}
if (index != entry.rooms.lastIndex) {
HorizontalDivider()
}
}
}
}
}
@@ -78,6 +78,9 @@ internal fun RoomSummaryRow(
onClick: (RoomListRoomSummary) -> Unit,
modifier: Modifier = Modifier,
showUnreadCount: Boolean = false,
// Blap: drag handle for manual reordering, applied to the avatar so the row's
// long-press context menu keeps working
blapAvatarModifier: Modifier = Modifier,
eventSink: (RoomListEvent) -> Unit,
) {
Box(modifier = modifier) {
@@ -122,6 +125,7 @@ internal fun RoomSummaryRow(
onLongClick = {
eventSink(RoomListEvent.ShowContextMenu(room))
},
blapAvatarModifier = blapAvatarModifier,
) {
NameAndTimestampRow(
name = room.name,
@@ -174,6 +178,7 @@ private fun RoomSummaryScaffoldRow(
onLongClick: (RoomListRoomSummary) -> Unit,
modifier: Modifier = Modifier,
hideAvatarImage: Boolean = false,
blapAvatarModifier: Modifier = Modifier,
content: @Composable ColumnScope.() -> Unit
) {
val clickModifier = Modifier
@@ -193,7 +198,7 @@ private fun RoomSummaryScaffoldRow(
.padding(horizontal = 16.dp, vertical = 11.dp)
.height(IntrinsicSize.Min),
) {
Box {
Box(modifier = blapAvatarModifier) {
Avatar(
avatarData = room.avatarData,
avatarType = if (room.isSpace) {
@@ -45,6 +45,7 @@ fun buildBlapSections(
summaries: List<RoomListRoomSummary>,
voiceRoomIds: Set<RoomId>,
collapsedSections: Set<String>,
manualOrder: List<RoomId> = emptyList(),
): Pair<List<RoomListRoomSummary>, List<BlapSectionEntry>> {
val (rooms, special) = summaries.partition {
it.displayType == io.element.android.features.home.impl.model.RoomSummaryDisplayType.ROOM
@@ -56,9 +57,13 @@ fun buildBlapSections(
else -> BlapSection.Text
}
}
// Manual drag-and-drop placements first, unplaced rooms keep their (stable)
// default order after them — same semantics as desktop's YapRoomList.
val manualIndex = manualOrder.withIndex().associate { (index, id) -> id to index }
val sectionOrder = listOf(BlapSection.Text, BlapSection.Voice, BlapSection.Dm)
val entries = sectionOrder.mapNotNull { section ->
val sectionRooms = bySection[section] ?: return@mapNotNull null
val sectionRooms = (bySection[section] ?: return@mapNotNull null)
.sortedBy { manualIndex[it.roomId] ?: Int.MAX_VALUE }
val isCollapsed = section.key in collapsedSections
BlapSectionEntry(
section = section,
@@ -27,6 +27,9 @@ sealed interface RoomListEvent {
// Blap: collapse/expand a room list section
data class BlapToggleSection(val section: BlapSection) : RoomListEvent
// Blap: persist the new manual room order after a drag (full displayed order)
data class BlapSetRoomOrder(val order: List<RoomId>) : RoomListEvent
sealed interface ContextMenuEvent : RoomListEvent
data object HideContextMenu : ContextMenuEvent
data class LeaveRoom(val roomId: RoomId, val needsConfirmation: Boolean) : ContextMenuEvent
@@ -51,16 +51,19 @@ import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsState
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.UserId
import io.element.android.libraries.matrix.api.encryption.RecoveryState
import io.element.android.libraries.matrix.api.roomlist.RoomList
import io.element.android.libraries.matrix.api.roomlist.RoomListFilter
import io.element.android.libraries.matrix.api.spaces.loadAllIncrementally
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.ui.safety.rememberHideInvitesAvatar
import io.element.android.libraries.push.api.battery.BatteryOptimizationState
import io.element.android.services.analytics.api.AnalyticsService
import io.element.android.services.analytics.api.watchers.AnalyticsColdStartWatcher
import io.element.android.services.analyticsproviders.api.trackers.captureInteraction
import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toImmutableMap
import kotlinx.collections.immutable.toImmutableSet
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -116,12 +119,18 @@ class RoomListPresenter(
sharedPreferences.getStringSet(BLAP_VOICE_ROOMS_KEY, null).orEmpty().map { RoomId(it) }.toSet()
)
}
// Blap: the selected space's true child list — used for voice classification AND
// unioned into the room filter (the SDK's descendants list omits DM portals, which
// left bridge spaces like mautrix-telegram's looking empty).
var blapSpaceChildIds by remember { mutableStateOf(emptySet<RoomId>()) }
LaunchedEffect(selectedSpaceId) {
blapSpaceChildIds = emptySet()
if (selectedSpaceId == null) return@LaunchedEffect
val spaceRoomList = client.spaceService.spaceRoomList(selectedSpaceId)
try {
spaceRoomList.loadAllIncrementally(this)
spaceRoomList.spaceRoomsFlow.collect { spaceRooms ->
blapSpaceChildIds = spaceRooms.map { it.roomId }.toSet()
val voiceIds = spaceRooms.filter { it.roomType.isBlapVoiceRoom() }.map { it.roomId }
if (voiceIds.isNotEmpty() && !blapVoiceRoomIds.containsAll(voiceIds)) {
blapVoiceRoomIds = blapVoiceRoomIds + voiceIds
@@ -135,8 +144,34 @@ class RoomListPresenter(
}
}
// Blap: per-space section collapse state, persisted like desktop's localStorage key.
// At Home the channel sections start collapsed (DMs stay in reach on a phone);
// inside a space they start expanded. User toggles win once persisted.
var blapCollapsedSections by remember(selectedSpaceId) {
mutableStateOf(sharedPreferences.getStringSet(blapCollapseKey(selectedSpaceId), null).orEmpty().toSet())
mutableStateOf(
sharedPreferences.getStringSet(blapCollapseKey(selectedSpaceId), null)?.toSet()
?: if (selectedSpaceId == null) setOf(BlapSection.Text.key, BlapSection.Voice.key) else emptySet()
)
}
// Blap: manual drag-and-drop room order, per space, device-local (desktop parity).
var blapManualOrder by remember(selectedSpaceId) {
mutableStateOf(
sharedPreferences.getString(blapOrderKey(selectedSpaceId), null)
?.split(",")?.filter { it.isNotBlank() }?.map { RoomId(it) }.orEmpty()
)
}
// Blap: resolve profiles (display name + avatar) for users in active calls.
var blapCallUsers by remember { mutableStateOf(emptyMap<UserId, MatrixUser>()) }
val blapProfileAttempts = remember { mutableSetOf<UserId>() }
LaunchedEffect(Unit) {
roomListDataSource.roomSummariesFlow.collect { summaries ->
val wanted = summaries.flatMap { it.activeRoomCallParticipants }.toSet()
for (userId in wanted) {
if (!blapProfileAttempts.add(userId)) continue
client.getProfile(userId).getOrNull()?.let { user ->
blapCallUsers = blapCallUsers + (userId to user)
}
}
}
}
var securityBannerDismissed by rememberSaveable { mutableStateOf(false) }
@@ -196,12 +231,22 @@ class RoomListPresenter(
}
sharedPreferences.edit { putStringSet(blapCollapseKey(selectedSpaceId), blapCollapsedSections) }
}
is RoomListEvent.BlapSetRoomOrder -> {
blapManualOrder = event.order
sharedPreferences.edit {
putString(blapOrderKey(selectedSpaceId), event.order.joinToString(",") { it.value })
}
}
}
}
LaunchedEffect(filtersState.filterSelectionStates, spaceFiltersState.selectedFilter()) {
LaunchedEffect(filtersState.filterSelectionStates, spaceFiltersState.selectedFilter(), blapSpaceChildIds) {
val selectedFilters = filtersState.selectedFilters().map { filter -> filter.into() }
val selectedSpaceFilter = spaceFiltersState.selectedFilter().into()
// Blap: union the space's real child list into the SDK's descendants — bridge
// spaces whose children are DM portals (mautrix-telegram) otherwise come up empty.
val selectedSpaceFilter = spaceFiltersState.selectedFilter()?.let { filter ->
RoomListFilter.Identifiers((filter.descendants.toSet() + blapSpaceChildIds).toList())
}
val allFilters = RoomListFilter.All(selectedFilters + listOfNotNull(selectedSpaceFilter))
roomListDataSource.updateFilter(allFilters)
}
@@ -217,6 +262,8 @@ class RoomListPresenter(
showUnreadCount,
blapVoiceRoomIds,
blapCollapsedSections,
blapManualOrder,
blapCallUsers,
)
return RoomListState(
@@ -271,6 +318,8 @@ class RoomListPresenter(
private fun blapCollapseKey(spaceId: RoomId?) = "blap_sections_collapsed_" + (spaceId?.value ?: "all")
private fun blapOrderKey(spaceId: RoomId?) = "blap_room_order_" + (spaceId?.value ?: "all")
private companion object {
const val BLAP_VOICE_ROOMS_KEY = "blap_voice_room_ids"
}
@@ -282,6 +331,8 @@ class RoomListPresenter(
showUnreadCount: Boolean,
blapVoiceRoomIds: Set<RoomId>,
blapCollapsedSections: Set<String>,
blapManualOrder: List<RoomId>,
blapCallUsers: Map<UserId, MatrixUser>,
): RoomListContentState {
val roomSummaries by produceState(initialValue = AsyncData.Loading()) {
roomListDataSource.roomSummariesFlow.collect { value = AsyncData.Success(it) }
@@ -317,6 +368,8 @@ class RoomListPresenter(
seenRoomInvites = seenRoomInvites.toImmutableSet(),
blapVoiceRoomIds = blapVoiceRoomIds.toImmutableSet(),
blapCollapsedSections = blapCollapsedSections.toImmutableSet(),
blapManualOrder = blapManualOrder.toImmutableList(),
blapCallUsers = blapCallUsers.toImmutableMap(),
)
}
}
@@ -17,9 +17,14 @@ import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteS
import io.element.android.features.leaveroom.api.LeaveRoomState
import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsState
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.push.api.battery.BatteryOptimizationState
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.ImmutableMap
import kotlinx.collections.immutable.ImmutableSet
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.persistentMapOf
data class RoomListState(
val contextMenu: ContextMenu,
@@ -77,5 +82,9 @@ sealed interface RoomListContentState {
// Blap: Discord-style sections
val blapVoiceRoomIds: ImmutableSet<RoomId>,
val blapCollapsedSections: ImmutableSet<String>,
// Blap: manual drag-and-drop order (per space, device-local) + resolved
// profiles for users in active calls
val blapManualOrder: ImmutableList<RoomId> = persistentListOf(),
val blapCallUsers: ImmutableMap<UserId, MatrixUser> = persistentMapOf(),
) : RoomListContentState
}
+2
View File
@@ -42,6 +42,7 @@ serialization_json = "1.11.0"
coil = "3.5.0"
# Rollback to 1.0.4, 1.0.5 has this issue: https://github.com/airbnb/Showkase/issues/420
showkase = "1.0.5"
reorderable = "2.5.1"
# There is some custom logic in `RootFlowNode` that may break because it reuses some Appyx internal APIs.
# When upgrading this version, check state restoration still works fine.
appyx = "1.7.1"
@@ -189,6 +190,7 @@ matrix_sdk = "org.matrix.rustcomponents:sdk-android:26.07.15"
# Others
coil = { module = "io.coil-kt.coil3:coil", version.ref = "coil" }
reorderable = { module = "sh.calvin.reorderable:reorderable", version.ref = "reorderable" }
coil_network_okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp", version.ref = "coil" }
coil_compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" }
coil_gif = { module = "io.coil-kt.coil3:coil-gif", version.ref = "coil" }