Skip to content

Commit

Permalink
Implement Entry Widget for ongoing engagement cases
Browse files Browse the repository at this point in the history
MOB-3811
  • Loading branch information
andrews-moc committed Dec 13, 2024
1 parent c55758d commit 1f4342f
Show file tree
Hide file tree
Showing 16 changed files with 330 additions and 33 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.glia.widgets.core.secureconversations.domain

import com.glia.widgets.chat.domain.IsAuthenticatedUseCase
import com.glia.widgets.core.secureconversations.SecureConversationsRepository
import com.glia.widgets.engagement.State
import com.glia.widgets.engagement.domain.EngagementStateUseCase
Expand All @@ -9,6 +10,7 @@ import io.reactivex.rxjava3.core.Flowable

internal class HasOngoingSecureConversationUseCase(
private val secureConversationsRepository: SecureConversationsRepository,
private val isAuthenticatedUseCase: IsAuthenticatedUseCase,
private val engagementStateUseCase: EngagementStateUseCase
) {
/**
Expand All @@ -20,7 +22,8 @@ internal class HasOngoingSecureConversationUseCase(
secureConversationsRepository.unreadMessagesCountObservable,
engagementStateUseCase()
) { pendingSecureConversations, unreadMessagesCount, state ->
pendingSecureConversations || unreadMessagesCount > 0 || state is State.TransferredToSecureConversation
isAuthenticatedUseCase() &&
(pendingSecureConversations || unreadMessagesCount > 0 || state is State.TransferredToSecureConversation)
}

operator fun invoke(): Flowable<Boolean> = hasOngoingInteraction.distinctUntilChanged().observeOn(AndroidSchedulers.mainThread())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -391,8 +391,11 @@ public EntryWidgetContract.Controller getEntryWidgetController() {
useCaseFactory.createIsAuthenticatedUseCase(),
repositoryFactory.getSecureConversationsRepository(),
useCaseFactory.getHasPendingSecureConversationsWithTimeoutUseCase(),
useCaseFactory.getEngagementStateUseCase(),
useCaseFactory.getEngagementTypeUseCase(),
core,
Dependencies.getEngagementLauncher()
Dependencies.getEngagementLauncher(),
Dependencies.getActivityLauncher()
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ internal object Dependencies {
@JvmStatic
val configurationManager: ConfigurationManager by lazy { ConfigurationManagerImpl() }

@JvmStatic
val activityLauncher: ActivityLauncher by lazy {
ActivityLauncherImpl(IntentHelperImpl(), repositoryFactory.engagementRepository)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -899,6 +899,7 @@ public EngagementTypeUseCase getEngagementTypeUseCase() {
return new EngagementTypeUseCaseImpl(
getIsQueueingOrEngagementUseCase(),
getIsCurrentEngagementCallVisualizer(),
getScreenSharingUseCase(),
getOperatorMediaUseCase(),
getVisitorMediaUseCase(),
getIsOperatorPresentUseCase()
Expand Down Expand Up @@ -1081,6 +1082,7 @@ public FlipCameraButtonStateUseCase getFlipCameraButtonStateUseCase() {
public HasOngoingSecureConversationUseCase getHasPendingSecureConversationsWithTimeoutUseCase() {
return new HasOngoingSecureConversationUseCase(
repositoryFactory.getSecureConversationsRepository(),
createIsAuthenticatedUseCase(),
getEngagementStateUseCase()
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,48 @@
package com.glia.widgets.engagement.domain

import com.glia.androidsdk.Engagement.MediaType
import com.glia.widgets.helper.hasAudio
import com.glia.widgets.helper.hasVideo
import io.reactivex.rxjava3.core.Flowable

internal interface EngagementTypeUseCase {
val isAudioEngagement: Boolean
val isVideoEngagement: Boolean
val isMediaEngagement: Boolean
val isChatEngagement: Boolean
val isCallVisualizer: Boolean
val isCallVisualizerScreenSharing: Boolean

operator fun invoke(): Flowable<MediaType>
}

internal class EngagementTypeUseCaseImpl(
private val isQueueingOrLiveEngagementUseCase: IsQueueingOrLiveEngagementUseCase,
private val isCurrentEngagementCallVisualizerUseCase: IsCurrentEngagementCallVisualizerUseCase,
private val screenSharingUseCase: ScreenSharingUseCase,
private val operatorMediaUseCase: OperatorMediaUseCase,
private val visitorMediaUseCase: VisitorMediaUseCase,
private val isOperatorPresentUseCase: IsOperatorPresentUseCase
private val isOperatorPresentUseCase: IsOperatorPresentUseCase,
) : EngagementTypeUseCase {
private val hasOngoingEngagement get() = isQueueingOrLiveEngagementUseCase.hasOngoingLiveEngagement
private val hasAudio: Boolean get() = visitorMediaUseCase.hasAudio || operatorMediaUseCase.hasAudio
private val hasVideo: Boolean get() = visitorMediaUseCase.hasVideo || operatorMediaUseCase.hasVideo
override val isAudioEngagement: Boolean get() = hasOngoingEngagement && isOperatorPresentUseCase() && hasAudio
override val isVideoEngagement: Boolean get() = hasOngoingEngagement && isOperatorPresentUseCase() && hasVideo
private val hasAnyMedia: Boolean get() = visitorMediaUseCase.hasMedia || operatorMediaUseCase.hasMedia
override val isMediaEngagement: Boolean get() = hasOngoingEngagement && isOperatorPresentUseCase() && hasAnyMedia
override val isChatEngagement: Boolean get() = hasOngoingEngagement && !isCurrentEngagementCallVisualizerUseCase() && isOperatorPresentUseCase() && !hasAnyMedia
override val isCallVisualizerScreenSharing: Boolean get() = isCurrentEngagementCallVisualizerUseCase() && !hasAnyMedia
override val isCallVisualizer: Boolean get() = isCurrentEngagementCallVisualizerUseCase()
override val isCallVisualizerScreenSharing: Boolean get() = isCurrentEngagementCallVisualizerUseCase() && screenSharingUseCase.isSharing
private val operatorMediaObservable by lazy { operatorMediaUseCase() }

override fun invoke(): Flowable<MediaType> {
return operatorMediaObservable.map { operatorMediaState ->
when {
operatorMediaState.hasVideo -> MediaType.VIDEO
operatorMediaState.hasAudio -> MediaType.AUDIO
else -> MediaType.UNKNOWN
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@ package com.glia.widgets.engagement.domain
import com.glia.androidsdk.comms.MediaState
import com.glia.widgets.engagement.EngagementRepository
import com.glia.widgets.helper.Data
import com.glia.widgets.helper.hasAudio
import com.glia.widgets.helper.hasMedia
import com.glia.widgets.helper.hasVideo
import io.reactivex.rxjava3.core.Flowable

internal interface OperatorMediaUseCase {
val hasAudio: Boolean
val hasVideo: Boolean
val hasMedia: Boolean
operator fun invoke(): Flowable<MediaState>
}
Expand All @@ -16,6 +20,8 @@ internal class OperatorMediaUseCaseImpl(private val engagementRepository: Engage
.filter(Data<MediaState>::hasValue)
.map { it as Data.Value }
.map(Data.Value<MediaState>::result)
override val hasAudio: Boolean get() = engagementRepository.operatorCurrentMediaState?.hasAudio ?: false
override val hasVideo: Boolean get() = engagementRepository.operatorCurrentMediaState?.hasVideo ?: false

override val hasMedia: Boolean get() = engagementRepository.operatorCurrentMediaState?.hasMedia ?: false
override fun invoke(): Flowable<MediaState> = operatorMediaState
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@ package com.glia.widgets.engagement.domain
import com.glia.androidsdk.comms.MediaState
import com.glia.widgets.engagement.EngagementRepository
import com.glia.widgets.helper.Data
import com.glia.widgets.helper.hasAudio
import com.glia.widgets.helper.hasMedia
import com.glia.widgets.helper.hasVideo
import io.reactivex.rxjava3.core.Flowable

internal interface VisitorMediaUseCase {
val hasAudio: Boolean
val hasVideo: Boolean
val hasMedia: Boolean
val onHoldState: Flowable<Boolean>
operator fun invoke(): Flowable<MediaState>
Expand All @@ -19,6 +23,8 @@ internal class VisitorMediaUseCaseImpl(private val engagementRepository: Engagem
.map(Data.Value<MediaState>::result)

override val onHoldState: Flowable<Boolean> = engagementRepository.onHoldState
override val hasAudio: Boolean get() = engagementRepository.visitorCurrentMediaState?.hasAudio ?: false
override val hasVideo: Boolean get() = engagementRepository.visitorCurrentMediaState?.hasVideo ?: false

override val hasMedia: Boolean get() = engagementRepository.visitorCurrentMediaState?.hasMedia ?: false

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,14 @@ internal interface EntryWidgetContract {

sealed class ItemType(private val order: Int = HIGHEST_ORDER) : Comparable<ItemType> {
data object VideoCall : ItemType(order = 0)
data object AudioCall : ItemType(order = 1)
data object Chat : ItemType(order = 2)
data class Messaging(val value: Int) : ItemType(order = 3)
data object VideoCallOngoing : ItemType(order = 1)
data object AudioCall : ItemType(order = 2)
data object AudioCallOngoing : ItemType(order = 3)
data object Chat : ItemType(order = 4)
data object ChatOngoing : ItemType(order = 5)
data class Messaging(val value: Int) : ItemType(order = 6)
data class MessagingOngoing(val value: Int) : ItemType(order = 7)
data object CallVisualizerOngoing : ItemType(order = 8)
data object LoadingState : ItemType()
data object EmptyState : ItemType()
data object SdkNotInitializedState : ItemType()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
package com.glia.widgets.entrywidget

import android.app.Activity
import com.glia.androidsdk.Engagement
import com.glia.androidsdk.Engagement.MediaType
import com.glia.androidsdk.queuing.Queue
import com.glia.widgets.chat.Intention
import com.glia.widgets.chat.domain.IsAuthenticatedUseCase
import com.glia.widgets.core.queue.QueueRepository
import com.glia.widgets.core.queue.QueuesState
import com.glia.widgets.core.secureconversations.SecureConversationsRepository
import com.glia.widgets.core.secureconversations.domain.HasOngoingSecureConversationUseCase
import com.glia.widgets.di.GliaCore
import com.glia.widgets.engagement.State
import com.glia.widgets.engagement.domain.EngagementStateUseCase
import com.glia.widgets.engagement.domain.EngagementTypeUseCase
import com.glia.widgets.helper.Logger
import com.glia.widgets.helper.TAG
import com.glia.widgets.helper.mediaTypes
import com.glia.widgets.launcher.ActivityLauncher
import com.glia.widgets.launcher.EngagementLauncher
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Flowable
Expand All @@ -22,13 +27,18 @@ internal class EntryWidgetController @JvmOverloads constructor(
private val isAuthenticatedUseCase: IsAuthenticatedUseCase,
private val secureConversationsRepository: SecureConversationsRepository,
private val hasOngoingSecureConversationUseCase: HasOngoingSecureConversationUseCase,
private val engagementStateUseCase: EngagementStateUseCase,
private val engagementTypeUseCase: EngagementTypeUseCase,
private val core: GliaCore,
private val engagementLauncher: EngagementLauncher,
private val activityLauncher: ActivityLauncher,
private val compositeDisposable: CompositeDisposable = CompositeDisposable()
) : EntryWidgetContract.Controller {
private val queueStateObservable by lazy { queueRepository.queuesState }
private val unreadMessagesCountObservable by lazy { secureConversationsRepository.unreadMessagesCountObservable }
private val hasOngoingSCObservable by lazy { hasOngoingSecureConversationUseCase() }
private val engagementStateObservable by lazy { engagementStateUseCase() }
private val mediaTypeObservable by lazy { engagementTypeUseCase() }

private val loadingState: List<EntryWidgetContract.ItemType> by lazy {
listOf(
Expand All @@ -51,7 +61,14 @@ internal class EntryWidgetController @JvmOverloads constructor(
get() = queueStateObservable.map(::mapMessagingQueueState)

private val entryWidgetObservableItemType: Flowable<List<EntryWidgetContract.ItemType>>
get() = Flowable.combineLatest(queueStateObservable, unreadMessagesCountObservable, hasOngoingSCObservable, ::mapEntryWidgetQueueState)
get() = Flowable.combineLatest(
engagementStateObservable,
mediaTypeObservable.startWithItem(MediaType.UNKNOWN),
queueStateObservable,
unreadMessagesCountObservable,
hasOngoingSCObservable,
::mapToEntryWidgetItems
)

private lateinit var view: EntryWidgetContract.View

Expand All @@ -68,6 +85,38 @@ internal class EntryWidgetController @JvmOverloads constructor(
.let(compositeDisposable::add)
}

private fun mapToEntryWidgetItems(
engagementState: State,
mediaType: MediaType,
queuesState: QueuesState,
unreadMessagesCount: Int,
hasOngoingSC: Boolean,
): List<EntryWidgetContract.ItemType> {
val items = when (engagementState) {
is State.Update -> prepareItemsBasedOnOngoingEngagement(mediaType, unreadMessagesCount, hasOngoingSC).toMutableList()
is State.FinishedOmniCore, State.FinishedCallVisualizer -> prepareItemsBasedOnQueues(queuesState, unreadMessagesCount, hasOngoingSC).toMutableList()
else -> prepareItemsBasedOnQueues(queuesState, unreadMessagesCount, hasOngoingSC).toMutableList()
}
if (!view.whiteLabel) {
items.add(EntryWidgetContract.ItemType.PoweredBy)
}
return items.apply { sort() }
}

private fun prepareItemsBasedOnOngoingEngagement(
mediaType: MediaType,
unreadMessagesCount: Int,
hasOngoingSC: Boolean
): List<EntryWidgetContract.ItemType> {
return when {
engagementTypeUseCase.isCallVisualizer -> listOf(EntryWidgetContract.ItemType.CallVisualizerOngoing)
hasOngoingSC -> listOf(EntryWidgetContract.ItemType.MessagingOngoing(unreadMessagesCount))
mediaType == MediaType.VIDEO -> listOf(EntryWidgetContract.ItemType.VideoCallOngoing)
mediaType == MediaType.AUDIO -> listOf(EntryWidgetContract.ItemType.AudioCallOngoing)
else -> listOf(EntryWidgetContract.ItemType.ChatOngoing)
}
}

private fun itemsObservableBasedOnType(type: EntryWidgetContract.ViewType) = when (type) {
EntryWidgetContract.ViewType.MESSAGING_LIVE_SUPPORT -> messagingChatObservableItemType
else -> entryWidgetObservableItemType
Expand All @@ -82,7 +131,7 @@ internal class EntryWidgetController @JvmOverloads constructor(
view.showItems(items)
}

private fun mapEntryWidgetQueueState(
private fun prepareItemsBasedOnQueues(
queuesState: QueuesState,
unreadMessagesCount: Int,
hasOngoingSC: Boolean
Expand All @@ -94,34 +143,28 @@ internal class EntryWidgetController @JvmOverloads constructor(
* Sometimes(when we authenticate with opened Entry Widget embedded view),
* we receive updates for ongoing secure conversations before the authentication result is saved, causing this check to return false. */
if (hasOngoingSC && isAuthenticatedUseCase())
listOf(EntryWidgetContract.ItemType.Messaging(unreadMessagesCount))
listOf(EntryWidgetContract.ItemType.MessagingOngoing(unreadMessagesCount))
else
default
}

