Update SDK version to 25.03.13 and fix breaking changes (#4406)

Breaking changes addressed:
* Make `MatrixClient.getNotificationSettings()` async, cache its result.
* Use `RoomInfo` for accessing the updated room's info.
* Refactor `MatrixRoom` so it always receives an initial `MatrixRoomInfo` value: this value will be used to make `MatrixRoom.roomInfoFlow` a `StateFlow` so we can assume the initial updated Room data will be present.
* Fetch encryption state when loading a room if it's unknown
This commit is contained in:
Jorge Martin Espinosa 2025-03-19 12:52:57 +01:00 committed by GitHub
parent 0c07a8165f
commit fccd881b1f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
76 changed files with 647 additions and 431 deletions

View file

@ -59,11 +59,14 @@ import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugIn
import io.element.android.libraries.mediaplayer.api.MediaPlayer
import io.element.android.services.analytics.api.AnalyticsService
import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
@ContributesNode(RoomScope::class)
class MessagesNode @AssistedInject constructor(
@Assisted buildContext: BuildContext,
@Assisted plugins: List<Plugin>,
private val coroutineScope: CoroutineScope,
private val room: MatrixRoom,
private val analyticsService: AnalyticsService,
messageComposerPresenterFactory: MessageComposerPresenter.Factory,
@ -108,7 +111,7 @@ class MessagesNode @AssistedInject constructor(
super.onBuilt()
lifecycle.subscribe(
onCreate = {
analyticsService.capture(room.toAnalyticsViewRoom())
coroutineScope.launch { analyticsService.capture(room.toAnalyticsViewRoom()) }
},
onDestroy = {
mediaPlayer.close()

View file

@ -128,7 +128,7 @@ class MessagesPresenter @AssistedInject constructor(
override fun present(): MessagesState {
htmlConverterProvider.Update(currentUserId = room.sessionId)
val roomInfo by room.roomInfoFlow.collectAsState(null)
val roomInfo by room.roomInfoFlow.collectAsState()
val localCoroutineScope = rememberCoroutineScope()
val composerState = composerPresenter.present()
val voiceMessageComposerState = voiceMessageComposerPresenter.present()
@ -147,13 +147,13 @@ class MessagesPresenter @AssistedInject constructor(
val userEventPermissions by userEventPermissions(syncUpdateFlow.value)
val roomName: AsyncData<String> by remember {
derivedStateOf { roomInfo?.name?.let { AsyncData.Success(it) } ?: AsyncData.Uninitialized }
derivedStateOf { roomInfo.name?.let { AsyncData.Success(it) } ?: AsyncData.Uninitialized }
}
val roomAvatar: AsyncData<AvatarData> by remember {
derivedStateOf { roomInfo?.avatarData()?.let { AsyncData.Success(it) } ?: AsyncData.Uninitialized }
derivedStateOf { AsyncData.Success(roomInfo.avatarData()) }
}
val heroes by remember {
derivedStateOf { roomInfo?.heroes().orEmpty().toPersistentList() }
derivedStateOf { roomInfo.heroes().toPersistentList() }
}
var hasDismissedInviteDialog by rememberSaveable {
@ -164,14 +164,20 @@ class MessagesPresenter @AssistedInject constructor(
// as those will be handled by the timeline.
withContext(dispatchers.io) {
room.setUnreadFlag(isUnread = false)
// If for some reason the encryption state is unknown, fetch it
if (roomInfo.isEncrypted == null) {
room.getUpdatedIsEncrypted()
}
}
}
val inviteProgress = remember { mutableStateOf<AsyncData<Unit>>(AsyncData.Uninitialized) }
var showReinvitePrompt by remember { mutableStateOf(false) }
LaunchedEffect(hasDismissedInviteDialog, composerState.textEditorState.hasFocus(), syncUpdateFlow.value) {
val composerHasFocus by remember { derivedStateOf { composerState.textEditorState.hasFocus() } }
LaunchedEffect(hasDismissedInviteDialog, composerHasFocus, roomInfo) {
withContext(dispatchers.io) {
showReinvitePrompt = !hasDismissedInviteDialog && composerState.textEditorState.hasFocus() && room.isDm && room.activeMemberCount == 1L
showReinvitePrompt = !hasDismissedInviteDialog && composerHasFocus && roomInfo.isDm && roomInfo.activeMembersCount == 1L
}
}
val isOnline by syncService.isOnline().collectAsState()
@ -189,9 +195,8 @@ class MessagesPresenter @AssistedInject constructor(
val dmRoomMember by room.getDirectRoomMember(membersState)
val roomMemberIdentityStateChanges = identityChangeState.roomMemberIdentityStateChanges
// TODO use `RoomInfo.isEncrypted` as a key here once it's available
LifecycleResumeEffect(dmRoomMember, roomMemberIdentityStateChanges) {
if (room.isEncrypted) {
LifecycleResumeEffect(dmRoomMember, roomInfo.isEncrypted) {
if (roomInfo.isEncrypted == true) {
val dmRoomMemberId = dmRoomMember?.userId
localCoroutineScope.launch {
dmRoomMemberId?.let { userId ->
@ -275,7 +280,7 @@ class MessagesPresenter @AssistedInject constructor(
return AvatarData(
id = id.value,
name = name,
url = avatarUrl ?: room.avatarUrl,
url = avatarUrl ?: room.info().avatarUrl,
size = AvatarSize.TimelineRoom
)
}

View file

@ -202,7 +202,7 @@ class MessageComposerPresenter @AssistedInject constructor(
suspend fun canSendRoomMention(): Boolean {
val userCanSendAtRoom = room.canUserTriggerRoomNotification(currentUserId).getOrDefault(false)
return !room.isDm && userCanSendAtRoom
return !room.isDm() && userCanSendAtRoom
}
// This will trigger a search immediately when `@` is typed

View file

@ -38,11 +38,11 @@ import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.isDm
import io.element.android.libraries.matrix.api.room.powerlevels.canPinUnpin
import io.element.android.libraries.matrix.api.room.powerlevels.canRedactOther
import io.element.android.libraries.matrix.api.room.powerlevels.canRedactOwn
import io.element.android.libraries.matrix.api.room.roomMembers
import io.element.android.libraries.matrix.ui.room.isDmAsState
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.services.analytics.api.AnalyticsService
import io.element.android.services.analyticsproviders.api.trackers.captureInteraction
@ -85,10 +85,12 @@ class PinnedMessagesListPresenter @AssistedInject constructor(
@Composable
override fun present(): PinnedMessagesListState {
val timelineRoomInfo = remember {
val isDm by room.isDmAsState()
val timelineRoomInfo = remember(isDm) {
TimelineRoomInfo(
isDm = room.isDm,
name = room.displayName,
isDm = isDm,
name = room.info().name,
// We don't need to compute those values
userHasPermissionToSendMessage = false,
userHasPermissionToSendReaction = false,

View file

@ -34,6 +34,7 @@ import io.element.android.features.poll.api.actions.EndPollAction
import io.element.android.features.poll.api.actions.SendPollResponseAction
import io.element.android.features.roomcall.api.RoomCallState
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.core.bool.orFalse
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.UniqueId
@ -95,7 +96,7 @@ class TimelinePresenter @AssistedInject constructor(
val lastReadReceiptId = rememberSaveable { mutableStateOf<EventId?>(null) }
val roomInfo by room.roomInfoFlow.collectAsState(initial = null)
val roomInfo by room.roomInfoFlow.collectAsState()
val syncUpdateFlow = room.syncUpdateFlow.collectAsState()
@ -231,15 +232,15 @@ class TimelinePresenter @AssistedInject constructor(
val typingNotificationState = typingNotificationPresenter.present()
val roomCallState = roomCallStatePresenter.present()
val timelineRoomInfo by remember(typingNotificationState, roomCallState) {
val timelineRoomInfo by remember(typingNotificationState, roomCallState, roomInfo) {
derivedStateOf {
TimelineRoomInfo(
name = room.displayName,
isDm = room.isDm,
name = roomInfo.name,
isDm = roomInfo.isDm.orFalse(),
userHasPermissionToSendMessage = userHasPermissionToSendMessage,
userHasPermissionToSendReaction = userHasPermissionToSendReaction,
roomCallState = roomCallState,
pinnedEventIds = roomInfo?.pinnedEventIds.orEmpty(),
pinnedEventIds = roomInfo.pinnedEventIds.orEmpty(),
typingNotificationState = typingNotificationState,
)
}