From 11ebaf50421af9793d135b5ccb8885106738b544 Mon Sep 17 00:00:00 2001 From: Kayos Date: Fri, 27 Mar 2026 12:29:12 -0700 Subject: [PATCH] fix(wallet): resolve sealed interface inheritance issue TimelineItemEventContent is a sealed interface in messages:impl, so external modules cannot add implementers to its hierarchy. Solution: Create TimelineItemPaymentContentWrapper in messages:impl that implements the sealed interface and wraps the wallet API's payment content. Changes: - Remove inheritance from TimelineItemPaymentContent (wallet:api) - Add TimelineItemPaymentContentWrapper (messages:impl) - Update TimelineItemContentFactory to wrap payment content - Update TimelineItemEventContentView to use wrapper --- .../event/TimelineItemEventContentView.kt | 6 +-- .../event/TimelineItemContentFactory.kt | 3 +- .../TimelineItemPaymentContentWrapper.kt | 39 +++++++++++++++++++ .../timeline/TimelineItemPaymentContent.kt | 11 ++++-- 4 files changed, 52 insertions(+), 7 deletions(-) create mode 100644 features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemPaymentContentWrapper.kt diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemEventContentView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemEventContentView.kt index 2ae7fca42a..b324de8ea2 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemEventContentView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemEventContentView.kt @@ -29,8 +29,8 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContent +import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPaymentContentWrapper import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent -import io.element.android.features.wallet.api.timeline.TimelineItemPaymentContent import io.element.android.features.wallet.impl.timeline.TimelineItemPaymentView import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.voiceplayer.api.VoiceMessageState @@ -136,8 +136,8 @@ fun TimelineItemEventContentView( modifier = modifier ) } - is TimelineItemPaymentContent -> TimelineItemPaymentView( - content = content, + is TimelineItemPaymentContentWrapper -> TimelineItemPaymentView( + content = content.paymentContent, modifier = modifier ) is TimelineItemRtcNotificationContent -> error("This shouldn't be rendered as the content of a bubble") diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFactory.kt index f6c3a958ad..3e3d77b537 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFactory.kt @@ -37,6 +37,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecry import io.element.android.libraries.matrix.api.timeline.item.event.UnknownContent import io.element.android.libraries.matrix.api.timeline.item.event.CustomEventContent import io.element.android.libraries.matrix.api.timeline.item.event.getDisambiguatedDisplayName +import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPaymentContentWrapper import io.element.android.features.wallet.impl.timeline.TimelineItemContentPaymentFactory @Inject @@ -65,7 +66,7 @@ class TimelineItemContentFactory( if (rawJson != null) { val paymentContent = paymentFactory.createFromRaw(rawJson, isOutgoing) if (paymentContent != null) { - return paymentContent + return TimelineItemPaymentContentWrapper(paymentContent) } } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemPaymentContentWrapper.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemPaymentContentWrapper.kt new file mode 100644 index 0000000000..70c0c48d08 --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemPaymentContentWrapper.kt @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2026 Sulkta Coop. + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package io.element.android.features.messages.impl.timeline.model.event + +import androidx.compose.runtime.Immutable +import io.element.android.features.wallet.api.PaymentCardStatus +import io.element.android.features.wallet.api.timeline.TimelineItemPaymentContent + +/** + * Wrapper for [TimelineItemPaymentContent] that implements [TimelineItemEventContent]. + * + * This wrapper is necessary because [TimelineItemEventContent] is a sealed interface + * that must have all implementers in the same module. Since the wallet module + * cannot add types to the sealed hierarchy, we wrap the payment content here. + */ +@Immutable +data class TimelineItemPaymentContentWrapper( + val paymentContent: TimelineItemPaymentContent, +) : TimelineItemEventContent { + override val type: String = paymentContent.type + + // Delegate properties for convenience + val amountLovelace: Long get() = paymentContent.amountLovelace + val toAddress: String get() = paymentContent.toAddress + val fromAddress: String get() = paymentContent.fromAddress + val txHash: String? get() = paymentContent.txHash + val status: PaymentCardStatus get() = paymentContent.status + val network: String get() = paymentContent.network + val isSentByMe: Boolean get() = paymentContent.isSentByMe + val fallbackText: String get() = paymentContent.fallbackText + val amountAda: String get() = paymentContent.amountAda + val isTestnet: Boolean get() = paymentContent.isTestnet + val truncatedTxHash: String? get() = paymentContent.truncatedTxHash + val explorerUrl: String? get() = paymentContent.explorerUrl +} diff --git a/features/wallet/api/src/main/kotlin/io/element/android/features/wallet/api/timeline/TimelineItemPaymentContent.kt b/features/wallet/api/src/main/kotlin/io/element/android/features/wallet/api/timeline/TimelineItemPaymentContent.kt index 97419456a6..637d56e712 100644 --- a/features/wallet/api/src/main/kotlin/io/element/android/features/wallet/api/timeline/TimelineItemPaymentContent.kt +++ b/features/wallet/api/src/main/kotlin/io/element/android/features/wallet/api/timeline/TimelineItemPaymentContent.kt @@ -7,12 +7,17 @@ package io.element.android.features.wallet.api.timeline import androidx.compose.runtime.Immutable -import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent import io.element.android.features.wallet.api.PaymentCardStatus /** * Timeline content for a Cardano payment event. * + * This class represents payment event content and can be rendered + * in the timeline. It does NOT inherit from TimelineItemEventContent + * to avoid circular dependencies between wallet:api and messages:impl. + * + * The TimelineItemContentFactory handles this type specially. + * * @property amountLovelace The payment amount in lovelace (1 ADA = 1,000,000 lovelace) * @property toAddress The recipient's Cardano address (Bech32) * @property fromAddress The sender's Cardano address (Bech32) @@ -32,8 +37,8 @@ data class TimelineItemPaymentContent( val network: String, val isSentByMe: Boolean, val fallbackText: String, -) : TimelineItemEventContent { - override val type: String = EVENT_TYPE +) { + val type: String = EVENT_TYPE /** * Amount formatted in ADA (lovelace / 1,000,000).