val items = when (queuesState) {
return when (queuesState) {
QueuesState.Empty -> messagingOrDefault(emptyState)
QueuesState.Loading -> messagingOrDefault(loadingState)
is QueuesState.Error -> messagingOrDefault(errorState)
is QueuesState.Queues -> mapEntryWidgetQueues(queuesState.queues, unreadMessagesCount, hasOngoingSC)
}.toMutableList()

if (!view.whiteLabel) {
items.add(EntryWidgetContract.ItemType.PoweredBy)
}

return items.apply { sort() }
}

private fun mapEntryWidgetQueues(queues: List<Queue>, unreadMessagesCount: Int, hasOngoingSC: Boolean): List<EntryWidgetContract.ItemType> {
val messaging = EntryWidgetContract.ItemType.Messaging(unreadMessagesCount)

val items = queues.mediaTypes.mapNotNull {
when {
it == Engagement.MediaType.VIDEO -> EntryWidgetContract.ItemType.VideoCall
it == Engagement.MediaType.AUDIO -> EntryWidgetContract.ItemType.AudioCall
it == Engagement.MediaType.TEXT -> EntryWidgetContract.ItemType.Chat
it == Engagement.MediaType.MESSAGING && isAuthenticatedUseCase() -> messaging
it == MediaType.VIDEO -> EntryWidgetContract.ItemType.VideoCall
it == MediaType.AUDIO -> EntryWidgetContract.ItemType.AudioCall
it == MediaType.TEXT -> EntryWidgetContract.ItemType.Chat
it == MediaType.MESSAGING && isAuthenticatedUseCase() -> messaging

else -> null
}
Expand All @@ -141,9 +184,9 @@ internal class EntryWidgetController @JvmOverloads constructor(
is QueuesState.Error -> errorState
is QueuesState.Queues -> queuesState.queues.mediaTypes.mapNotNull { mediaType ->
when (mediaType) {
Engagement.MediaType.VIDEO -> EntryWidgetContract.ItemType.VideoCall
Engagement.MediaType.AUDIO -> EntryWidgetContract.ItemType.AudioCall
Engagement.MediaType.TEXT -> EntryWidgetContract.ItemType.Chat
MediaType.VIDEO -> EntryWidgetContract.ItemType.VideoCall
MediaType.AUDIO -> EntryWidgetContract.ItemType.AudioCall
MediaType.TEXT -> EntryWidgetContract.ItemType.Chat
else -> null
}
}.takeIf { it.isNotEmpty() }?.sorted() ?: emptyState
Expand All @@ -153,10 +196,20 @@ internal class EntryWidgetController @JvmOverloads constructor(
Logger.d(TAG, "Item clicked: $itemType")

when (itemType) {
EntryWidgetContract.ItemType.Chat -> engagementLauncher.startChat(activity)
EntryWidgetContract.ItemType.AudioCall -> engagementLauncher.startAudioCall(activity)
EntryWidgetContract.ItemType.VideoCall -> engagementLauncher.startVideoCall(activity)
is EntryWidgetContract.ItemType.Messaging -> engagementLauncher.startSecureMessaging(activity)
EntryWidgetContract.ItemType.VideoCallOngoing -> activityLauncher.launchCall(activity, null, false)
EntryWidgetContract.ItemType.AudioCall -> engagementLauncher.startAudioCall(activity)
EntryWidgetContract.ItemType.AudioCallOngoing -> activityLauncher.launchCall(activity, null, false)
EntryWidgetContract.ItemType.Chat -> engagementLauncher.startChat(activity)
EntryWidgetContract.ItemType.ChatOngoing -> activityLauncher.launchChat(activity, Intention.RETURN_TO_CHAT)
is EntryWidgetContract.ItemType.Messaging,
is EntryWidgetContract.ItemType.MessagingOngoing -> engagementLauncher.startSecureMessaging(activity)

is EntryWidgetContract.ItemType.CallVisualizerOngoing -> {
if (engagementTypeUseCase.isMediaEngagement) activityLauncher.launchCall(activity, null, false)
else if (engagementTypeUseCase.isCallVisualizerScreenSharing) activityLauncher.launchEndScreenSharing(activity)
}

EntryWidgetContract.ItemType.ErrorState -> onRetryButtonClicked()
else -> {}
}
Expand Down
Loading

0 comments on commit 1f4342f

Please sign in to comment.