Skip to content

Update SDK version to 25.03.13 and fix breaking changes #4406

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Mar 19, 2025
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import io.element.android.x.di.DaggerAppComponent
import io.element.android.x.info.logApplicationInfo
import io.element.android.x.initializer.CrashInitializer
import io.element.android.x.initializer.TracingInitializer
import io.element.android.x.initializer.PlatformInitializer

class ElementXApplication : Application(), DaggerComponentOwner {
override val daggerComponent: AppComponent = DaggerAppComponent.factory().create(this)
Expand All @@ -24,7 +24,7 @@
super.onCreate()
AppInitializer.getInstance(this).apply {
initializeComponent(CrashInitializer::class.java)
initializeComponent(TracingInitializer::class.java)
initializeComponent(PlatformInitializer::class.java)

Check warning on line 27 in app/src/main/kotlin/io/element/android/x/ElementXApplication.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/io/element/android/x/ElementXApplication.kt#L27

Added line #L27 was not covered by tests
initializeComponent(CacheCleanerInitializer::class.java)
}
logApplicationInfo(this)
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/kotlin/io/element/android/x/di/AppBindings.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import io.element.android.libraries.core.meta.BuildMeta
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.matrix.api.platform.InitPlatformService
import io.element.android.libraries.matrix.api.tracing.TracingService
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
import io.element.android.services.analytics.api.AnalyticsService
Expand All @@ -27,6 +28,8 @@ interface AppBindings {

fun tracingService(): TracingService

fun platformService(): InitPlatformService

fun bugReporter(): BugReporter

fun lockScreenService(): LockScreenService
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@

private const val ELEMENT_X_TARGET = "elementx"

class TracingInitializer : Initializer<Unit> {
class PlatformInitializer : Initializer<Unit> {

Check warning on line 25 in app/src/main/kotlin/io/element/android/x/initializer/PlatformInitializer.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/io/element/android/x/initializer/PlatformInitializer.kt#L25

Added line #L25 was not covered by tests
override fun create(context: Context) {
val appBindings = context.bindings<AppBindings>()
val tracingService = appBindings.tracingService()
val platformService = appBindings.platformService()

Check warning on line 29 in app/src/main/kotlin/io/element/android/x/initializer/PlatformInitializer.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/io/element/android/x/initializer/PlatformInitializer.kt#L29

Added line #L29 was not covered by tests
val bugReporter = appBindings.bugReporter()
Timber.plant(tracingService.createTimberTree(ELEMENT_X_TARGET))
val preferencesStore = appBindings.preferencesStore()
Expand All @@ -38,7 +39,7 @@
extraTargets = listOf(ELEMENT_X_TARGET),
)
bugReporter.setCurrentTracingLogLevel(logLevel.name)
tracingService.setupTracing(tracingConfiguration)
platformService.init(tracingConfiguration)

Check warning on line 42 in app/src/main/kotlin/io/element/android/x/initializer/PlatformInitializer.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/io/element/android/x/initializer/PlatformInitializer.kt#L42

Added line #L42 was not covered by tests
// Also set env variable for rust back trace
Os.setenv("RUST_BACKTRACE", "1", true)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ class DefaultCallWidgetProvider @Inject constructor(
val baseUrl = appPreferencesStore.getCustomElementCallBaseUrlFlow().firstOrNull()
?: elementCallBaseUrlProvider.provides(matrixClient)
?: ElementCallConfig.DEFAULT_BASE_URL
val widgetSettings = callWidgetSettingsProvider.provide(baseUrl, encrypted = room.isEncrypted)
val isEncrypted = room.info().isEncrypted ?: room.getUpdatedIsEncrypted().getOrThrow()
val widgetSettings = callWidgetSettingsProvider.provide(baseUrl, encrypted = isEncrypted)
val callUrl = room.generateWidgetWebViewUrl(
widgetSettings = widgetSettings,
clientId = clientId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.room.isDm
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
Expand Down Expand Up @@ -74,10 +75,11 @@ private suspend fun showLeaveRoomAlert(
confirmation: MutableState<LeaveRoomState.Confirmation>,
) {
matrixClient.getRoom(roomId)?.use { room ->
val roomInfo = room.roomInfoFlow.first()
confirmation.value = when {
room.isDm -> Dm(roomId)
!room.isPublic -> PrivateRoom(roomId)
room.joinedMemberCount == 1L -> LastUserInRoom(roomId)
roomInfo.isDm -> Dm(roomId)
!roomInfo.isPublic -> PrivateRoom(roomId)
roomInfo.joinedMembersCount == 1L -> LastUserInRoom(roomId)
else -> Generic(roomId)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
import io.element.android.libraries.matrix.test.room.aRoomInfo
import io.element.android.tests.testutils.WarmUpRule
import io.element.android.tests.testutils.lambda.assert
import io.element.android.tests.testutils.lambda.lambdaRecorder
Expand Down Expand Up @@ -52,7 +53,9 @@ class LeaveRoomPresenterTest {
client = FakeMatrixClient().apply {
givenGetRoomResult(
roomId = A_ROOM_ID,
result = FakeMatrixRoom()
result = FakeMatrixRoom().apply {
givenRoomInfo(aRoomInfo(isDirect = false, isPublic = true, joinedMembersCount = 10))
}
)
}
)
Expand All @@ -72,7 +75,9 @@ class LeaveRoomPresenterTest {
client = FakeMatrixClient().apply {
givenGetRoomResult(
roomId = A_ROOM_ID,
result = FakeMatrixRoom(isPublic = false),
result = FakeMatrixRoom().apply {
givenRoomInfo(aRoomInfo(isPublic = false))
},
)
}
)
Expand All @@ -92,7 +97,9 @@ class LeaveRoomPresenterTest {
client = FakeMatrixClient().apply {
givenGetRoomResult(
roomId = A_ROOM_ID,
result = FakeMatrixRoom(joinedMemberCount = 1),
result = FakeMatrixRoom().apply {
givenRoomInfo(aRoomInfo(joinedMembersCount = 1))
},
)
}
)
Expand All @@ -112,7 +119,9 @@ class LeaveRoomPresenterTest {
client = FakeMatrixClient().apply {
givenGetRoomResult(
roomId = A_ROOM_ID,
result = FakeMatrixRoom(activeMemberCount = 2, isDirect = true),
result = FakeMatrixRoom().apply {
givenRoomInfo(aRoomInfo(isDirect = true, activeMembersCount = 2))
},
)
}
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ class ConfirmAccountProviderPresenter @AssistedInject constructor(
val matrixHomeServerDetails = authenticationService.getHomeserverDetails().value!!
if (matrixHomeServerDetails.supportsOidcLogin) {
// Retrieve the details right now
val oidcPrompt = if (params.isAccountCreation) OidcPrompt.Create else OidcPrompt.Consent
val oidcPrompt = if (params.isAccountCreation) OidcPrompt.Create else OidcPrompt.Login
LoginFlow.OidcFlow(authenticationService.getOidcUrl(oidcPrompt).getOrThrow())
} else if (params.isAccountCreation) {
val url = webClientUrlForAuthenticationRetriever.retrieve(homeserverUrl)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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 {
Expand All @@ -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()
Expand All @@ -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 ->
Expand Down Expand Up @@ -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
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()

Expand Down Expand Up @@ -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,
)
}
Expand Down
Loading
Loading