Merge pull request #3694 from element-hq/feature/bma/oidcPrompt
OIDC prompt
This commit is contained in:
commit
80a052c7cf
37 changed files with 202 additions and 142 deletions
|
|
@ -43,7 +43,7 @@ interface MatrixAuthenticationService {
|
|||
/**
|
||||
* Get the Oidc url to display to the user.
|
||||
*/
|
||||
suspend fun getOidcUrl(): Result<OidcDetails>
|
||||
suspend fun getOidcUrl(prompt: OidcPrompt): Result<OidcDetails>
|
||||
|
||||
/**
|
||||
* Cancel Oidc login sequence.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.auth
|
||||
|
||||
sealed interface OidcPrompt {
|
||||
/**
|
||||
* The Authorization Server must not display any authentication or consent
|
||||
* user interface pages.
|
||||
*/
|
||||
data object None : OidcPrompt
|
||||
|
||||
/**
|
||||
* The Authorization Server should prompt the End-User for
|
||||
* reauthentication.
|
||||
*/
|
||||
data object Login : OidcPrompt
|
||||
|
||||
/**
|
||||
* The Authorization Server should prompt the End-User for consent before
|
||||
* returning information to the Client.
|
||||
*/
|
||||
data object Consent : OidcPrompt
|
||||
|
||||
/**
|
||||
* The Authorization Server should prompt the End-User to select a user
|
||||
* account.
|
||||
*
|
||||
* This enables an End-User who has multiple accounts at the Authorization
|
||||
* Server to select amongst the multiple accounts that they might have
|
||||
* current sessions for.
|
||||
*/
|
||||
data object SelectAccount : OidcPrompt
|
||||
|
||||
/**
|
||||
* The Authorization Server should prompt the End-User to create a user
|
||||
* account.
|
||||
*
|
||||
* Defined in [Initiating User Registration via OpenID Connect](https://openid.net/specs/openid-connect-prompt-create-1_0.html).
|
||||
*/
|
||||
data object Create : OidcPrompt
|
||||
|
||||
/**
|
||||
* An unknown value.
|
||||
*/
|
||||
data class Unknown(val value: String) : OidcPrompt
|
||||
}
|
||||
|
|
@ -25,14 +25,14 @@ interface MatrixMediaLoader {
|
|||
/**
|
||||
* @param source to fetch the data for.
|
||||
* @param mimeType: optional mime type.
|
||||
* @param body: optional body which will be used to name the file.
|
||||
* @param filename: optional String which will be used to name the file.
|
||||
* @param useCache: if true, the rust sdk will cache the media in its store.
|
||||
* @return a [Result] of [MediaFile]
|
||||
*/
|
||||
suspend fun downloadMediaFile(
|
||||
source: MediaSource,
|
||||
mimeType: String?,
|
||||
body: String?,
|
||||
filename: String?,
|
||||
useCache: Boolean = true,
|
||||
): Result<MediaFile>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,5 +10,6 @@ package io.element.android.libraries.matrix.api.room
|
|||
enum class CurrentUserMembership {
|
||||
INVITED,
|
||||
JOINED,
|
||||
LEFT
|
||||
LEFT,
|
||||
KNOCKED,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ data class EventTimelineItem(
|
|||
val transactionId: TransactionId?,
|
||||
val isEditable: Boolean,
|
||||
val canBeRepliedTo: Boolean,
|
||||
val isLocal: Boolean,
|
||||
val isOwn: Boolean,
|
||||
val isRemote: Boolean,
|
||||
val localSendState: LocalEventSendState?,
|
||||
|
|
@ -28,9 +27,9 @@ data class EventTimelineItem(
|
|||
val senderProfile: ProfileTimelineDetails,
|
||||
val timestamp: Long,
|
||||
val content: EventContent,
|
||||
val debugInfoProvider: EventDebugInfoProvider,
|
||||
val origin: TimelineItemEventOrigin?,
|
||||
val messageShieldProvider: EventShieldsProvider,
|
||||
val timelineItemDebugInfoProvider: TimelineItemDebugInfoProvider,
|
||||
val messageShieldProvider: MessageShieldProvider,
|
||||
) {
|
||||
fun inReplyTo(): InReplyTo? {
|
||||
return (content as? MessageContent)?.inReplyTo
|
||||
|
|
@ -46,10 +45,10 @@ data class EventTimelineItem(
|
|||
}
|
||||
}
|
||||
|
||||
fun interface EventDebugInfoProvider {
|
||||
fun get(): TimelineItemDebugInfo
|
||||
fun interface TimelineItemDebugInfoProvider {
|
||||
operator fun invoke(): TimelineItemDebugInfo
|
||||
}
|
||||
|
||||
fun interface EventShieldsProvider {
|
||||
fun getShield(strict: Boolean): MessageShield?
|
||||
fun interface MessageShieldProvider {
|
||||
operator fun invoke(strict: Boolean): MessageShield?
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.impl.auth
|
||||
|
||||
import io.element.android.libraries.matrix.api.auth.OidcPrompt
|
||||
import org.matrix.rustcomponents.sdk.OidcPrompt as RustOidcPrompt
|
||||
|
||||
internal fun OidcPrompt.toRustPrompt(): RustOidcPrompt {
|
||||
return when (this) {
|
||||
OidcPrompt.None -> RustOidcPrompt.None
|
||||
OidcPrompt.Login -> RustOidcPrompt.Login
|
||||
OidcPrompt.Consent -> RustOidcPrompt.Consent
|
||||
OidcPrompt.SelectAccount -> RustOidcPrompt.SelectAccount
|
||||
OidcPrompt.Create -> RustOidcPrompt.Create
|
||||
is OidcPrompt.Unknown -> RustOidcPrompt.Unknown(value)
|
||||
}
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@ import io.element.android.libraries.matrix.api.MatrixClient
|
|||
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
||||
import io.element.android.libraries.matrix.api.auth.MatrixHomeServerDetails
|
||||
import io.element.android.libraries.matrix.api.auth.OidcDetails
|
||||
import io.element.android.libraries.matrix.api.auth.OidcPrompt
|
||||
import io.element.android.libraries.matrix.api.auth.external.ExternalSession
|
||||
import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData
|
||||
import io.element.android.libraries.matrix.api.auth.qrlogin.QrCodeLoginStep
|
||||
|
|
@ -181,11 +182,14 @@ class RustMatrixAuthenticationService @Inject constructor(
|
|||
|
||||
private var pendingOidcAuthorizationData: OidcAuthorizationData? = null
|
||||
|
||||
override suspend fun getOidcUrl(): Result<OidcDetails> {
|
||||
override suspend fun getOidcUrl(prompt: OidcPrompt): Result<OidcDetails> {
|
||||
return withContext(coroutineDispatchers.io) {
|
||||
runCatching {
|
||||
val client = currentClient ?: error("You need to call `setHomeserver()` first")
|
||||
val oidcAuthenticationData = client.urlForOidcLogin(oidcConfigurationProvider.get())
|
||||
val oidcAuthenticationData = client.urlForOidc(
|
||||
oidcConfiguration = oidcConfigurationProvider.get(),
|
||||
prompt = prompt.toRustPrompt(),
|
||||
)
|
||||
val url = oidcAuthenticationData.loginUrl()
|
||||
pendingOidcAuthorizationData = oidcAuthenticationData
|
||||
OidcDetails(url)
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ class RustMediaLoader(
|
|||
override suspend fun downloadMediaFile(
|
||||
source: MediaSource,
|
||||
mimeType: String?,
|
||||
body: String?,
|
||||
filename: String?,
|
||||
useCache: Boolean,
|
||||
): Result<MediaFile> =
|
||||
withContext(mediaDispatcher) {
|
||||
|
|
@ -71,7 +71,7 @@ class RustMediaLoader(
|
|||
source.toRustMediaSource().use { mediaSource ->
|
||||
val mediaFile = innerClient.getMediaFile(
|
||||
mediaSource = mediaSource,
|
||||
body = body,
|
||||
filename = filename,
|
||||
mimeType = mimeType?.takeIf { MimeTypes.hasSubtype(it) } ?: MimeTypes.OctetStream,
|
||||
useCache = useCache,
|
||||
tempDir = cacheDirectory.path,
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import io.element.android.libraries.matrix.impl.room.member.RoomMemberMapper
|
|||
import kotlinx.collections.immutable.ImmutableMap
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.collections.immutable.toPersistentMap
|
||||
import org.matrix.rustcomponents.sdk.Membership
|
||||
import org.matrix.rustcomponents.sdk.RoomHero
|
||||
import org.matrix.rustcomponents.sdk.Membership as RustMembership
|
||||
import org.matrix.rustcomponents.sdk.RoomInfo as RustRoomInfo
|
||||
|
|
@ -65,6 +66,7 @@ fun RustMembership.map(): CurrentUserMembership = when (this) {
|
|||
RustMembership.INVITED -> CurrentUserMembership.INVITED
|
||||
RustMembership.JOINED -> CurrentUserMembership.JOINED
|
||||
RustMembership.LEFT -> CurrentUserMembership.LEFT
|
||||
Membership.KNOCKED -> CurrentUserMembership.KNOCKED
|
||||
}
|
||||
|
||||
fun RustRoomNotificationMode.map(): RoomNotificationMode = when (this) {
|
||||
|
|
|
|||
|
|
@ -12,9 +12,7 @@ import io.element.android.libraries.matrix.api.core.EventId
|
|||
import io.element.android.libraries.matrix.api.core.TransactionId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventDebugInfoProvider
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventReaction
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventShieldsProvider
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield
|
||||
|
|
@ -27,12 +25,10 @@ import kotlinx.collections.immutable.persistentListOf
|
|||
import kotlinx.collections.immutable.toImmutableList
|
||||
import org.matrix.rustcomponents.sdk.EventOrTransactionId
|
||||
import org.matrix.rustcomponents.sdk.EventSendState
|
||||
import org.matrix.rustcomponents.sdk.EventTimelineItemDebugInfoProvider
|
||||
import org.matrix.rustcomponents.sdk.Reaction
|
||||
import org.matrix.rustcomponents.sdk.ShieldState
|
||||
import uniffi.matrix_sdk_common.ShieldStateCode
|
||||
import org.matrix.rustcomponents.sdk.EventSendState as RustEventSendState
|
||||
import org.matrix.rustcomponents.sdk.EventShieldsProvider as RustEventShieldsProvider
|
||||
import org.matrix.rustcomponents.sdk.EventTimelineItem as RustEventTimelineItem
|
||||
import org.matrix.rustcomponents.sdk.EventTimelineItemDebugInfo as RustEventTimelineItemDebugInfo
|
||||
import org.matrix.rustcomponents.sdk.ProfileDetails as RustProfileDetails
|
||||
|
|
@ -48,7 +44,6 @@ class EventTimelineItemMapper(
|
|||
transactionId = eventOrTransactionId.transactionId(),
|
||||
isEditable = isEditable,
|
||||
canBeRepliedTo = canBeRepliedTo,
|
||||
isLocal = isLocal,
|
||||
isOwn = isOwn,
|
||||
isRemote = isRemote,
|
||||
localSendState = localSendState?.map(),
|
||||
|
|
@ -58,9 +53,9 @@ class EventTimelineItemMapper(
|
|||
senderProfile = senderProfile.map(),
|
||||
timestamp = timestamp.toLong(),
|
||||
content = contentMapper.map(content),
|
||||
debugInfoProvider = RustEventDebugInfoProvider(debugInfoProvider),
|
||||
origin = origin?.map(),
|
||||
messageShieldProvider = RustEventShieldsProvider(shieldsProvider)
|
||||
timelineItemDebugInfoProvider = { lazyProvider.debugInfo().map() },
|
||||
messageShieldProvider = { strict -> lazyProvider.getShields(strict)?.map() },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -168,18 +163,6 @@ private fun ShieldState?.map(): MessageShield? {
|
|||
}
|
||||
}
|
||||
|
||||
class RustEventDebugInfoProvider(private val debugInfoProvider: EventTimelineItemDebugInfoProvider) : EventDebugInfoProvider {
|
||||
override fun get(): TimelineItemDebugInfo {
|
||||
return debugInfoProvider.get().map()
|
||||
}
|
||||
}
|
||||
|
||||
class RustEventShieldsProvider(private val shieldsProvider: RustEventShieldsProvider) : EventShieldsProvider {
|
||||
override fun getShield(strict: Boolean): MessageShield? {
|
||||
return shieldsProvider.getShields(strict)?.map()
|
||||
}
|
||||
}
|
||||
|
||||
private fun EventOrTransactionId.eventId(): EventId? {
|
||||
return (this as? EventOrTransactionId.EventId)?.let { EventId(it.eventId) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,8 +7,7 @@
|
|||
|
||||
package io.element.android.libraries.matrix.impl.fixtures.factories
|
||||
|
||||
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeRustEventShieldsProvider
|
||||
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeRustEventTimelineItemDebugInfoProvider
|
||||
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeRustLazyTimelineItemProvider
|
||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||
import io.element.android.libraries.matrix.test.A_USER_ID
|
||||
import org.matrix.rustcomponents.sdk.EventOrTransactionId
|
||||
|
|
@ -23,7 +22,6 @@ import org.matrix.rustcomponents.sdk.TimelineItemContent
|
|||
import uniffi.matrix_sdk_ui.EventItemOrigin
|
||||
|
||||
fun aRustEventTimelineItem(
|
||||
isLocal: Boolean = false,
|
||||
isRemote: Boolean = true,
|
||||
eventOrTransactionId: EventOrTransactionId = EventOrTransactionId.EventId(AN_EVENT_ID.value),
|
||||
sender: String = A_USER_ID.value,
|
||||
|
|
@ -40,7 +38,6 @@ fun aRustEventTimelineItem(
|
|||
canBeRepliedTo: Boolean = true,
|
||||
shieldsState: ShieldState? = null,
|
||||
) = EventTimelineItem(
|
||||
isLocal = isLocal,
|
||||
isRemote = isRemote,
|
||||
eventOrTransactionId = eventOrTransactionId,
|
||||
sender = sender,
|
||||
|
|
@ -50,10 +47,12 @@ fun aRustEventTimelineItem(
|
|||
isEditable = isEditable,
|
||||
canBeRepliedTo = canBeRepliedTo,
|
||||
content = content,
|
||||
debugInfoProvider = FakeRustEventTimelineItemDebugInfoProvider(debugInfo),
|
||||
shieldsProvider = FakeRustEventShieldsProvider(shieldsState),
|
||||
localSendState = localSendState,
|
||||
reactions = reactions,
|
||||
readReceipts = readReceipts,
|
||||
origin = origin,
|
||||
lazyProvider = FakeRustLazyTimelineItemProvider(
|
||||
debugInfo = debugInfo,
|
||||
shieldsState = shieldsState,
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,18 +0,0 @@
|
|||
/*
|
||||
* Copyright 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.impl.fixtures.fakes
|
||||
|
||||
import org.matrix.rustcomponents.sdk.EventShieldsProvider
|
||||
import org.matrix.rustcomponents.sdk.NoPointer
|
||||
import org.matrix.rustcomponents.sdk.ShieldState
|
||||
|
||||
class FakeRustEventShieldsProvider(
|
||||
private val shieldsState: ShieldState? = null,
|
||||
) : EventShieldsProvider(NoPointer) {
|
||||
override fun getShields(strict: Boolean): ShieldState? = shieldsState
|
||||
}
|
||||
|
|
@ -9,11 +9,14 @@ package io.element.android.libraries.matrix.impl.fixtures.fakes
|
|||
|
||||
import io.element.android.libraries.matrix.impl.fixtures.factories.anEventTimelineItemDebugInfo
|
||||
import org.matrix.rustcomponents.sdk.EventTimelineItemDebugInfo
|
||||
import org.matrix.rustcomponents.sdk.EventTimelineItemDebugInfoProvider
|
||||
import org.matrix.rustcomponents.sdk.LazyTimelineItemProvider
|
||||
import org.matrix.rustcomponents.sdk.NoPointer
|
||||
import org.matrix.rustcomponents.sdk.ShieldState
|
||||
|
||||
class FakeRustEventTimelineItemDebugInfoProvider(
|
||||
class FakeRustLazyTimelineItemProvider(
|
||||
private val debugInfo: EventTimelineItemDebugInfo = anEventTimelineItemDebugInfo(),
|
||||
) : EventTimelineItemDebugInfoProvider(NoPointer) {
|
||||
override fun get(): EventTimelineItemDebugInfo = debugInfo
|
||||
private val shieldsState: ShieldState? = null,
|
||||
) : LazyTimelineItemProvider(NoPointer) {
|
||||
override fun getShields(strict: Boolean) = shieldsState
|
||||
override fun debugInfo() = debugInfo
|
||||
}
|
||||
|
|
@ -11,6 +11,7 @@ import io.element.android.libraries.matrix.api.MatrixClient
|
|||
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
||||
import io.element.android.libraries.matrix.api.auth.MatrixHomeServerDetails
|
||||
import io.element.android.libraries.matrix.api.auth.OidcDetails
|
||||
import io.element.android.libraries.matrix.api.auth.OidcPrompt
|
||||
import io.element.android.libraries.matrix.api.auth.external.ExternalSession
|
||||
import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData
|
||||
import io.element.android.libraries.matrix.api.auth.qrlogin.QrCodeLoginStep
|
||||
|
|
@ -80,7 +81,7 @@ class FakeMatrixAuthenticationService(
|
|||
return importCreatedSessionLambda(externalSession)
|
||||
}
|
||||
|
||||
override suspend fun getOidcUrl(): Result<OidcDetails> = simulateLongTask {
|
||||
override suspend fun getOidcUrl(prompt: OidcPrompt): Result<OidcDetails> = simulateLongTask {
|
||||
oidcError?.let { Result.failure(it) } ?: Result.success(A_OIDC_DATA)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ class FakeMatrixMediaLoader : MatrixMediaLoader {
|
|||
override suspend fun downloadMediaFile(
|
||||
source: MediaSource,
|
||||
mimeType: String?,
|
||||
body: String?,
|
||||
filename: String?,
|
||||
useCache: Boolean,
|
||||
): Result<MediaFile> = simulateLongTask {
|
||||
if (shouldFail) {
|
||||
|
|
|
|||
|
|
@ -42,7 +42,6 @@ fun anEventTimelineItem(
|
|||
transactionId: TransactionId? = null,
|
||||
isEditable: Boolean = false,
|
||||
canBeRepliedTo: Boolean = false,
|
||||
isLocal: Boolean = false,
|
||||
isOwn: Boolean = false,
|
||||
isRemote: Boolean = false,
|
||||
localSendState: LocalEventSendState? = null,
|
||||
|
|
@ -59,7 +58,6 @@ fun anEventTimelineItem(
|
|||
transactionId = transactionId,
|
||||
isEditable = isEditable,
|
||||
canBeRepliedTo = canBeRepliedTo,
|
||||
isLocal = isLocal,
|
||||
isOwn = isOwn,
|
||||
isRemote = isRemote,
|
||||
localSendState = localSendState,
|
||||
|
|
@ -69,8 +67,8 @@ fun anEventTimelineItem(
|
|||
senderProfile = senderProfile,
|
||||
timestamp = timestamp,
|
||||
content = content,
|
||||
debugInfoProvider = { debugInfo },
|
||||
origin = null,
|
||||
timelineItemDebugInfoProvider = { debugInfo },
|
||||
messageShieldProvider = { messageShield },
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ class MediaViewerPresenter @AssistedInject constructor(
|
|||
mediaLoader.downloadMediaFile(
|
||||
source = inputs.mediaSource,
|
||||
mimeType = inputs.mediaInfo.mimeType,
|
||||
body = inputs.mediaInfo.filename
|
||||
filename = inputs.mediaInfo.filename
|
||||
)
|
||||
.onSuccess {
|
||||
mediaFile.value = it
|
||||
|
|
|
|||
|
|
@ -299,7 +299,7 @@ class DefaultNotifiableEventResolver @Inject constructor(
|
|||
.getMediaFile(
|
||||
mediaSource = messageType.source,
|
||||
mimeType = messageType.info?.mimetype,
|
||||
body = messageType.filename,
|
||||
filename = messageType.filename,
|
||||
)
|
||||
is VideoMessageType -> null // Use the thumbnail here?
|
||||
else -> null
|
||||
|
|
|
|||
|
|
@ -47,13 +47,13 @@ interface NotificationMediaRepo {
|
|||
*
|
||||
* @param mediaSource the media source of the media.
|
||||
* @param mimeType the mime type of the media.
|
||||
* @param body optional body which will be used to name the file.
|
||||
* @param filename optional String which will be used to name the file.
|
||||
* @return A [Result] holding either the media [File] from the cache directory or an [Exception].
|
||||
*/
|
||||
suspend fun getMediaFile(
|
||||
mediaSource: MediaSource,
|
||||
mimeType: String?,
|
||||
body: String?,
|
||||
filename: String?,
|
||||
): Result<File>
|
||||
}
|
||||
|
||||
|
|
@ -75,7 +75,7 @@ class DefaultNotificationMediaRepo @AssistedInject constructor(
|
|||
override suspend fun getMediaFile(
|
||||
mediaSource: MediaSource,
|
||||
mimeType: String?,
|
||||
body: String?,
|
||||
filename: String?,
|
||||
): Result<File> {
|
||||
val cachedFile = mediaSource.cachedFile()
|
||||
return when {
|
||||
|
|
@ -84,7 +84,7 @@ class DefaultNotificationMediaRepo @AssistedInject constructor(
|
|||
else -> matrixMediaLoader.downloadMediaFile(
|
||||
source = mediaSource,
|
||||
mimeType = mimeType,
|
||||
body = body,
|
||||
filename = filename,
|
||||
).mapCatching {
|
||||
it.use { mediaFile ->
|
||||
val dest = cachedFile.apply { parentFile?.mkdirs() }
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ class FakeNotificationMediaRepo : NotificationMediaRepo {
|
|||
override suspend fun getMediaFile(
|
||||
mediaSource: MediaSource,
|
||||
mimeType: String?,
|
||||
body: String?,
|
||||
filename: String?,
|
||||
): Result<File> {
|
||||
return Result.failure(IllegalStateException("Fake class"))
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue