From d2e13ad8fcab376e1559b2d1fcd2b81d5949a860 Mon Sep 17 00:00:00 2001 From: Davit Dolmazyan Date: Fri, 10 Jan 2025 17:22:45 +0200 Subject: [PATCH] Implement EngagementLauncher business logic for ongoing live engagement --- build.gradle | 2 +- .../CallVisualizerActivityWatcher.kt | 25 +- .../controller/CallVisualizerController.kt | 14 +- .../HasOngoingSecureConversationUseCase.kt | 15 +- .../java/com/glia/widgets/di/Dependencies.kt | 16 +- .../com/glia/widgets/di/UseCaseFactory.java | 1 - .../domain/EngagementTypeUseCase.kt | 3 +- .../glia/widgets/entrywidget/EntryWidget.kt | 21 +- .../widgets/launcher/EngagementLauncher.kt | 129 ++++-- .../com/glia/widgets/locale/LocaleProvider.kt | 2 +- .../widgets/view/snackbar/SnackBarDelegate.kt | 28 +- .../CallVisualizerControllerTest.kt | 10 +- ...HasOngoingSecureConversationUseCaseTest.kt | 76 ++-- .../launcher/EngagementLauncherTest.kt | 425 +++++++++++++++--- 14 files changed, 562 insertions(+), 205 deletions(-) diff --git a/build.gradle b/build.gradle index 7968740ad..77b55b061 100644 --- a/build.gradle +++ b/build.gradle @@ -26,7 +26,7 @@ allprojects { // Needed for some automated and manual testing (e.g: acceptance tests) mavenLocal() //TODO switch to the release version before releasing core SDK - maven { url = "https://s01.oss.sonatype.org/content/repositories/comglia-1339" } + maven { url = "https://s01.oss.sonatype.org/content/repositories/comglia-1340" } } } diff --git a/widgetssdk/src/main/java/com/glia/widgets/callvisualizer/CallVisualizerActivityWatcher.kt b/widgetssdk/src/main/java/com/glia/widgets/callvisualizer/CallVisualizerActivityWatcher.kt index a0c86e1c3..af1cb0814 100644 --- a/widgetssdk/src/main/java/com/glia/widgets/callvisualizer/CallVisualizerActivityWatcher.kt +++ b/widgetssdk/src/main/java/com/glia/widgets/callvisualizer/CallVisualizerActivityWatcher.kt @@ -1,6 +1,7 @@ package com.glia.widgets.callvisualizer import android.app.Activity +import androidx.annotation.StringRes import com.glia.widgets.R import com.glia.widgets.base.BaseSingleActivityWatcher import com.glia.widgets.callvisualizer.controller.CallVisualizerContract @@ -14,7 +15,6 @@ import com.glia.widgets.launcher.ActivityLauncher import com.glia.widgets.locale.LocaleProvider import com.glia.widgets.locale.LocaleString import com.glia.widgets.view.Dialogs -import com.glia.widgets.view.snackbar.SnackBarDelegate import com.glia.widgets.view.snackbar.SnackBarDelegateFactory import com.glia.widgets.view.unifiedui.theme.UnifiedThemeManager import com.glia.widgets.webbrowser.WebBrowserActivity @@ -45,7 +45,8 @@ internal class CallVisualizerActivityWatcher( activity == null || activity.isFinishing -> Logger.d(TAG, "skipping.. activity is null or finishing") activity is WebBrowserActivity && state is ControllerState.DisplayConfirmationDialog -> Logger.d(TAG, "skipping.. WebBrowser is open") activity is WebBrowserActivity && state is ControllerState.OpenWebBrowserScreen -> event.consume { controller.onWebBrowserOpened() } - state is ControllerState.ShowTimeoutSnackBar -> event.consume { showSnackBar(activity) } + state is ControllerState.ShowTimeoutSnackBar -> event.consume { showTimedOutSnackBar(activity) } + state is ControllerState.ShowAlreadyInCallSnackBar -> event.consume { showAlreadyInCallSnackBar(activity) } //Ensure this state remains unconsumed until the opening of the WebBrowserActivity. state is ControllerState.OpenWebBrowserScreen -> openWebBrowser(activity, state.title, state.url) state is ControllerState.DisplayVisitorCodeDialog -> displayVisitorCodeDialog(activity) @@ -94,15 +95,15 @@ internal class CallVisualizerActivityWatcher( activityLauncher.launchWebBrowser(activity, title, url) } - private fun showSnackBar(activity: Activity) { - makeSnackBar(activity).show() - } + private fun showTimedOutSnackBar(activity: Activity) = showSnackBar(activity, R.string.engagement_incoming_request_timed_out_message) + + private fun showAlreadyInCallSnackBar(activity: Activity) = showSnackBar(activity, R.string.entry_widget_call_visualizer_description) + + private fun showSnackBar(activity: Activity, @StringRes messageRes: Int) = SnackBarDelegateFactory( + activity, + messageRes, + localeProvider, + themeManager.theme + ).createDelegate().show() - private fun makeSnackBar(activity: Activity): SnackBarDelegate = - SnackBarDelegateFactory( - activity, - R.string.engagement_incoming_request_timed_out_message, - localeProvider, - themeManager.theme - ).createDelegate() } diff --git a/widgetssdk/src/main/java/com/glia/widgets/callvisualizer/controller/CallVisualizerController.kt b/widgetssdk/src/main/java/com/glia/widgets/callvisualizer/controller/CallVisualizerController.kt index f22784592..bb48bee08 100644 --- a/widgetssdk/src/main/java/com/glia/widgets/callvisualizer/controller/CallVisualizerController.kt +++ b/widgetssdk/src/main/java/com/glia/widgets/callvisualizer/controller/CallVisualizerController.kt @@ -23,11 +23,12 @@ import com.glia.widgets.engagement.State as EngagementState internal interface CallVisualizerContract { sealed interface State { - object DisplayVisitorCodeDialog : State + data object DisplayVisitorCodeDialog : State data class DisplayConfirmationDialog(val links: ConfirmationDialogLinks) : State - object DismissDialog : State - object ShowTimeoutSnackBar : State - object CloseHolderActivity : State + data object DismissDialog : State + data object ShowTimeoutSnackBar : State + data object ShowAlreadyInCallSnackBar : State + data object CloseHolderActivity : State data class OpenWebBrowserScreen(val title: LocaleString, val url: String) : State } @@ -43,6 +44,7 @@ internal interface CallVisualizerContract { fun onLinkClicked(link: Link) fun dismissVisitorCodeDialog() fun onWebBrowserOpened() + fun showAlreadyInCallSnackBar() } } @@ -142,6 +144,10 @@ internal class CallVisualizerController( dialogController.showCVEngagementConfirmationDialog() } + override fun showAlreadyInCallSnackBar() { + _state.onNext(CallVisualizerContract.State.ShowAlreadyInCallSnackBar) + } + private fun closeHolderActivity() { _state.onNext(CallVisualizerContract.State.CloseHolderActivity) } diff --git a/widgetssdk/src/main/java/com/glia/widgets/core/secureconversations/domain/HasOngoingSecureConversationUseCase.kt b/widgetssdk/src/main/java/com/glia/widgets/core/secureconversations/domain/HasOngoingSecureConversationUseCase.kt index 308a94cc8..bfbff6748 100644 --- a/widgetssdk/src/main/java/com/glia/widgets/core/secureconversations/domain/HasOngoingSecureConversationUseCase.kt +++ b/widgetssdk/src/main/java/com/glia/widgets/core/secureconversations/domain/HasOngoingSecureConversationUseCase.kt @@ -1,6 +1,5 @@ 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 @@ -10,7 +9,6 @@ import io.reactivex.rxjava3.core.Flowable internal class HasOngoingSecureConversationUseCase( private val secureConversationsRepository: SecureConversationsRepository, - private val isAuthenticatedUseCase: IsAuthenticatedUseCase, private val engagementStateUseCase: EngagementStateUseCase ) { /** @@ -22,12 +20,19 @@ internal class HasOngoingSecureConversationUseCase( secureConversationsRepository.unreadMessagesCountObservable, engagementStateUseCase() ) { pendingSecureConversations, unreadMessagesCount, state -> - isAuthenticatedUseCase() && - (pendingSecureConversations || unreadMessagesCount > 0 || state is State.TransferredToSecureConversation) + pendingSecureConversations || unreadMessagesCount > 0 || state is State.TransferredToSecureConversation } operator fun invoke(): Flowable = hasOngoingInteraction.distinctUntilChanged().observeOn(AndroidSchedulers.mainThread()) - operator fun invoke(callback: (Boolean) -> Unit) = invoke().firstOrError().unSafeSubscribe(callback) + operator fun invoke(onHasOngoingSecureConversation: () -> Unit, onNoOngoingSecureConversation: () -> Unit) { + invoke().firstOrError().unSafeSubscribe { hasOngoing -> + if (hasOngoing) { + onHasOngoingSecureConversation() + } else { + onNoOngoingSecureConversation() + } + } + } } diff --git a/widgetssdk/src/main/java/com/glia/widgets/di/Dependencies.kt b/widgetssdk/src/main/java/com/glia/widgets/di/Dependencies.kt index b365524ac..f89b404cc 100644 --- a/widgetssdk/src/main/java/com/glia/widgets/di/Dependencies.kt +++ b/widgetssdk/src/main/java/com/glia/widgets/di/Dependencies.kt @@ -84,12 +84,16 @@ internal object Dependencies { @JvmStatic val engagementLauncher: EngagementLauncher by lazy { EngagementLauncherImpl( - activityLauncher, - useCaseFactory.hasPendingSecureConversationsWithTimeoutUseCase, - useCaseFactory.isQueueingOrEngagementUseCase, - useCaseFactory.endEngagementUseCase, - configurationManager, - controllerFactory) + activityLauncher = activityLauncher, + hasOngoingSecureConversationUseCase = useCaseFactory.hasPendingSecureConversationsWithTimeoutUseCase, + isQueueingOrLiveEngagementUseCase = useCaseFactory.isQueueingOrEngagementUseCase, + endEngagementUseCase = useCaseFactory.endEngagementUseCase, + configurationManager = configurationManager, + engagementTypeUseCase = useCaseFactory.engagementTypeUseCase, + callVisualizerController = controllerFactory.callVisualizerController, + destroyChatController = controllerFactory::destroyChatController, + destroyCallController = controllerFactory::destroyCallController + ) } @JvmStatic diff --git a/widgetssdk/src/main/java/com/glia/widgets/di/UseCaseFactory.java b/widgetssdk/src/main/java/com/glia/widgets/di/UseCaseFactory.java index c45db2dc6..df3d59a2e 100644 --- a/widgetssdk/src/main/java/com/glia/widgets/di/UseCaseFactory.java +++ b/widgetssdk/src/main/java/com/glia/widgets/di/UseCaseFactory.java @@ -1065,7 +1065,6 @@ public FlipCameraButtonStateUseCase getFlipCameraButtonStateUseCase() { public HasOngoingSecureConversationUseCase getHasPendingSecureConversationsWithTimeoutUseCase() { return new HasOngoingSecureConversationUseCase( repositoryFactory.getSecureConversationsRepository(), - createIsAuthenticatedUseCase(), getEngagementStateUseCase() ); } diff --git a/widgetssdk/src/main/java/com/glia/widgets/engagement/domain/EngagementTypeUseCase.kt b/widgetssdk/src/main/java/com/glia/widgets/engagement/domain/EngagementTypeUseCase.kt index 4fee50521..75103d363 100644 --- a/widgetssdk/src/main/java/com/glia/widgets/engagement/domain/EngagementTypeUseCase.kt +++ b/widgetssdk/src/main/java/com/glia/widgets/engagement/domain/EngagementTypeUseCase.kt @@ -12,6 +12,7 @@ internal interface EngagementTypeUseCase { val isChatEngagement: Boolean val isCallVisualizer: Boolean val isCallVisualizerScreenSharing: Boolean + val hasVideo: Boolean operator fun invoke(): Flowable } @@ -26,7 +27,7 @@ internal class EngagementTypeUseCaseImpl( ) : 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 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 diff --git a/widgetssdk/src/main/java/com/glia/widgets/entrywidget/EntryWidget.kt b/widgetssdk/src/main/java/com/glia/widgets/entrywidget/EntryWidget.kt index f85a3afa9..dd93ec76e 100644 --- a/widgetssdk/src/main/java/com/glia/widgets/entrywidget/EntryWidget.kt +++ b/widgetssdk/src/main/java/com/glia/widgets/entrywidget/EntryWidget.kt @@ -42,25 +42,16 @@ internal class EntryWidgetImpl( ) : EntryWidget { override fun show(activity: Activity) { - hasOngoingSecureConversationUseCase { - handleShowWithPendingSecureConversations(it, activity) - } - } - - private fun handleShowWithPendingSecureConversations(hasPendingSecureConversations: Boolean, activity: Activity) { - if (hasPendingSecureConversations) { - activityLauncher.launchChat(activity, Intention.SC_CHAT) - } else { - activityLauncher.launchEntryWidget(activity) - } + hasOngoingSecureConversationUseCase( + onHasOngoingSecureConversation = { activityLauncher.launchChat(activity, Intention.SC_CHAT) }, + onNoOngoingSecureConversation = { activityLauncher.launchEntryWidget(activity) } + ) } override fun getView(context: Context): View { val entryWidgetTheme = themeManager.theme?.entryWidgetTheme - val adapter = EntryWidgetAdapter( - EntryWidgetContract.ViewType.EMBEDDED_VIEW, - entryWidgetTheme - ) + val adapter = EntryWidgetAdapter(EntryWidgetContract.ViewType.EMBEDDED_VIEW, entryWidgetTheme) + //Wrapping with Glia theme to make the Theme available for embedded view return EntryWidgetView(context.wrapWithTheme()).apply { setAdapter(adapter) diff --git a/widgetssdk/src/main/java/com/glia/widgets/launcher/EngagementLauncher.kt b/widgetssdk/src/main/java/com/glia/widgets/launcher/EngagementLauncher.kt index 5f6372a2b..841405552 100644 --- a/widgetssdk/src/main/java/com/glia/widgets/launcher/EngagementLauncher.kt +++ b/widgetssdk/src/main/java/com/glia/widgets/launcher/EngagementLauncher.kt @@ -2,26 +2,55 @@ package com.glia.widgets.launcher import android.content.Context import com.glia.androidsdk.Engagement +import com.glia.widgets.callvisualizer.controller.CallVisualizerContract import com.glia.widgets.chat.Intention import com.glia.widgets.core.secureconversations.domain.HasOngoingSecureConversationUseCase -import com.glia.widgets.di.ControllerFactory import com.glia.widgets.engagement.domain.EndEngagementUseCase +import com.glia.widgets.engagement.domain.EngagementTypeUseCase import com.glia.widgets.engagement.domain.IsQueueingOrLiveEngagementUseCase import com.glia.widgets.helper.Logger import com.glia.widgets.helper.TAG +@RequiresOptIn( + level = RequiresOptIn.Level.ERROR, + message = "This feature is under consideration for removal in a future releases. Use with caution." +) +@Retention(AnnotationRetention.BINARY) +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY) +/** + * Marks the feature as under consideration for removal in a future releases. + */ +annotation class FeatureUnderConsiderationForRemoval + /** * An interface for launching different types of engagements, such as chat, * audio calls, video calls, and secure messaging */ +@OptIn(FeatureUnderConsiderationForRemoval::class) interface EngagementLauncher { + + /** + * Starts a chat engagement. + * + * @param context Activity or Context used to launch the chat screen + */ + fun startChat(context: Context) = startChat(context, null) + /** * Starts a chat engagement. * * @param context Activity or Context used to launch the chat screen * @param visitorContextAssetId Optional visitor context id from Glia Hub */ - fun startChat(context: Context, visitorContextAssetId: String? = null) + @FeatureUnderConsiderationForRemoval + fun startChat(context: Context, visitorContextAssetId: String?) + + /** + * Starts an audio engagement. + * + * @param context Activity or Context used to launch the call screen + */ + fun startAudioCall(context: Context) = startAudioCall(context, null) /** * Starts an audio engagement. @@ -29,7 +58,15 @@ interface EngagementLauncher { * @param context Activity or Context used to launch the call screen * @param visitorContextAssetId Optional visitor context id from Glia Hub */ - fun startAudioCall(context: Context, visitorContextAssetId: String? = null) + @FeatureUnderConsiderationForRemoval + fun startAudioCall(context: Context, visitorContextAssetId: String?) + + /** + * Starts a video engagement. + * + * @param context Activity or Context used to launch the call screen + */ + fun startVideoCall(context: Context) = startVideoCall(context, null) /** * Starts a video engagement. @@ -37,7 +74,15 @@ interface EngagementLauncher { * @param context Activity or Context used to launch the call screen * @param visitorContextAssetId Optional visitor context id from Glia Hub */ - fun startVideoCall(context: Context, visitorContextAssetId: String? = null) + @FeatureUnderConsiderationForRemoval + fun startVideoCall(context: Context, visitorContextAssetId: String?) + + /** + * Starts a secure messaging. + * + * @param context Activity or Context used to launch the secure messaging welcome or chat screen + */ + fun startSecureMessaging(context: Context) = startSecureMessaging(context, null) /** * Starts a secure messaging. @@ -45,7 +90,8 @@ interface EngagementLauncher { * @param context Activity or Context used to launch the secure messaging welcome or chat screen * @param visitorContextAssetId Optional visitor context id from Glia Hub */ - fun startSecureMessaging(context: Context, visitorContextAssetId: String? = null) + @FeatureUnderConsiderationForRemoval + fun startSecureMessaging(context: Context, visitorContextAssetId: String?) } internal class EngagementLauncherImpl( @@ -54,68 +100,85 @@ internal class EngagementLauncherImpl( private val isQueueingOrLiveEngagementUseCase: IsQueueingOrLiveEngagementUseCase, private val endEngagementUseCase: EndEngagementUseCase, private val configurationManager: ConfigurationManager, - private val controllerFactory: ControllerFactory + private val engagementTypeUseCase: EngagementTypeUseCase, + private val callVisualizerController: CallVisualizerContract.Controller, + private val destroyChatController: () -> Unit, + private val destroyCallController: () -> Unit ) : EngagementLauncher { + @FeatureUnderConsiderationForRemoval override fun startChat(context: Context, visitorContextAssetId: String?) { if (isQueueingOrLiveEngagementUseCase.isQueueing) { Logger.i(TAG, "Canceling ongoing queue ticket to create a new one") endEngagementUseCase() - controllerFactory.destroyChatController() + destroyChatController() } visitorContextAssetId?.let { configurationManager.setVisitorContextAssetId(it) } - hasOngoingSecureConversationUseCase { - if (it) { - activityLauncher.launchChat(context, Intention.SC_DIALOG_ENQUEUE_FOR_TEXT) - } else { - activityLauncher.launchChat(context, Intention.LIVE_CHAT) - } + + when { + engagementTypeUseCase.isCallVisualizer -> callVisualizerController.showAlreadyInCallSnackBar() + isQueueingOrLiveEngagementUseCase.hasOngoingLiveEngagement -> activityLauncher.launchChat(context, Intention.RETURN_TO_CHAT) + else -> hasOngoingSecureConversationUseCase( + onHasOngoingSecureConversation = { activityLauncher.launchChat(context, Intention.SC_DIALOG_ENQUEUE_FOR_TEXT) }, + onNoOngoingSecureConversation = { activityLauncher.launchChat(context, Intention.LIVE_CHAT) } + ) } } + @FeatureUnderConsiderationForRemoval override fun startAudioCall(context: Context, visitorContextAssetId: String?) { if (isQueueingOrLiveEngagementUseCase.isQueueing) { Logger.i(TAG, "Canceling ongoing queue ticket to create a new one") endEngagementUseCase() - controllerFactory.destroyCallController() + destroyCallController() } visitorContextAssetId?.let { configurationManager.setVisitorContextAssetId(it) } - hasOngoingSecureConversationUseCase { - if (it) { - activityLauncher.launchChat(context, Intention.SC_DIALOG_START_AUDIO) - } else { - activityLauncher.launchCall(context, Engagement.MediaType.AUDIO, false) - } + + when { + engagementTypeUseCase.isCallVisualizer -> callVisualizerController.showAlreadyInCallSnackBar() + engagementTypeUseCase.isMediaEngagement -> activityLauncher.launchCall(context, null, false) + isQueueingOrLiveEngagementUseCase.hasOngoingLiveEngagement -> activityLauncher.launchChat(context, Intention.RETURN_TO_CHAT) + else -> hasOngoingSecureConversationUseCase( + onHasOngoingSecureConversation = { activityLauncher.launchChat(context, Intention.SC_DIALOG_START_AUDIO) }, + onNoOngoingSecureConversation = { activityLauncher.launchCall(context, Engagement.MediaType.AUDIO, false) } + ) } } + @FeatureUnderConsiderationForRemoval override fun startVideoCall(context: Context, visitorContextAssetId: String?) { if (isQueueingOrLiveEngagementUseCase.isQueueing) { Logger.i(TAG, "Canceling ongoing queue ticket to create a new one") endEngagementUseCase() - controllerFactory.destroyCallController() + destroyCallController() } visitorContextAssetId?.let { configurationManager.setVisitorContextAssetId(it) } - hasOngoingSecureConversationUseCase { - if (it) { - activityLauncher.launchChat(context, Intention.SC_DIALOG_START_VIDEO) - } else { - activityLauncher.launchCall(context, Engagement.MediaType.VIDEO, false) - } + + when { + engagementTypeUseCase.isCallVisualizer && !engagementTypeUseCase.hasVideo -> callVisualizerController.showAlreadyInCallSnackBar() + engagementTypeUseCase.isMediaEngagement -> activityLauncher.launchCall(context, null, false) + isQueueingOrLiveEngagementUseCase.hasOngoingLiveEngagement -> activityLauncher.launchChat(context, Intention.RETURN_TO_CHAT) + else -> hasOngoingSecureConversationUseCase( + onHasOngoingSecureConversation = { activityLauncher.launchChat(context, Intention.SC_DIALOG_START_VIDEO) }, + onNoOngoingSecureConversation = { activityLauncher.launchCall(context, Engagement.MediaType.VIDEO, false) } + ) } } + @FeatureUnderConsiderationForRemoval override fun startSecureMessaging(context: Context, visitorContextAssetId: String?) { visitorContextAssetId?.let { configurationManager.setVisitorContextAssetId(it) } - hasOngoingSecureConversationUseCase { - if (it) { - activityLauncher.launchChat(context, Intention.SC_CHAT) - } else { - activityLauncher.launchSecureMessagingWelcomeScreen(context) - } + + when { + engagementTypeUseCase.isCallVisualizer -> callVisualizerController.showAlreadyInCallSnackBar() + isQueueingOrLiveEngagementUseCase.hasOngoingLiveEngagement -> activityLauncher.launchChat(context, Intention.RETURN_TO_CHAT) + else -> hasOngoingSecureConversationUseCase( + onHasOngoingSecureConversation = { activityLauncher.launchChat(context, Intention.SC_CHAT) }, + onNoOngoingSecureConversation = { activityLauncher.launchSecureMessagingWelcomeScreen(context) } + ) } } } diff --git a/widgetssdk/src/main/java/com/glia/widgets/locale/LocaleProvider.kt b/widgetssdk/src/main/java/com/glia/widgets/locale/LocaleProvider.kt index 04637a4b9..a6d3e5bad 100644 --- a/widgetssdk/src/main/java/com/glia/widgets/locale/LocaleProvider.kt +++ b/widgetssdk/src/main/java/com/glia/widgets/locale/LocaleProvider.kt @@ -34,7 +34,7 @@ data class LocaleString( } @OpenForTesting -internal open class LocaleProvider @JvmOverloads constructor( +internal open class LocaleProvider( private val resourceProvider: IResourceProvider ) : StringProvider { diff --git a/widgetssdk/src/main/java/com/glia/widgets/view/snackbar/SnackBarDelegate.kt b/widgetssdk/src/main/java/com/glia/widgets/view/snackbar/SnackBarDelegate.kt index 1344ae940..9f558ad0b 100644 --- a/widgetssdk/src/main/java/com/glia/widgets/view/snackbar/SnackBarDelegate.kt +++ b/widgetssdk/src/main/java/com/glia/widgets/view/snackbar/SnackBarDelegate.kt @@ -5,6 +5,7 @@ import android.view.View import android.view.ViewGroup.MarginLayoutParams import androidx.annotation.ColorRes import androidx.annotation.IdRes +import androidx.annotation.StringRes import androidx.annotation.VisibleForTesting import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat @@ -25,7 +26,7 @@ import com.google.android.material.snackbar.Snackbar internal abstract class SnackBarDelegate( view: View, - titleStringKey: Int, + @StringRes titleStringKey: Int, localeProvider: LocaleProvider, snackBarTheme: SnackBarTheme? ) { @@ -50,10 +51,10 @@ internal abstract class SnackBarDelegate( fun show() = snackBar.show() - private fun makeSnackBar(view: View, titleStringKey: Int, stringProvider: LocaleProvider, snackBarTheme: SnackBarTheme?): Snackbar { + private fun makeSnackBar(view: View, @StringRes titleStringKey: Int, stringProvider: LocaleProvider, snackBarTheme: SnackBarTheme?): Snackbar { val bgColor = snackBarTheme?.backgroundColorTheme?.primaryColor ?: view.getColorCompat(fallbackBackgroundColor) val textColor = snackBarTheme?.textColorTheme?.primaryColor ?: view.getColorCompat(fallbackTextColor) - val message = stringProvider.getRemoteString(titleStringKey) + val message = stringProvider.getString(titleStringKey) return Snackbar.make(view, message, Snackbar.LENGTH_SHORT) .setBackgroundTint(bgColor) @@ -71,7 +72,12 @@ internal abstract class SnackBarDelegate( } @VisibleForTesting -internal class CommonSnackBarDelegate(activity: Activity, titleStringKey: Int, localeProvider: LocaleProvider, unifiedTheme: UnifiedTheme?) : +internal class CommonSnackBarDelegate( + activity: Activity, + @StringRes titleStringKey: Int, + localeProvider: LocaleProvider, + unifiedTheme: UnifiedTheme? +) : SnackBarDelegate(activity.rootView, titleStringKey, localeProvider, unifiedTheme?.snackBarTheme) { override val marginBottom: Int = calculateBottomMargin(activity.rootView) @@ -86,13 +92,23 @@ internal class CommonSnackBarDelegate(activity: Activity, titleStringKey: Int, l } @VisibleForTesting -internal class ChatActivitySnackBarDelegate(activity: ChatActivity, titleStringKey: Int, localeProvider: LocaleProvider, unifiedTheme: UnifiedTheme?) : +internal class ChatActivitySnackBarDelegate( + activity: ChatActivity, + @StringRes titleStringKey: Int, + localeProvider: LocaleProvider, + unifiedTheme: UnifiedTheme? +) : SnackBarDelegate(activity.findViewById(R.id.chat_view), titleStringKey, localeProvider, unifiedTheme?.snackBarTheme) { override val anchorViewId: Int = R.id.message_input_background } @VisibleForTesting -internal class CallActivitySnackBarDelegate(activity: CallActivity, titleStringKey: Int, localeProvider: LocaleProvider, unifiedTheme: UnifiedTheme?) : +internal class CallActivitySnackBarDelegate( + activity: CallActivity, + @StringRes titleStringKey: Int, + localeProvider: LocaleProvider, + unifiedTheme: UnifiedTheme? +) : SnackBarDelegate(activity.findViewById(R.id.call_view), titleStringKey, localeProvider, unifiedTheme?.callTheme?.snackBar) { override val anchorViewId: Int = R.id.buttons_layout_bg override val fallbackBackgroundColor: Int = R.color.glia_light_color diff --git a/widgetssdk/src/test/java/com/glia/widgets/callvisualizer/controller/CallVisualizerControllerTest.kt b/widgetssdk/src/test/java/com/glia/widgets/callvisualizer/controller/CallVisualizerControllerTest.kt index 55ac13f13..0e26eb130 100644 --- a/widgetssdk/src/test/java/com/glia/widgets/callvisualizer/controller/CallVisualizerControllerTest.kt +++ b/widgetssdk/src/test/java/com/glia/widgets/callvisualizer/controller/CallVisualizerControllerTest.kt @@ -184,7 +184,6 @@ class CallVisualizerControllerTest { testState.assertNotComplete().assertValue { it.value is CallVisualizerContract.State.CloseHolderActivity } verify { engagementRequestUseCase.accept(any()) } verify { dialogController.dismissCurrentDialog() } - } @Test @@ -196,7 +195,6 @@ class CallVisualizerControllerTest { testState.assertNotComplete().assertValue { it.value is CallVisualizerContract.State.CloseHolderActivity } verify { engagementRequestUseCase.decline() } verify { dialogController.dismissCurrentDialog() } - } @Test @@ -207,7 +205,15 @@ class CallVisualizerControllerTest { testState.assertNotComplete().assertValue { it.value is CallVisualizerContract.State.CloseHolderActivity } verify { dialogController.dismissVisitorCodeDialog() } + } + + @Test + fun `showAlreadyInCallSnackBar will show snackBar`() { + val testState = controller.state.test() + + controller.showAlreadyInCallSnackBar() + testState.assertNotComplete().assertValue { it.value is CallVisualizerContract.State.ShowAlreadyInCallSnackBar } } @Test diff --git a/widgetssdk/src/test/java/com/glia/widgets/core/secureconversations/domain/HasOngoingSecureConversationUseCaseTest.kt b/widgetssdk/src/test/java/com/glia/widgets/core/secureconversations/domain/HasOngoingSecureConversationUseCaseTest.kt index 97d5bd487..f2c987806 100644 --- a/widgetssdk/src/test/java/com/glia/widgets/core/secureconversations/domain/HasOngoingSecureConversationUseCaseTest.kt +++ b/widgetssdk/src/test/java/com/glia/widgets/core/secureconversations/domain/HasOngoingSecureConversationUseCaseTest.kt @@ -1,6 +1,5 @@ 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 @@ -21,7 +20,6 @@ class HasOngoingSecureConversationUseCaseTest { private lateinit var repository: SecureConversationsRepository private lateinit var useCase: HasOngoingSecureConversationUseCase private lateinit var engagementStateUseCase: EngagementStateUseCase - private lateinit var isAuthenticatedUseCase: IsAuthenticatedUseCase @Before fun setUp() { @@ -29,8 +27,7 @@ class HasOngoingSecureConversationUseCaseTest { RxAndroidPlugins.setInitMainThreadSchedulerHandler { Schedulers.trampoline() } repository = mockk() engagementStateUseCase = mockk() - isAuthenticatedUseCase = mockk() - useCase = HasOngoingSecureConversationUseCase(repository, isAuthenticatedUseCase, engagementStateUseCase) + useCase = HasOngoingSecureConversationUseCase(repository, engagementStateUseCase) } @After @@ -39,49 +36,46 @@ class HasOngoingSecureConversationUseCaseTest { RxAndroidPlugins.reset() } - private fun mockInitialState( - pendingSC: Boolean = false, - unreadMessagesCount: Int = 0, - transferredSC: Boolean = false, - isAuthenticated: Boolean = false - ) { + private fun mockInitialState(pendingSC: Boolean = false, unreadMessagesCount: Int = 0, transferredSC: Boolean = false) { every { repository.pendingSecureConversationsStatusObservable } returns Flowable.just(pendingSC) every { repository.unreadMessagesCountObservable } returns Flowable.just(unreadMessagesCount) every { engagementStateUseCase() } returns Flowable.just(if (transferredSC) State.TransferredToSecureConversation else State.NoEngagement) - every { isAuthenticatedUseCase() } returns isAuthenticated } @Test fun `invoke returns true when there are pending secure conversations`() { - mockInitialState(pendingSC = true, isAuthenticated = true) + mockInitialState(pendingSC = true) val result = useCase().blockingLast() assertEquals(true, result) - val callback = mockk<(Boolean) -> Unit>() - useCase(callback) - verify(timeout = 1100) { callback.invoke(true) } + val onHasOngoingSecureConversation = mockk<() -> Unit>() + val onNoOngoingSecureConversation = mockk<() -> Unit>() + useCase(onHasOngoingSecureConversation, onNoOngoingSecureConversation) + verify(timeout = 1100) { onHasOngoingSecureConversation() } } @Test fun `invoke returns true when there are positive unread messages count`() { - mockInitialState(unreadMessagesCount = 3, isAuthenticated = true) + mockInitialState(unreadMessagesCount = 3) val result = useCase().blockingLast() assertEquals(true, result) - val callback = mockk<(Boolean) -> Unit>() - useCase(callback) - verify(timeout = 1100) { callback.invoke(true) } + val onHasOngoingSecureConversation = mockk<() -> Unit>() + val onNoOngoingSecureConversation = mockk<() -> Unit>() + useCase(onHasOngoingSecureConversation, onNoOngoingSecureConversation) + verify(timeout = 1100) { onHasOngoingSecureConversation() } } @Test fun `invoke returns true when there is transferred sc`() { - mockInitialState(transferredSC = true, isAuthenticated = true) + mockInitialState(transferredSC = true) val result = useCase().blockingLast() assertEquals(true, result) - val callback = mockk<(Boolean) -> Unit>() - useCase(callback) - verify(timeout = 1100) { callback.invoke(true) } + val onHasOngoingSecureConversation = mockk<() -> Unit>() + val onNoOngoingSecureConversation = mockk<() -> Unit>() + useCase(onHasOngoingSecureConversation, onNoOngoingSecureConversation) + verify(timeout = 1100) { onHasOngoingSecureConversation() } } @Test @@ -90,25 +84,10 @@ class HasOngoingSecureConversationUseCaseTest { val result = useCase().blockingLast() assertEquals(false, result) - val callback = mockk<(Boolean) -> Unit>() - useCase(callback) - verify(timeout = 1100) { callback.invoke(false) } - } - - @Test - fun `invoke returns false when only isAuthenticated false`() { - mockInitialState( - pendingSC = true, - unreadMessagesCount = 3, - transferredSC = true, - isAuthenticated = false - ) - - val result = useCase().blockingLast() - assertEquals(false, result) - val callback = mockk<(Boolean) -> Unit>() - useCase(callback) - verify(timeout = 1100) { callback.invoke(false) } + val onHasOngoingSecureConversation = mockk<() -> Unit>() + val onNoOngoingSecureConversation = mockk<() -> Unit>() + useCase(onHasOngoingSecureConversation, onNoOngoingSecureConversation) + verify(timeout = 1100) { onNoOngoingSecureConversation() } } @Test @@ -118,18 +97,17 @@ class HasOngoingSecureConversationUseCaseTest { every { repository.pendingSecureConversationsStatusObservable } returns pendingSC every { repository.unreadMessagesCountObservable } returns Flowable.just(0) every { engagementStateUseCase() } returns Flowable.just(State.NoEngagement) - every { isAuthenticatedUseCase() } returns false useCase().test().assertValue(false) - val callback = mockk<(Boolean) -> Unit>() - useCase(callback) - verify(timeout = 1100) { callback.invoke(false) } + val onHasOngoingSecureConversation = mockk<() -> Unit>() + val onNoOngoingSecureConversation = mockk<() -> Unit>() + useCase(onHasOngoingSecureConversation, onNoOngoingSecureConversation) + verify(timeout = 1100) { onNoOngoingSecureConversation() } pendingSC.onNext(true) - every { isAuthenticatedUseCase() } returns true useCase().test().assertValue(true) - verify(timeout = 1100, exactly = 1) { callback.invoke(false) } - verify(timeout = 1100, inverse = true) { callback.invoke(true) } + verify(timeout = 1100, exactly = 1) { onNoOngoingSecureConversation() } + verify(timeout = 1100, inverse = true) { onHasOngoingSecureConversation() } } } diff --git a/widgetssdk/src/test/java/com/glia/widgets/launcher/EngagementLauncherTest.kt b/widgetssdk/src/test/java/com/glia/widgets/launcher/EngagementLauncherTest.kt index 2030aec91..f15236e5e 100644 --- a/widgetssdk/src/test/java/com/glia/widgets/launcher/EngagementLauncherTest.kt +++ b/widgetssdk/src/test/java/com/glia/widgets/launcher/EngagementLauncherTest.kt @@ -2,18 +2,24 @@ package com.glia.widgets.launcher import android.app.Activity import com.glia.androidsdk.Engagement +import com.glia.widgets.callvisualizer.controller.CallVisualizerContract import com.glia.widgets.chat.Intention import com.glia.widgets.core.secureconversations.domain.HasOngoingSecureConversationUseCase -import com.glia.widgets.di.ControllerFactory import com.glia.widgets.engagement.domain.EndEngagementUseCase +import com.glia.widgets.engagement.domain.EngagementTypeUseCase import com.glia.widgets.engagement.domain.IsQueueingOrLiveEngagementUseCase +import com.glia.widgets.helper.Logger import io.mockk.MockKAnnotations +import io.mockk.confirmVerified import io.mockk.every import io.mockk.impl.annotations.MockK +import io.mockk.mockk import io.mockk.verify +import org.junit.After import org.junit.Before import org.junit.Test +@OptIn(FeatureUnderConsiderationForRemoval::class) class EngagementLauncherImplTest { @MockK(relaxUnitFun = true) @@ -29,7 +35,10 @@ class EngagementLauncherImplTest { private lateinit var endEngagementUseCase: EndEngagementUseCase @MockK(relaxUnitFun = true) - private lateinit var controllerFactory: ControllerFactory + private lateinit var engagementTypeUseCase: EngagementTypeUseCase + + @MockK(relaxUnitFun = true) + private lateinit var callVisualizerController: CallVisualizerContract.Controller @MockK private lateinit var activity: Activity @@ -37,6 +46,9 @@ class EngagementLauncherImplTest { @MockK(relaxUnitFun = true) private lateinit var configurationManager: ConfigurationManager + private lateinit var destroyChatControllerCallback: () -> Unit + private lateinit var destroyCallControllerCallback: () -> Unit + private lateinit var engagementLauncher: EngagementLauncherImpl private val visitorContextAssetId = "visitor_context_asset_id" @@ -45,143 +57,418 @@ class EngagementLauncherImplTest { @Before fun setUp() { MockKAnnotations.init(this) + destroyChatControllerCallback = mockk(relaxed = true) + destroyCallControllerCallback = mockk(relaxed = true) engagementLauncher = EngagementLauncherImpl( activityLauncher, hasOngoingSecureConversationUseCase, isQueueingOrLiveEngagementUseCase, endEngagementUseCase, configurationManager, - controllerFactory + engagementTypeUseCase, + callVisualizerController, + destroyChatControllerCallback, + destroyCallControllerCallback + ) + Logger.setIsDebug(false) + } + + @After + fun tearDown() { + confirmVerified( + activityLauncher, + hasOngoingSecureConversationUseCase, + isQueueingOrLiveEngagementUseCase, + endEngagementUseCase, + engagementTypeUseCase, + callVisualizerController, + configurationManager ) } + //Start Chat + + @Test + fun `startChat shows already in call snackBar when CV is ongoing`() { + mockConditions(isQueueing = true, isCallVisualizer = true, hasOngoingLiveEngagement = true) + + engagementLauncher.startChat(activity, visitorContextAssetId) + + verify { isQueueingOrLiveEngagementUseCase.isQueueing } + + verify { endEngagementUseCase() } + verify { destroyChatControllerCallback() } + + verify { configurationManager.setVisitorContextAssetId(eq(visitorContextAssetId)) } + + verify { engagementTypeUseCase.isCallVisualizer } + verify { callVisualizerController.showAlreadyInCallSnackBar() } + verify(exactly = 0) { isQueueingOrLiveEngagementUseCase.hasOngoingLiveEngagement } + verify(exactly = 0) { activityLauncher.launchChat(any(), any()) } + verify(exactly = 0) { hasOngoingSecureConversationUseCase(any(), any()) } + } + + @Test + fun `startChat restores chat screen when has ongoing Live engagement`() { + mockConditions(hasOngoingLiveEngagement = true) + + engagementLauncher.startChat(activity) + + verify { isQueueingOrLiveEngagementUseCase.isQueueing } + + verify(exactly = 0) { endEngagementUseCase() } + verify(exactly = 0) { destroyChatControllerCallback() } + + verify(exactly = 0) { configurationManager.setVisitorContextAssetId(eq(visitorContextAssetId)) } + + verify { engagementTypeUseCase.isCallVisualizer } + verify(exactly = 0) { callVisualizerController.showAlreadyInCallSnackBar() } + verify { isQueueingOrLiveEngagementUseCase.hasOngoingLiveEngagement } + verify { activityLauncher.launchChat(eq(activity), eq(Intention.RETURN_TO_CHAT)) } + verify(exactly = 0) { hasOngoingSecureConversationUseCase(any(), any()) } + } + + @Test + fun `startChat launches secure conversation dialog when has pending secure conversation`() { + mockConditions(hasOngoingInteraction = true) + + engagementLauncher.startChat(activity) + + verify { isQueueingOrLiveEngagementUseCase.isQueueing } + + verify(exactly = 0) { endEngagementUseCase() } + verify(exactly = 0) { destroyChatControllerCallback() } + + verify(exactly = 0) { configurationManager.setVisitorContextAssetId(eq(visitorContextAssetId)) } + + verify { engagementTypeUseCase.isCallVisualizer } + verify(exactly = 0) { callVisualizerController.showAlreadyInCallSnackBar() } + verify { isQueueingOrLiveEngagementUseCase.hasOngoingLiveEngagement } + verify { hasOngoingSecureConversationUseCase(any(), any()) } + verify { activityLauncher.launchChat(eq(activity), eq(Intention.SC_DIALOG_ENQUEUE_FOR_TEXT)) } + } + @Test fun `startChat launches live chat when no pending secure conversations`() { - mockOngoingInteractionCallback(false) - every { isQueueingOrLiveEngagementUseCase.isQueueing } returns false + mockConditions(hasOngoingInteraction = false) engagementLauncher.startChat(activity) - verify { activityLauncher.launchChat(activity, Intention.LIVE_CHAT) } - verify(exactly = 0) { configurationManager.setVisitorContextAssetId(any()) } + verify { isQueueingOrLiveEngagementUseCase.isQueueing } + + verify(exactly = 0) { endEngagementUseCase() } + verify(exactly = 0) { destroyChatControllerCallback() } + + verify(exactly = 0) { configurationManager.setVisitorContextAssetId(eq(visitorContextAssetId)) } + + verify { engagementTypeUseCase.isCallVisualizer } + verify(exactly = 0) { callVisualizerController.showAlreadyInCallSnackBar() } + verify { isQueueingOrLiveEngagementUseCase.hasOngoingLiveEngagement } + verify { hasOngoingSecureConversationUseCase(any(), any()) } + verify { activityLauncher.launchChat(eq(activity), eq(Intention.LIVE_CHAT)) } } + //Start Audio Call @Test - fun `startChat launches secure conversation dialog when there are pending secure conversations`() { - mockOngoingInteractionCallback(true) - every { isQueueingOrLiveEngagementUseCase.isQueueing } returns false + fun `startAudioCall shows already in call snackBar when CV is ongoing`() { + mockConditions(isQueueing = true, isCallVisualizer = true, hasOngoingLiveEngagement = true) - engagementLauncher.startChat(activity, visitorContextAssetId) + engagementLauncher.startAudioCall(activity, visitorContextAssetId) + + verify { isQueueingOrLiveEngagementUseCase.isQueueing } + + verify { endEngagementUseCase() } + verify { destroyCallControllerCallback() } - verify { activityLauncher.launchChat(activity, Intention.SC_DIALOG_ENQUEUE_FOR_TEXT) } verify { configurationManager.setVisitorContextAssetId(eq(visitorContextAssetId)) } + + verify { engagementTypeUseCase.isCallVisualizer } + verify { callVisualizerController.showAlreadyInCallSnackBar() } + verify(exactly = 0) { engagementTypeUseCase.isMediaEngagement } + verify(exactly = 0) { isQueueingOrLiveEngagementUseCase.hasOngoingLiveEngagement } + verify(exactly = 0) { activityLauncher.launchCall(any(), any(), any()) } + verify(exactly = 0) { hasOngoingSecureConversationUseCase(any(), any()) } } @Test - fun `startAudioCall launches audio call when no pending secure conversations`() { - mockOngoingInteractionCallback(false) - every { isQueueingOrLiveEngagementUseCase.isQueueing } returns false + fun `startAudioCall restores call screen when has ongoing Media engagement`() { + mockConditions(isMediaEngagement = true) engagementLauncher.startAudioCall(activity) - verify { activityLauncher.launchCall(activity, Engagement.MediaType.AUDIO, false) } - verify(exactly = 0) { configurationManager.setVisitorContextAssetId(any()) } + verify { isQueueingOrLiveEngagementUseCase.isQueueing } + + verify(exactly = 0) { endEngagementUseCase() } + verify(exactly = 0) { destroyChatControllerCallback() } + + verify(exactly = 0) { configurationManager.setVisitorContextAssetId(eq(visitorContextAssetId)) } + + verify { engagementTypeUseCase.isCallVisualizer } + verify(exactly = 0) { callVisualizerController.showAlreadyInCallSnackBar() } + verify { engagementTypeUseCase.isMediaEngagement } + verify { activityLauncher.launchCall(eq(activity), isNull(), eq(false)) } + verify(exactly = 0) { isQueueingOrLiveEngagementUseCase.hasOngoingLiveEngagement } + verify(exactly = 0) { hasOngoingSecureConversationUseCase(any(), any()) } } @Test - fun `startAudioCall launches secure conversation audio dialog when there are pending secure conversations`() { - mockOngoingInteractionCallback(true) - every { isQueueingOrLiveEngagementUseCase.isQueueing } returns false + fun `startAudioCall restores chat screen when has ongoing Live engagement`() { + mockConditions(hasOngoingLiveEngagement = true) - engagementLauncher.startAudioCall(activity, visitorContextAssetId) + engagementLauncher.startAudioCall(activity) - verify { activityLauncher.launchChat(activity, Intention.SC_DIALOG_START_AUDIO) } - verify { configurationManager.setVisitorContextAssetId(eq(visitorContextAssetId)) } + verify { isQueueingOrLiveEngagementUseCase.isQueueing } + + verify(exactly = 0) { endEngagementUseCase() } + verify(exactly = 0) { destroyChatControllerCallback() } + + verify(exactly = 0) { configurationManager.setVisitorContextAssetId(eq(visitorContextAssetId)) } + + verify { engagementTypeUseCase.isCallVisualizer } + verify(exactly = 0) { callVisualizerController.showAlreadyInCallSnackBar() } + verify { engagementTypeUseCase.isMediaEngagement } + verify { isQueueingOrLiveEngagementUseCase.hasOngoingLiveEngagement } + verify { activityLauncher.launchChat(eq(activity), eq(Intention.RETURN_TO_CHAT)) } + verify(exactly = 0) { hasOngoingSecureConversationUseCase(any(), any()) } } @Test - fun `startVideoCall launches video call when no pending secure conversations`() { - mockOngoingInteractionCallback(false) - every { isQueueingOrLiveEngagementUseCase.isQueueing } returns false + fun `startAudioCall launches secure conversation dialog when has pending secure conversation`() { + mockConditions(hasOngoingInteraction = true) - engagementLauncher.startVideoCall(activity) + engagementLauncher.startAudioCall(activity) + + verify { isQueueingOrLiveEngagementUseCase.isQueueing } + + verify(exactly = 0) { endEngagementUseCase() } + verify(exactly = 0) { destroyChatControllerCallback() } + + verify(exactly = 0) { configurationManager.setVisitorContextAssetId(eq(visitorContextAssetId)) } - verify { activityLauncher.launchCall(activity, Engagement.MediaType.VIDEO, false) } - verify(exactly = 0) { configurationManager.setVisitorContextAssetId(any()) } + verify { engagementTypeUseCase.isCallVisualizer } + verify(exactly = 0) { callVisualizerController.showAlreadyInCallSnackBar() } + verify { engagementTypeUseCase.isMediaEngagement } + verify { isQueueingOrLiveEngagementUseCase.hasOngoingLiveEngagement } + verify { hasOngoingSecureConversationUseCase(any(), any()) } + verify { activityLauncher.launchChat(eq(activity), eq(Intention.SC_DIALOG_START_AUDIO)) } } @Test - fun `startVideoCall launches secure conversation video dialog when there are pending secure conversations`() { - mockOngoingInteractionCallback(true) - every { isQueueingOrLiveEngagementUseCase.isQueueing } returns false + fun `startAudioCall launches call screen when no pending secure conversations`() { + mockConditions(hasOngoingInteraction = false) + + engagementLauncher.startAudioCall(activity) + + verify { isQueueingOrLiveEngagementUseCase.isQueueing } + + verify(exactly = 0) { endEngagementUseCase() } + verify(exactly = 0) { destroyChatControllerCallback() } + + verify(exactly = 0) { configurationManager.setVisitorContextAssetId(eq(visitorContextAssetId)) } + + verify { engagementTypeUseCase.isCallVisualizer } + verify(exactly = 0) { callVisualizerController.showAlreadyInCallSnackBar() } + verify { engagementTypeUseCase.isMediaEngagement } + verify { isQueueingOrLiveEngagementUseCase.hasOngoingLiveEngagement } + verify { hasOngoingSecureConversationUseCase(any(), any()) } + verify { activityLauncher.launchCall(eq(activity), eq(Engagement.MediaType.AUDIO), eq(false)) } + } + + //Start Video Call + @Test + fun `startVideoCall shows already in call snackBar when CV is ongoing`() { + mockConditions(isQueueing = true, isCallVisualizer = true, hasOngoingLiveEngagement = true) engagementLauncher.startVideoCall(activity, visitorContextAssetId) - verify { activityLauncher.launchChat(activity, Intention.SC_DIALOG_START_VIDEO) } + verify { isQueueingOrLiveEngagementUseCase.isQueueing } + + verify { endEngagementUseCase() } + verify { destroyCallControllerCallback() } + verify { configurationManager.setVisitorContextAssetId(eq(visitorContextAssetId)) } + + verify { engagementTypeUseCase.isCallVisualizer } + verify { engagementTypeUseCase.hasVideo } + verify { callVisualizerController.showAlreadyInCallSnackBar() } + verify(exactly = 0) { engagementTypeUseCase.isMediaEngagement } + verify(exactly = 0) { isQueueingOrLiveEngagementUseCase.hasOngoingLiveEngagement } + verify(exactly = 0) { activityLauncher.launchCall(any(), any(), any()) } + verify(exactly = 0) { hasOngoingSecureConversationUseCase(any(), any()) } } @Test - fun `startSecureMessaging launches secure messaging welcome screen when no pending secure conversations`() { - mockOngoingInteractionCallback(false) - every { isQueueingOrLiveEngagementUseCase.isQueueing } returns false + fun `startVideoCall restores call screen when has ongoing Media engagement`() { + mockConditions(isMediaEngagement = true, isCallVisualizer = true, hasVideo = true) - engagementLauncher.startSecureMessaging(activity) + engagementLauncher.startVideoCall(activity) + + verify { isQueueingOrLiveEngagementUseCase.isQueueing } - verify { activityLauncher.launchSecureMessagingWelcomeScreen(activity) } - verify(exactly = 0) { configurationManager.setVisitorContextAssetId(any()) } + verify(exactly = 0) { endEngagementUseCase() } + verify(exactly = 0) { destroyChatControllerCallback() } + + verify(exactly = 0) { configurationManager.setVisitorContextAssetId(eq(visitorContextAssetId)) } + + verify { engagementTypeUseCase.isCallVisualizer } + verify { engagementTypeUseCase.hasVideo } + verify(exactly = 0) { callVisualizerController.showAlreadyInCallSnackBar() } + verify { engagementTypeUseCase.isMediaEngagement } + verify { activityLauncher.launchCall(eq(activity), isNull(), eq(false)) } + verify(exactly = 0) { isQueueingOrLiveEngagementUseCase.hasOngoingLiveEngagement } + verify(exactly = 0) { hasOngoingSecureConversationUseCase(any(), any()) } + } + + @Test + fun `startVideoCall restores chat screen when has ongoing Live engagement`() { + mockConditions(hasOngoingLiveEngagement = true) + + engagementLauncher.startVideoCall(activity) + + verify { isQueueingOrLiveEngagementUseCase.isQueueing } + + verify(exactly = 0) { endEngagementUseCase() } + verify(exactly = 0) { destroyChatControllerCallback() } + + verify(exactly = 0) { configurationManager.setVisitorContextAssetId(eq(visitorContextAssetId)) } + + verify { engagementTypeUseCase.isCallVisualizer } + verify(exactly = 0) { callVisualizerController.showAlreadyInCallSnackBar() } + verify { engagementTypeUseCase.isMediaEngagement } + verify { isQueueingOrLiveEngagementUseCase.hasOngoingLiveEngagement } + verify { activityLauncher.launchChat(eq(activity), eq(Intention.RETURN_TO_CHAT)) } + verify(exactly = 0) { hasOngoingSecureConversationUseCase(any(), any()) } } @Test - fun `startSecureMessaging launches secure chat when there are pending secure conversations`() { - mockOngoingInteractionCallback(true) - every { isQueueingOrLiveEngagementUseCase.isQueueing } returns false + fun `startVideoCall launches secure conversation dialog when has pending secure conversation`() { + mockConditions(hasOngoingInteraction = true) + + engagementLauncher.startVideoCall(activity) + + verify { isQueueingOrLiveEngagementUseCase.isQueueing } + + verify(exactly = 0) { endEngagementUseCase() } + verify(exactly = 0) { destroyChatControllerCallback() } + + verify(exactly = 0) { configurationManager.setVisitorContextAssetId(eq(visitorContextAssetId)) } + + verify { engagementTypeUseCase.isCallVisualizer } + verify(exactly = 0) { callVisualizerController.showAlreadyInCallSnackBar() } + verify { engagementTypeUseCase.isMediaEngagement } + verify { isQueueingOrLiveEngagementUseCase.hasOngoingLiveEngagement } + verify { hasOngoingSecureConversationUseCase(any(), any()) } + verify { activityLauncher.launchChat(eq(activity), eq(Intention.SC_DIALOG_START_VIDEO)) } + } + + @Test + fun `startVideoCall launches call screen when no pending secure conversations`() { + mockConditions(hasOngoingInteraction = false) + + engagementLauncher.startVideoCall(activity) + + verify { isQueueingOrLiveEngagementUseCase.isQueueing } + + verify(exactly = 0) { endEngagementUseCase() } + verify(exactly = 0) { destroyChatControllerCallback() } + + verify(exactly = 0) { configurationManager.setVisitorContextAssetId(eq(visitorContextAssetId)) } + + verify { engagementTypeUseCase.isCallVisualizer } + verify(exactly = 0) { callVisualizerController.showAlreadyInCallSnackBar() } + verify { engagementTypeUseCase.isMediaEngagement } + verify { isQueueingOrLiveEngagementUseCase.hasOngoingLiveEngagement } + verify { hasOngoingSecureConversationUseCase(any(), any()) } + verify { activityLauncher.launchCall(eq(activity), eq(Engagement.MediaType.VIDEO), eq(false)) } + } + + + @Test + fun `startSecureMessaging shows already in call snackBar when CV is ongoing`() { + mockConditions(isCallVisualizer = true, hasOngoingLiveEngagement = true) engagementLauncher.startSecureMessaging(activity, visitorContextAssetId) - verify { activityLauncher.launchChat(activity, Intention.SC_CHAT) } verify { configurationManager.setVisitorContextAssetId(eq(visitorContextAssetId)) } - } - private fun mockOngoingInteractionCallback(hasOngoingInteraction: Boolean) { - every { hasOngoingSecureConversationUseCase(captureLambda()) } answers { - firstArg<(Boolean) -> Unit>().invoke(hasOngoingInteraction) - } + verify { engagementTypeUseCase.isCallVisualizer } + verify { callVisualizerController.showAlreadyInCallSnackBar() } + verify(exactly = 0) { isQueueingOrLiveEngagementUseCase.hasOngoingLiveEngagement } + verify(exactly = 0) { activityLauncher.launchChat(any(), any()) } + verify(exactly = 0) { activityLauncher.launchSecureMessagingWelcomeScreen(any()) } + verify(exactly = 0) { hasOngoingSecureConversationUseCase(any(), any()) } } @Test - fun `startChat ends engagement and destroys chat controller when queueing`() { - mockOngoingInteractionCallback(false) - every { isQueueingOrLiveEngagementUseCase.isQueueing } returns true + fun `startSecureMessaging restores chat screen when has ongoing Live engagement`() { + mockConditions(hasOngoingLiveEngagement = true) - engagementLauncher.startChat(activity) + engagementLauncher.startSecureMessaging(activity) - verify { endEngagementUseCase() } - verify { controllerFactory.destroyChatController() } - verify { activityLauncher.launchChat(activity, Intention.LIVE_CHAT) } + verify(exactly = 0) { configurationManager.setVisitorContextAssetId(eq(visitorContextAssetId)) } + + verify { engagementTypeUseCase.isCallVisualizer } + verify(exactly = 0) { callVisualizerController.showAlreadyInCallSnackBar() } + verify { isQueueingOrLiveEngagementUseCase.hasOngoingLiveEngagement } + verify { activityLauncher.launchChat(eq(activity), eq(Intention.RETURN_TO_CHAT)) } + verify(exactly = 0) { activityLauncher.launchSecureMessagingWelcomeScreen(any()) } + verify(exactly = 0) { hasOngoingSecureConversationUseCase(any(), any()) } } @Test - fun `startAudioCall ends engagement and destroys call controller when queueing`() { - mockOngoingInteractionCallback(false) - every { isQueueingOrLiveEngagementUseCase.isQueueing } returns true + fun `startSecureMessaging launches secure conversation screen when has pending secure conversation`() { + mockConditions(hasOngoingInteraction = true) - engagementLauncher.startAudioCall(activity) + engagementLauncher.startSecureMessaging(activity) - verify { endEngagementUseCase() } - verify { controllerFactory.destroyCallController() } - verify { activityLauncher.launchCall(activity, Engagement.MediaType.AUDIO, false) } + verify(exactly = 0) { configurationManager.setVisitorContextAssetId(eq(visitorContextAssetId)) } + + verify { engagementTypeUseCase.isCallVisualizer } + verify(exactly = 0) { callVisualizerController.showAlreadyInCallSnackBar() } + verify { isQueueingOrLiveEngagementUseCase.hasOngoingLiveEngagement } + verify { hasOngoingSecureConversationUseCase(any(), any()) } + verify { activityLauncher.launchChat(eq(activity), eq(Intention.SC_CHAT)) } + verify(exactly = 0) { activityLauncher.launchSecureMessagingWelcomeScreen(any()) } } @Test - fun `startVideoCall ends engagement and destroys call controller when queueing`() { - mockOngoingInteractionCallback(false) - every { isQueueingOrLiveEngagementUseCase.isQueueing } returns true + fun `startSecureMessaging launches SC welcome screen when no pending secure conversations`() { + mockConditions(hasOngoingInteraction = false) - engagementLauncher.startVideoCall(activity) + engagementLauncher.startSecureMessaging(activity) - verify { endEngagementUseCase() } - verify { controllerFactory.destroyCallController() } - verify { activityLauncher.launchCall(activity, Engagement.MediaType.VIDEO, false) } + verify(exactly = 0) { configurationManager.setVisitorContextAssetId(eq(visitorContextAssetId)) } + + verify { engagementTypeUseCase.isCallVisualizer } + verify(exactly = 0) { callVisualizerController.showAlreadyInCallSnackBar() } + verify { isQueueingOrLiveEngagementUseCase.hasOngoingLiveEngagement } + verify { hasOngoingSecureConversationUseCase(any(), any()) } + verify(exactly = 0) { activityLauncher.launchChat(eq(activity), eq(Intention.SC_CHAT)) } + verify { activityLauncher.launchSecureMessagingWelcomeScreen(eq(activity)) } + } + + private fun mockConditions( + isQueueing: Boolean = false, + hasOngoingLiveEngagement: Boolean = false, + isCallVisualizer: Boolean = false, + isMediaEngagement: Boolean = false, + hasVideo: Boolean = false, + hasOngoingInteraction: Boolean? = null + ) { + every { isQueueingOrLiveEngagementUseCase.isQueueing } returns isQueueing + every { isQueueingOrLiveEngagementUseCase.hasOngoingLiveEngagement } returns hasOngoingLiveEngagement + every { engagementTypeUseCase.isCallVisualizer } returns isCallVisualizer + every { engagementTypeUseCase.isMediaEngagement } returns isMediaEngagement + every { engagementTypeUseCase.hasVideo } returns hasVideo + + if (hasOngoingInteraction ?: return) { + every { hasOngoingSecureConversationUseCase(captureLambda(), any()) } answers { + firstArg<() -> Unit>().invoke() + } + } else { + every { hasOngoingSecureConversationUseCase(any(), captureLambda()) } answers { + secondArg<() -> Unit>().invoke() + } + } } }