Skip to content

Commit

Permalink
feat: send and receive in-call reactions [#WPB-14254]
Browse files Browse the repository at this point in the history
  • Loading branch information
sbakhtiarov committed Dec 19, 2024
1 parent bb9bd8f commit 06392c4
Show file tree
Hide file tree
Showing 19 changed files with 588 additions and 22 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* 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.QualifiedID

data class InCallReactionMessage(
val emojis: Set<String>,
val senderUserId: QualifiedID,
val senderUserName: 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,53 @@
/*
* 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.user.UserId
import com.wire.kalium.logic.data.user.UserRepository
import com.wire.kalium.logic.functional.onSuccess
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow

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

internal class InCallReactionsDataSource(
private val userRepository: UserRepository,
) : InCallReactionsRepository {

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

override suspend fun addInCallReaction(emojis: Set<String>, senderUserId: UserId) {
userRepository.userById(senderUserId).onSuccess { user ->
inCallReactionsFlow.emit(
InCallReactionMessage(emojis, senderUserId, user.name)
)
}
}

override fun observeInCallReactions(): Flow<InCallReactionMessage> = inCallReactionsFlow.asSharedFlow()

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(userRepository)

/**
* 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,38 @@
/*
* 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 kotlinx.coroutines.flow.Flow

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

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

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

0 comments on commit 06392c4

Please sign in to comment.