Join room : change state and view

This commit is contained in:
ganfra
2024-04-12 14:09:57 +02:00
parent 038d8e3334
commit c0918bd965
11 changed files with 228 additions and 165 deletions

View File

@@ -26,7 +26,6 @@ import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
import io.element.android.features.invite.api.response.InviteData
import io.element.android.features.roomdirectory.api.RoomDescription
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.RoomId
@@ -49,18 +48,16 @@ class JoinRoomPresenter @AssistedInject constructor(
@Composable
override fun present(): JoinRoomState {
val roomInfo by matrixClient.getRoomInfoFlow(roomId).collectAsState(initial = Optional.empty())
val contentState by produceState<AsyncData<ContentState>>(initialValue = AsyncData.Uninitialized, key1 = roomInfo) {
val contentState by produceState<ContentState>(initialValue = ContentState.Loading(roomId), key1 = roomInfo) {
value = when {
roomInfo.isPresent -> {
val contentState = roomInfo.get().toContentState()
AsyncData.Success(contentState)
roomInfo.get().toContentState()
}
roomDescription.isPresent -> {
val contentState = roomDescription.get().toContentState()
AsyncData.Success(contentState)
roomDescription.get().toContentState()
}
else -> {
AsyncData.Uninitialized
ContentState.UnknownRoom(roomId)
}
}
}
@@ -93,10 +90,11 @@ class JoinRoomPresenter @AssistedInject constructor(
@VisibleForTesting
internal fun RoomDescription.toContentState(): ContentState {
return ContentState(
return ContentState.Loaded(
roomId = roomId,
name = name,
description = description,
topic = topic,
alias = alias,
numberOfMembers = numberOfMembers,
isDirect = false,
roomAvatarUrl = avatarUrl,
@@ -110,26 +108,11 @@ internal fun RoomDescription.toContentState(): ContentState {
@VisibleForTesting
internal fun MatrixRoomInfo.toContentState(): ContentState {
fun title(): String {
return name ?: canonicalAlias ?: id
}
fun description(): String? {
val topic = topic
val alias = canonicalAlias
val name = name
return when {
topic != null -> topic
name != null && alias != null -> alias
name == null && alias == null -> null
else -> id
}
}
return ContentState(
return ContentState.Loaded(
roomId = RoomId(id),
name = title(),
description = description(),
name = name,
topic = topic,
alias = canonicalAlias,
numberOfMembers = activeMembersCount,
isDirect = isDirect,
roomAvatarUrl = avatarUrl,
@@ -142,12 +125,13 @@ internal fun MatrixRoomInfo.toContentState(): ContentState {
}
@VisibleForTesting
internal fun AsyncData<ContentState>.toInviteData(): InviteData? {
return dataOrNull()?.let { contentState ->
InviteData(
roomId = contentState.roomId,
roomName = contentState.name,
isDirect = contentState.isDirect
internal fun ContentState.toInviteData(): InviteData? {
return when (this) {
is ContentState.Loaded -> InviteData(
roomId = roomId,
roomName = computedTitle,
isDirect = isDirect
)
else -> null
}
}

View File

@@ -18,39 +18,53 @@ package io.element.android.features.joinroom.impl
import androidx.compose.runtime.Immutable
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.matrix.api.core.RoomId
@Immutable
data class JoinRoomState(
val contentState: AsyncData<ContentState>,
val contentState: ContentState,
val acceptDeclineInviteState: AcceptDeclineInviteState,
val eventSink: (JoinRoomEvents) -> Unit
) {
val joinAuthorisationStatus = contentState.dataOrNull()?.joinAuthorisationStatus ?: JoinAuthorisationStatus.Unknown
val joinAuthorisationStatus = when(contentState) {
is ContentState.Loaded -> contentState.joinAuthorisationStatus
else -> JoinAuthorisationStatus.Unknown
}
}
data class ContentState(
val roomId: RoomId,
val name: String,
val description: String?,
val numberOfMembers: Long?,
val isDirect: Boolean,
val roomAvatarUrl: String?,
val joinAuthorisationStatus: JoinAuthorisationStatus,
) {
sealed interface ContentState {
data class Loading(val roomId: RoomId) : ContentState
data class UnknownRoom(val roomId: RoomId) : ContentState
data class Loaded(
val roomId: RoomId,
val name: String?,
val topic: String?,
val alias: String?,
val numberOfMembers: Long?,
val isDirect: Boolean,
val roomAvatarUrl: String?,
val joinAuthorisationStatus: JoinAuthorisationStatus,
) : ContentState {
val computedTitle = name ?: roomId.value
val showMemberCount = numberOfMembers != null
val computedSubtitle = when {
alias != null -> alias
name == null -> ""
else -> roomId.value
}
fun avatarData(size: AvatarSize): AvatarData {
return AvatarData(
id = roomId.value,
name = name,
url = roomAvatarUrl,
size = size,
)
val showMemberCount = numberOfMembers != null
fun avatarData(size: AvatarSize): AvatarData {
return AvatarData(
id = roomId.value,
name = name,
url = roomAvatarUrl,
size = size,
)
}
}
}

View File

@@ -19,45 +19,47 @@ package io.element.android.features.joinroom.impl
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
import io.element.android.features.invite.api.response.anAcceptDeclineInviteState
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.matrix.api.core.RoomId
open class JoinRoomStateProvider : PreviewParameterProvider<JoinRoomState> {
override val values: Sequence<JoinRoomState>
get() = sequenceOf(
aJoinRoomState(
contentState = AsyncData.Uninitialized
contentState = anUninitializedContentState()
),
aJoinRoomState(
contentState = AsyncData.Success(
aContentState(joinAuthorisationStatus = JoinAuthorisationStatus.CanJoin)
)
contentState = anUnknownContentState()
),
aJoinRoomState(
contentState = AsyncData.Success(
aContentState(joinAuthorisationStatus = JoinAuthorisationStatus.CanKnock)
)
contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.CanJoin)
),
aJoinRoomState(
contentState = AsyncData.Success(
aContentState(joinAuthorisationStatus = JoinAuthorisationStatus.IsInvited)
)
contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.CanKnock)
),
aJoinRoomState(
contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.IsInvited)
),
)
}
fun aContentState(
fun anUnknownContentState(roomId: RoomId = RoomId("@exa:matrix.org")) = ContentState.UnknownRoom(roomId)
fun anUninitializedContentState(roomId: RoomId = RoomId("@exa:matrix.org")) = ContentState.Loading(roomId)
fun aLoadedContentState(
roomId: RoomId = RoomId("@exa:matrix.org"),
name: String = "Element x android",
description: String? = "#exa:matrix.org",
alias: String? = "#exa:matrix.org",
topic: String? = "Element X is a secure, private and decentralized messenger.",
numberOfMembers: Long? = null,
isDirect: Boolean = false,
roomAvatarUrl: String? = null,
joinAuthorisationStatus: JoinAuthorisationStatus = JoinAuthorisationStatus.Unknown
) = ContentState(
) = ContentState.Loaded(
roomId = roomId,
name = name,
description = description,
alias = alias,
topic = topic,
numberOfMembers = numberOfMembers,
isDirect = isDirect,
roomAvatarUrl = roomAvatarUrl,
@@ -65,9 +67,7 @@ fun aContentState(
)
fun aJoinRoomState(
contentState: AsyncData<ContentState> = AsyncData.Success(
aContentState()
),
contentState: ContentState = aLoadedContentState(),
acceptDeclineInviteState: AcceptDeclineInviteState = anAcceptDeclineInviteState(),
eventSink: (JoinRoomEvents) -> Unit = {}
) = JoinRoomState(

View File

@@ -16,9 +16,11 @@
package io.element.android.features.joinroom.impl
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
@@ -30,14 +32,15 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.PreviewLightDark
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.designsystem.atomic.atoms.PlaceholderAtom
import io.element.android.libraries.designsystem.atomic.molecules.ButtonRowMolecule
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
@@ -61,11 +64,12 @@ fun JoinRoomView(
) {
HeaderFooterPage(
modifier = modifier,
paddingValues = PaddingValues(16.dp),
topBar = {
JoinRoomTopBar(onBackClicked = onBackPressed)
},
content = {
JoinRoomContent(asyncContentState = state.contentState)
JoinRoomContent(contentState = state.contentState)
},
footer = {
JoinRoomFooter(
@@ -111,22 +115,19 @@ private fun JoinRoomFooter(
}
JoinAuthorisationStatus.CanJoin -> {
Button(
text = stringResource(CommonStrings.action_join),
text = stringResource(R.string.screen_join_room_join_action),
onClick = onJoinRoom,
modifier = modifier.fillMaxWidth(),
size = ButtonSize.Medium,
)
}
JoinAuthorisationStatus.CanKnock -> {
//TODO knock
/*
Button(
text = stringResource(CommonStrings.action_knock),
text = stringResource(R.string.screen_join_room_knock_action),
onClick = onJoinRoom,
modifier = modifier.fillMaxWidth(),
size = ButtonSize.Medium,
)
*/
}
JoinAuthorisationStatus.Unknown -> Unit
}
@@ -134,63 +135,60 @@ private fun JoinRoomFooter(
@Composable
private fun JoinRoomContent(
asyncContentState: AsyncData<ContentState>,
contentState: ContentState,
modifier: Modifier = Modifier,
) {
@Composable
fun ContentScaffold(
avatar: @Composable () -> Unit,
title: String,
description: String,
memberCount: @Composable (() -> Unit)? = null
) {
avatar()
Spacer(modifier = Modifier.height(16.dp))
Text(
text = title,
style = ElementTheme.typography.fontHeadingMdBold,
textAlign = TextAlign.Center,
color = ElementTheme.colors.textPrimary,
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = description,
style = ElementTheme.typography.fontBodyMdRegular,
textAlign = TextAlign.Center,
color = ElementTheme.colors.textSecondary,
)
memberCount?.invoke()
}
Column(
modifier = modifier
.fillMaxWidth()
.padding(all = 16.dp),
modifier = modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
when (asyncContentState) {
is AsyncData.Success -> {
val contentState = asyncContentState.data
when (contentState) {
is ContentState.Loaded -> {
ContentScaffold(
avatar = {
Avatar(contentState.avatarData(AvatarSize.RoomHeader))
},
title = contentState.name,
description = contentState.description ?: stringResource(R.string.screen_join_room_subtitle_no_preview)
) {
if (contentState.showMemberCount) {
JoinRoomMembersCount(memberCount = contentState.numberOfMembers ?: 0)
title = {
Title(contentState.computedTitle)
},
subtitle = {
Subtitle(contentState.computedSubtitle)
},
description = {
Description(contentState.topic ?: "")
},
memberCount = {
if (contentState.showMemberCount) {
MembersCount(memberCount = contentState.numberOfMembers ?: 0)
}
}
}
)
}
else -> {
is ContentState.UnknownRoom -> {
ContentScaffold(
avatar = {
PlaceholderAtom(width = AvatarSize.RoomHeader.dp, height = AvatarSize.RoomHeader.dp)
},
title = stringResource(R.string.screen_join_room_title_no_preview),
description = stringResource(R.string.screen_join_room_subtitle_no_preview),
title = {
Title(stringResource(R.string.screen_join_room_title_no_preview))
},
subtitle = {
Subtitle(stringResource(R.string.screen_join_room_subtitle_no_preview))
},
)
}
is ContentState.Loading -> {
ContentScaffold(
avatar = {
PlaceholderAtom(width = AvatarSize.RoomHeader.dp, height = AvatarSize.RoomHeader.dp)
},
title = {
PlaceholderAtom(width = 200.dp, height = 22.dp)
},
subtitle = {
PlaceholderAtom(width = 140.dp, height = 20.dp)
},
)
}
}
@@ -198,13 +196,72 @@ private fun JoinRoomContent(
}
@Composable
private fun JoinRoomMembersCount(memberCount: Long) {
private fun ContentScaffold(
avatar: @Composable () -> Unit,
title: @Composable () -> Unit,
subtitle: @Composable () -> Unit,
description: @Composable (() -> Unit)? = null,
memberCount: @Composable (() -> Unit)? = null,
) {
avatar()
Spacer(modifier = Modifier.height(16.dp))
title()
Spacer(modifier = Modifier.height(8.dp))
subtitle()
Spacer(modifier = Modifier.height(8.dp))
if (memberCount != null) {
memberCount()
}
Spacer(modifier = Modifier.height(8.dp))
if (description != null) {
description()
}
Spacer(modifier = Modifier.height(24.dp))
}
@Composable
private fun Title(title: String, modifier: Modifier = Modifier) {
Text(
modifier = modifier,
text = title,
style = ElementTheme.typography.fontHeadingMdBold,
textAlign = TextAlign.Center,
color = ElementTheme.colors.textPrimary,
)
}
@Composable
private fun Subtitle(subtitle: String, modifier: Modifier = Modifier) {
Text(
modifier = modifier,
text = subtitle,
style = ElementTheme.typography.fontBodyLgRegular,
textAlign = TextAlign.Center,
color = ElementTheme.colors.textSecondary,
)
}
@Composable
private fun Description(description: String, modifier: Modifier = Modifier) {
Text(
modifier = modifier,
text = description,
style = ElementTheme.typography.fontBodySmRegular,
textAlign = TextAlign.Center,
color = ElementTheme.colors.textSecondary,
maxLines = 3,
overflow = TextOverflow.Ellipsis,
)
}
@Composable
private fun MembersCount(memberCount: Long) {
Spacer(modifier = Modifier.height(8.dp))
Row(
modifier = Modifier
.background(color = ElementTheme.colors.bgSubtleSecondary, shape = CircleShape)
.widthIn(min = 48.dp)
.padding(all = 2.dp),
.background(color = ElementTheme.colors.bgSubtleSecondary, shape = CircleShape)
.widthIn(min = 48.dp)
.padding(all = 2.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp)
) {
@@ -230,9 +287,7 @@ private fun JoinRoomTopBar(
navigationIcon = {
BackButton(onClick = onBackClicked)
},
title = {
},
title = {},
)
}

View File

@@ -21,7 +21,6 @@ import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
import io.element.android.features.invite.api.response.anAcceptDeclineInviteState
import io.element.android.features.roomdirectory.api.RoomDescription
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.RoomId
@@ -51,10 +50,13 @@ class JoinRoomPresenterTest {
val presenter = createJoinRoomPresenter()
presenter.test {
awaitItem().also { state ->
assertThat(state.contentState).isInstanceOf(AsyncData.Uninitialized::class.java)
assertThat(state.contentState).isEqualTo(ContentState.Loading(A_ROOM_ID))
assertThat(state.joinAuthorisationStatus).isEqualTo(JoinAuthorisationStatus.Unknown)
assertThat(state.acceptDeclineInviteState).isEqualTo(anAcceptDeclineInviteState())
}
awaitItem().also { state ->
assertThat(state.contentState).isEqualTo(ContentState.UnknownRoom(A_ROOM_ID))
}
}
}
@@ -72,11 +74,11 @@ class JoinRoomPresenterTest {
presenter.test {
skipItems(1)
awaitItem().also { state ->
assertThat(state.contentState).isInstanceOf(AsyncData.Success::class.java)
val contentState = state.contentState.dataOrNull()!!
val contentState = state.contentState as ContentState.Loaded
assertThat(contentState.roomId).isEqualTo(A_ROOM_ID)
assertThat(contentState.name).isEqualTo(roomInfo.name)
assertThat(contentState.description).isEqualTo(roomInfo.topic)
assertThat(contentState.topic).isEqualTo(roomInfo.topic)
assertThat(contentState.alias).isEqualTo(roomInfo.canonicalAlias)
assertThat(contentState.numberOfMembers).isEqualTo(roomInfo.activeMembersCount)
assertThat(contentState.isDirect).isEqualTo(roomInfo.isDirect)
assertThat(contentState.roomAvatarUrl).isEqualTo(roomInfo.avatarUrl)
@@ -186,11 +188,11 @@ class JoinRoomPresenterTest {
presenter.test {
skipItems(1)
awaitItem().also { state ->
assertThat(state.contentState).isInstanceOf(AsyncData.Success::class.java)
val contentState = state.contentState.dataOrNull()!!
val contentState = state.contentState as ContentState.Loaded
assertThat(contentState.roomId).isEqualTo(A_ROOM_ID)
assertThat(contentState.name).isEqualTo(roomDescription.name)
assertThat(contentState.description).isEqualTo(roomDescription.description)
assertThat(contentState.topic).isEqualTo(roomDescription.topic)
assertThat(contentState.alias).isEqualTo(roomDescription.alias)
assertThat(contentState.numberOfMembers).isEqualTo(roomDescription.numberOfMembers)
assertThat(contentState.isDirect).isFalse()
assertThat(contentState.roomAvatarUrl).isEqualTo(roomDescription.avatarUrl)
@@ -256,8 +258,9 @@ class JoinRoomPresenterTest {
private fun aRoomDescription(
roomId: RoomId = A_ROOM_ID,
name: String = A_ROOM_NAME,
description: String = "A room about something",
name: String? = A_ROOM_NAME,
topic: String? = "A room about something",
alias: String? = "#alias:matrix.org",
avatarUrl: String? = null,
joinRule: RoomDescription.JoinRule = RoomDescription.JoinRule.UNKNOWN,
numberOfMembers: Long = 2L
@@ -265,7 +268,8 @@ class JoinRoomPresenterTest {
return RoomDescription(
roomId = roomId,
name = name,
description = description,
topic = topic,
alias = alias,
avatarUrl = avatarUrl,
joinRule = joinRule,
numberOfMembers = numberOfMembers

View File

@@ -17,16 +17,19 @@
package io.element.android.features.roomdirectory.api
import android.os.Parcelable
import androidx.compose.runtime.Immutable
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.matrix.api.core.RoomId
import kotlinx.parcelize.Parcelize
@Parcelize
@Immutable
data class RoomDescription(
val roomId: RoomId,
val name: String,
val description: String,
val name: String?,
val alias: String?,
val topic: String?,
val avatarUrl: String?,
val joinRule: JoinRule,
val numberOfMembers: Long,
@@ -38,6 +41,18 @@ data class RoomDescription(
UNKNOWN
}
val computedName = name ?: alias ?: roomId.value
val computedDescription: String
get() {
return when {
topic != null -> topic
name != null && alias != null -> alias
name == null && alias == null -> ""
else -> roomId.value
}
}
fun canBeJoined() = joinRule == JoinRule.PUBLIC || joinRule == JoinRule.KNOCK
fun avatarData(size: AvatarSize) = AvatarData(

View File

@@ -68,7 +68,8 @@ fun aRoomDescriptionList(): ImmutableList<RoomDescription> {
RoomDescription(
roomId = RoomId("!exa:matrix.org"),
name = "Element X Android",
description = "Element X is a secure, private and decentralized messenger.",
topic = "Element X is a secure, private and decentralized messenger.",
alias = "#element-x-android:matrix.org",
avatarUrl = null,
joinRule = RoomDescription.JoinRule.PUBLIC,
numberOfMembers = 2765,
@@ -76,7 +77,8 @@ fun aRoomDescriptionList(): ImmutableList<RoomDescription> {
RoomDescription(
roomId = RoomId("!exi:matrix.org"),
name = "Element X iOS",
description = "Element X is a secure, private and decentralized messenger.",
topic = "Element X is a secure, private and decentralized messenger.",
alias = "#element-x-ios:matrix.org",
avatarUrl = null,
joinRule = RoomDescription.JoinRule.UNKNOWN,
numberOfMembers = 356,

View File

@@ -292,14 +292,14 @@ private fun RoomDirectoryRoomRow(
.padding(start = 16.dp)
) {
Text(
text = roomDescription.name,
text = roomDescription.computedName,
maxLines = 1,
style = ElementTheme.typography.fontBodyLgRegular,
color = ElementTheme.colors.textPrimary,
overflow = TextOverflow.Ellipsis,
)
Text(
text = roomDescription.description,
text = roomDescription.computedDescription,
maxLines = 1,
style = ElementTheme.typography.fontBodyMdRegular,
color = ElementTheme.colors.textSecondary,

View File

@@ -17,31 +17,15 @@
package io.element.android.features.roomdirectory.impl.root.model
import io.element.android.features.roomdirectory.api.RoomDescription
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.matrix.api.roomdirectory.RoomDescription as MatrixRoomDescription
fun MatrixRoomDescription.toFeatureModel(): RoomDescription {
fun name(): String {
return name ?: alias ?: roomId.value
}
fun description(): String {
val topic = topic
val alias = alias
val name = name
return when {
topic != null -> topic
name != null && alias != null -> alias
name == null && alias == null -> ""
else -> roomId.value
}
}
return RoomDescription(
roomId = roomId,
name = name(),
description = description(),
name = name,
alias = alias,
topic = topic,
avatarUrl = avatarUrl,
numberOfMembers = numberOfMembers,
joinRule = when (joinRule) {

View File

@@ -18,6 +18,7 @@ package io.element.android.libraries.designsystem.atomic.pages
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
@@ -43,6 +44,7 @@ import io.element.android.libraries.designsystem.theme.components.Text
@Composable
fun HeaderFooterPage(
modifier: Modifier = Modifier,
paddingValues: PaddingValues = PaddingValues(20.dp),
background: @Composable () -> Unit = {},
topBar: @Composable () -> Unit = {},
header: @Composable () -> Unit = {},
@@ -57,7 +59,7 @@ fun HeaderFooterPage(
background()
Column(
modifier = Modifier
.padding(all = 20.dp)
.padding(paddingValues = paddingValues)
.padding(padding)
.consumeWindowInsets(padding)
) {

View File

@@ -16,6 +16,7 @@
package io.element.android.libraries.matrix.impl.util
import io.element.android.libraries.core.data.tryOrNull
import kotlinx.coroutines.channels.ProducerScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.callbackFlow
@@ -23,7 +24,9 @@ import org.matrix.rustcomponents.sdk.TaskHandle
internal fun <T> mxCallbackFlow(block: suspend ProducerScope<T>.() -> TaskHandle?) =
callbackFlow {
val taskHandle: TaskHandle? = block(this)
val taskHandle: TaskHandle? = tryOrNull {
block(this)
}
awaitClose {
taskHandle?.cancelAndDestroy()
}