Skip to content
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

feat: send and receive in-call reactions [#WPB-14254] #3190

Merged
merged 1 commit into from
Dec 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Wire
* Copyright (C) 2024 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
package com.wire.kalium.logic.data.call

import com.wire.kalium.logic.data.id.ConversationId
import com.wire.kalium.logic.data.id.QualifiedID

data class InCallReactionMessage(
val conversationId: ConversationId,
val senderUserId: QualifiedID,
val emojis: Set<String>,
)
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,11 @@ sealed interface Message {
typeKey to "dataTransfer",
"content" to content.toLogMap(),
)

is MessageContent.InCallEmoji -> mutableMapOf(
typeKey to "inCallEmoji",
"content" to content.emojis
)
}

val standardProperties = mapOf(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,10 @@ sealed interface MessageContent {
data object Disabled : ForConversation()
}
}

data class InCallEmoji(
val emojis: Map<String, Int>
) : Signaling
}

/**
Expand Down Expand Up @@ -455,6 +459,7 @@ fun MessageContent?.getType() = when (this) {
is MessageContent.LegalHold.ForMembers.Disabled -> "LegalHold.ForMembers.Disabled"
is MessageContent.LegalHold.ForMembers.Enabled -> "LegalHold.ForMembers.Enabled"
is MessageContent.DataTransfer -> "DataTransfer"
is MessageContent.InCallEmoji -> "InCallEmoji"
null -> "null"
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,5 @@ inline fun MessageContent.FromProto.typeDescription(): String = when (this) {
is MessageContent.Receipt -> "Receipt"
is MessageContent.TextEdited -> "TextEdited"
is MessageContent.DataTransfer -> "DataTransfer"
is MessageContent.InCallEmoji -> "InCallEmoji"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Wire
* Copyright (C) 2024 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
package com.wire.kalium.logic.data.call

import com.wire.kalium.logic.data.id.ConversationId
import com.wire.kalium.logic.data.user.UserId
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.filter

internal interface InCallReactionsRepository {
suspend fun addInCallReaction(conversationId: ConversationId, senderUserId: UserId, emojis: Set<String>)
fun observeInCallReactions(conversationId: ConversationId): Flow<InCallReactionMessage>
}

internal class InCallReactionsDataSource : InCallReactionsRepository {

private val inCallReactionsFlow: MutableSharedFlow<InCallReactionMessage> =
MutableSharedFlow(extraBufferCapacity = BUFFER_SIZE, onBufferOverflow = BufferOverflow.DROP_OLDEST)

override suspend fun addInCallReaction(conversationId: ConversationId, senderUserId: UserId, emojis: Set<String>) {
inCallReactionsFlow.emit(InCallReactionMessage(conversationId, senderUserId, emojis))
}

override fun observeInCallReactions(conversationId: ConversationId): Flow<InCallReactionMessage> = inCallReactionsFlow.asSharedFlow()
.filter { it.conversationId == conversationId }

private companion object {
const val BUFFER_SIZE = 32 // drop after this threshold
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ internal class PersistMessageUseCaseImpl(
is MessageContent.MemberChange.RemovedFromTeam -> false
is MessageContent.TeamMemberRemoved -> false
is MessageContent.DataTransfer -> false
is MessageContent.InCallEmoji -> false
}

@Suppress("ComplexMethod")
Expand Down Expand Up @@ -180,6 +181,7 @@ internal class PersistMessageUseCaseImpl(
is MessageContent.LegalHold,
is MessageContent.MemberChange.RemovedFromTeam,
is MessageContent.TeamMemberRemoved,
is MessageContent.DataTransfer -> false
is MessageContent.DataTransfer,
is MessageContent.InCallEmoji -> false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ import com.wire.kalium.protobuf.messages.DataTransfer
import com.wire.kalium.protobuf.messages.Ephemeral
import com.wire.kalium.protobuf.messages.External
import com.wire.kalium.protobuf.messages.GenericMessage
import com.wire.kalium.protobuf.messages.GenericMessage.UnknownStrategy
import com.wire.kalium.protobuf.messages.InCallEmoji
import com.wire.kalium.protobuf.messages.Knock
import com.wire.kalium.protobuf.messages.LastRead
import com.wire.kalium.protobuf.messages.LegalHoldStatus
Expand All @@ -57,7 +59,6 @@ import com.wire.kalium.protobuf.messages.Quote
import com.wire.kalium.protobuf.messages.Reaction
import com.wire.kalium.protobuf.messages.Text
import com.wire.kalium.protobuf.messages.TrackingIdentifier
import com.wire.kalium.protobuf.messages.UnknownStrategy
import kotlinx.datetime.Instant
import pbandk.ByteArr

Expand Down Expand Up @@ -146,6 +147,7 @@ class ProtoContentMapperImpl(
is MessageContent.Location -> packLocation(readableContent, expectsReadConfirmation, legalHoldStatus)

is MessageContent.DataTransfer -> packDataTransfer(readableContent)
is MessageContent.InCallEmoji -> packInCallEmoji(readableContent)
}
}

Expand Down Expand Up @@ -266,7 +268,8 @@ class ProtoContentMapperImpl(
is MessageContent.ButtonAction,
is MessageContent.ButtonActionConfirmation,
is MessageContent.TextEdited,
is MessageContent.DataTransfer -> throw IllegalArgumentException(
is MessageContent.DataTransfer,
is MessageContent.InCallEmoji -> throw IllegalArgumentException(
"Unexpected message content type: ${readableContent.getType()}"
)
}
Expand Down Expand Up @@ -377,6 +380,8 @@ class ProtoContentMapperImpl(
MessageContent.Ignored
}

is GenericMessage.Content.InCallEmoji -> unpackInCallEmoji(protoContent)

null -> {
kaliumLogger.w(
"Null content when parsing protobuf. Message UUID = ${genericMessage.messageId.obfuscateId()}" +
Expand All @@ -390,6 +395,8 @@ class ProtoContentMapperImpl(
null -> MessageContent.Ignored
}
}

is GenericMessage.Content.InCallHandRaise -> MessageContent.Ignored
}
return readableContent
}
Expand Down Expand Up @@ -752,6 +759,29 @@ class ProtoContentMapperImpl(
)
}

private fun unpackInCallEmoji(protoContent: GenericMessage.Content.InCallEmoji): MessageContent.InCallEmoji {
return MessageContent.InCallEmoji(
// Map of emoji to senderId
emojis = protoContent.value.emojis
.mapNotNull {
val key = it.key ?: return@mapNotNull null
val value = it.value ?: return@mapNotNull null
key to value
}
.associateBy({ it.first }, { it.second })
)
}

private fun packInCallEmoji(content: MessageContent.InCallEmoji): GenericMessage.Content.InCallEmoji {
return GenericMessage.Content.InCallEmoji(
inCallEmoji = InCallEmoji(
emojis = content.emojis.map { entry ->
InCallEmoji.EmojisEntry(key = entry.key, value = entry.value)
}
)
)
}

private fun extractConversationId(
qualifiedConversationID: QualifiedConversationId?,
unqualifiedConversationID: String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ import com.wire.kalium.logic.data.asset.KaliumFileSystem
import com.wire.kalium.logic.data.asset.KaliumFileSystemImpl
import com.wire.kalium.logic.data.call.CallDataSource
import com.wire.kalium.logic.data.call.CallRepository
import com.wire.kalium.logic.data.call.InCallReactionsDataSource
import com.wire.kalium.logic.data.call.InCallReactionsRepository
import com.wire.kalium.logic.data.call.VideoStateChecker
import com.wire.kalium.logic.data.call.VideoStateCheckerImpl
import com.wire.kalium.logic.data.call.mapper.CallMapper
Expand Down Expand Up @@ -1387,7 +1389,8 @@ class UserSessionScope internal constructor(
receiptMessageHandler,
buttonActionConfirmationHandler,
dataTransferEventHandler,
userId
inCallReactionsRepository,
userId,
)

private val staleEpochVerifier: StaleEpochVerifier
Expand Down Expand Up @@ -2066,7 +2069,8 @@ class UserSessionScope internal constructor(
userConfigRepository = userConfigRepository,
getCallConversationType = getCallConversationType,
conversationClientsInCallUpdater = conversationClientsInCallUpdater,
kaliumConfigs = kaliumConfigs
kaliumConfigs = kaliumConfigs,
inCallReactionsRepository = inCallReactionsRepository,
)

val connection: ConnectionScope
Expand Down Expand Up @@ -2165,6 +2169,8 @@ class UserSessionScope internal constructor(
)
}

private val inCallReactionsRepository: InCallReactionsRepository = InCallReactionsDataSource()

/**
* This will start subscribers of observable work per user session, as long as the user is logged in.
* When the user logs out, this work will be canceled.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import com.wire.kalium.logic.configuration.UserConfigRepository
import com.wire.kalium.logic.data.call.CallRepository
import com.wire.kalium.logic.data.call.CallingParticipantsOrder
import com.wire.kalium.logic.data.call.CallingParticipantsOrderImpl
import com.wire.kalium.logic.data.call.InCallReactionsRepository
import com.wire.kalium.logic.data.call.ParticipantsFilterImpl
import com.wire.kalium.logic.data.call.ParticipantsOrderByNameImpl
import com.wire.kalium.logic.data.conversation.ConversationRepository
Expand All @@ -43,8 +44,6 @@ import com.wire.kalium.logic.feature.call.usecase.GetAllCallsWithSortedParticipa
import com.wire.kalium.logic.feature.call.usecase.GetCallConversationTypeProvider
import com.wire.kalium.logic.feature.call.usecase.GetIncomingCallsUseCase
import com.wire.kalium.logic.feature.call.usecase.GetIncomingCallsUseCaseImpl
import com.wire.kalium.logic.feature.call.usecase.ObserveConferenceCallingEnabledUseCase
import com.wire.kalium.logic.feature.call.usecase.ObserveConferenceCallingEnabledUseCaseImpl
import com.wire.kalium.logic.feature.call.usecase.IsCallRunningUseCase
import com.wire.kalium.logic.feature.call.usecase.IsEligibleToStartCallUseCase
import com.wire.kalium.logic.feature.call.usecase.IsEligibleToStartCallUseCaseImpl
Expand All @@ -53,12 +52,16 @@ import com.wire.kalium.logic.feature.call.usecase.IsLastCallClosedUseCaseImpl
import com.wire.kalium.logic.feature.call.usecase.MuteCallUseCase
import com.wire.kalium.logic.feature.call.usecase.MuteCallUseCaseImpl
import com.wire.kalium.logic.feature.call.usecase.ObserveAskCallFeedbackUseCase
import com.wire.kalium.logic.feature.call.usecase.ObserveConferenceCallingEnabledUseCase
import com.wire.kalium.logic.feature.call.usecase.ObserveConferenceCallingEnabledUseCaseImpl
import com.wire.kalium.logic.feature.call.usecase.ObserveEndCallDueToConversationDegradationUseCase
import com.wire.kalium.logic.feature.call.usecase.ObserveEndCallDueToConversationDegradationUseCaseImpl
import com.wire.kalium.logic.feature.call.usecase.ObserveEstablishedCallWithSortedParticipantsUseCase
import com.wire.kalium.logic.feature.call.usecase.ObserveEstablishedCallWithSortedParticipantsUseCaseImpl
import com.wire.kalium.logic.feature.call.usecase.ObserveEstablishedCallsUseCase
import com.wire.kalium.logic.feature.call.usecase.ObserveEstablishedCallsUseCaseImpl
import com.wire.kalium.logic.feature.call.usecase.ObserveInCallReactionsUseCase
import com.wire.kalium.logic.feature.call.usecase.ObserveInCallReactionsUseCaseImpl
import com.wire.kalium.logic.feature.call.usecase.ObserveOngoingCallsUseCase
import com.wire.kalium.logic.feature.call.usecase.ObserveOngoingCallsUseCaseImpl
import com.wire.kalium.logic.feature.call.usecase.ObserveOutgoingCallUseCase
Expand Down Expand Up @@ -103,6 +106,7 @@ class CallsScope internal constructor(
private val conversationClientsInCallUpdater: ConversationClientsInCallUpdater,
private val getCallConversationType: GetCallConversationTypeProvider,
private val kaliumConfigs: KaliumConfigs,
private val inCallReactionsRepository: InCallReactionsRepository,
internal val dispatcher: KaliumDispatcher = KaliumDispatcherImpl
) {

Expand Down Expand Up @@ -239,4 +243,7 @@ class CallsScope internal constructor(
get() = ObserveRecentlyEndedCallMetadataUseCaseImpl(
callRepository = callRepository
)

val observeInCallReactions: ObserveInCallReactionsUseCase
get() = ObserveInCallReactionsUseCaseImpl(inCallReactionsRepository)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Wire
* Copyright (C) 2024 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
package com.wire.kalium.logic.feature.call.usecase

import com.wire.kalium.logic.data.call.InCallReactionMessage
import com.wire.kalium.logic.data.call.InCallReactionsRepository
import com.wire.kalium.logic.data.id.ConversationId
import kotlinx.coroutines.flow.Flow

/**
* Observe incoming in-call reactions
*/
interface ObserveInCallReactionsUseCase {
operator fun invoke(conversationId: ConversationId): Flow<InCallReactionMessage>
}

internal class ObserveInCallReactionsUseCaseImpl(
private val inCallReactionsRepository: InCallReactionsRepository,
) : ObserveInCallReactionsUseCase {

override fun invoke(conversationId: ConversationId): Flow<InCallReactionMessage> {
return inCallReactionsRepository.observeInCallReactions(conversationId)
}
}
Loading
Loading