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
This commit is contained in:
Kayos 2026-03-27 12:29:12 -07:00
parent 06a9c6b0d2
commit 11ebaf5042
4 changed files with 52 additions and 7 deletions

View file

@ -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")

View file

@ -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)
}
}
}

View file

@ -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
}

View file

@ -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).