Code format
This commit is contained in:
Родитель
bcb36bc87c
Коммит
d642737c1f
|
@ -0,0 +1,8 @@
|
|||
root = true
|
||||
|
||||
[*.{kt,kts}]
|
||||
ktlint_standard_max-line-length = disabled
|
||||
ktlint_standard_property-naming = disabled
|
||||
ktlint_standard_no-consecutive-comments = disabled
|
||||
ktlint_standard_filename = disabled
|
||||
ktlint_standard_blank-line-before-declaration = disabled
|
|
@ -95,10 +95,10 @@ buildscript {
|
|||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:8.2.2'
|
||||
// classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath "org.jacoco:org.jacoco.core:$jacoco_version"
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
|
@ -108,7 +108,7 @@ buildscript {
|
|||
|
||||
plugins {
|
||||
id 'org.jetbrains.kotlin.android' version '1.9.0' apply false
|
||||
id "org.jlleitschuh.gradle.ktlint" version "10.2.1" apply(true)
|
||||
id "org.jlleitschuh.gradle.ktlint" version "12.1.0"
|
||||
}
|
||||
|
||||
allprojects {
|
||||
|
@ -140,10 +140,10 @@ allprojects {
|
|||
}
|
||||
}
|
||||
}
|
||||
apply plugin: "org.jlleitschuh.gradle.ktlint"
|
||||
}
|
||||
|
||||
subprojects {
|
||||
apply plugin: "org.jlleitschuh.gradle.ktlint"
|
||||
apply from: "../checkstyle.gradle"
|
||||
afterEvaluate {
|
||||
preBuild.dependsOn 'checkstyle'
|
||||
|
|
|
@ -82,13 +82,12 @@ android {
|
|||
}
|
||||
|
||||
ktlint {
|
||||
debug.set(false)
|
||||
debug = true
|
||||
verbose.set(true)
|
||||
android.set(true)
|
||||
outputToConsole.set(true)
|
||||
outputColorName.set("RED")
|
||||
enableExperimentalRules.set(false)
|
||||
disabledRules = ["import-ordering", "max-line-length", "parameter-list-wrapping"]
|
||||
reporters {
|
||||
reporter "checkstyle"
|
||||
reporter "plain"
|
||||
|
|
|
@ -40,7 +40,8 @@ internal open class BaseUiTest {
|
|||
internal val upperMessageBarNotificationId = R.id.azure_communication_ui_calling_upper_message_bar_notification
|
||||
internal val upperMessageBarNotificationIconId = R.id.azure_communication_ui_calling_upper_message_bar_notification_icon
|
||||
internal val upperMessageBarNotificationMessageId = R.id.azure_communication_ui_calling_upper_message_bar_notification_message
|
||||
internal val upperMessageBarNotificationDismissButtonId = R.id.azure_communication_ui_calling_upper_message_bar_notification_dismiss_button
|
||||
internal val upperMessageBarNotificationDismissButtonId =
|
||||
R.id.azure_communication_ui_calling_upper_message_bar_notification_dismiss_button
|
||||
internal val setupCameraButtonId = R.id.azure_communication_ui_setup_camera_button
|
||||
internal val callCameraButtonId = R.id.azure_communication_ui_call_switch_camera_button
|
||||
|
||||
|
@ -50,17 +51,19 @@ internal open class BaseUiTest {
|
|||
internal val userMessageEditTextId = R.id.azure_communication_ui_user_message_edit_text
|
||||
|
||||
internal val showSupportFormTextId = R.string.azure_communication_ui_calling_report_issue_title
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
var grantPermissionRule: GrantPermissionRule
|
||||
|
||||
private val basePermissionList = arrayOf(
|
||||
"android.permission.ACCESS_NETWORK_STATE",
|
||||
"android.permission.WAKE_LOCK",
|
||||
"android.permission.MODIFY_AUDIO_SETTINGS",
|
||||
"android.permission.CAMERA",
|
||||
"android.permission.RECORD_AUDIO"
|
||||
)
|
||||
private val basePermissionList =
|
||||
arrayOf(
|
||||
"android.permission.ACCESS_NETWORK_STATE",
|
||||
"android.permission.WAKE_LOCK",
|
||||
"android.permission.MODIFY_AUDIO_SETTINGS",
|
||||
"android.permission.CAMERA",
|
||||
"android.permission.RECORD_AUDIO",
|
||||
)
|
||||
|
||||
init {
|
||||
grantPermissionRule = GrantPermissionRule.grant(*basePermissionList)
|
||||
|
|
|
@ -26,48 +26,51 @@ internal fun waitUntilDisplayed(id: Int) {
|
|||
internal fun assertDisplayed(id: Int): ViewInteraction {
|
||||
return Espresso.onView(
|
||||
Matchers.allOf(
|
||||
ViewMatchers.withId(id)
|
||||
)
|
||||
ViewMatchers.withId(id),
|
||||
),
|
||||
).check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
|
||||
}
|
||||
|
||||
internal fun assertNotDisplayed(id: Int): ViewInteraction? {
|
||||
return Espresso.onView(
|
||||
Matchers.allOf(
|
||||
ViewMatchers.withId(id)
|
||||
)
|
||||
ViewMatchers.withId(id),
|
||||
),
|
||||
).check(doesNotExist())
|
||||
}
|
||||
|
||||
internal fun assertViewGone(id: Int): ViewInteraction? {
|
||||
return Espresso.onView(
|
||||
Matchers.allOf(
|
||||
ViewMatchers.withId(id)
|
||||
)
|
||||
ViewMatchers.withId(id),
|
||||
),
|
||||
).check(ViewAssertions.matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE)))
|
||||
}
|
||||
|
||||
internal fun assertViewNotDisplayed(id: Int) {
|
||||
Espresso.onView(
|
||||
Matchers.allOf(
|
||||
ViewMatchers.withId(id)
|
||||
)
|
||||
ViewMatchers.withId(id),
|
||||
),
|
||||
).check(ViewAssertions.matches(Matchers.not(ViewMatchers.isDisplayed())))
|
||||
}
|
||||
|
||||
internal fun assertNotExist(id: Int): ViewInteraction? {
|
||||
return Espresso.onView(
|
||||
Matchers.allOf(
|
||||
ViewMatchers.withId(id)
|
||||
)
|
||||
ViewMatchers.withId(id),
|
||||
),
|
||||
).check(doesNotExist())
|
||||
}
|
||||
|
||||
internal fun assertViewText(id: Int, text: String) {
|
||||
internal fun assertViewText(
|
||||
id: Int,
|
||||
text: String,
|
||||
) {
|
||||
Espresso.onView(
|
||||
Matchers.allOf(
|
||||
ViewMatchers.withId(id)
|
||||
)
|
||||
ViewMatchers.withId(id),
|
||||
),
|
||||
).check(ViewAssertions.matches(ViewMatchers.withText(text)))
|
||||
}
|
||||
|
||||
|
@ -75,24 +78,28 @@ internal fun tapOnScreen() {
|
|||
Espresso.onView(ViewMatchers.isRoot())
|
||||
.perform(ViewActions.click())
|
||||
}
|
||||
|
||||
internal fun tapWithTextWhenDisplayed(text: String) {
|
||||
// wait until text is displayed
|
||||
waitUntilViewIsDisplayed {
|
||||
Espresso.onView(
|
||||
Matchers.allOf(ViewMatchers.withText(text), withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))
|
||||
Matchers.allOf(ViewMatchers.withText(text), withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)),
|
||||
).check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
|
||||
}
|
||||
tapDelay()
|
||||
Espresso.onView(
|
||||
Matchers.allOf(ViewMatchers.withText(text), withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))
|
||||
Matchers.allOf(ViewMatchers.withText(text), withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)),
|
||||
).perform(ViewActions.click())
|
||||
}
|
||||
|
||||
internal fun assertViewText(id: Int, textId: Int) {
|
||||
internal fun assertViewText(
|
||||
id: Int,
|
||||
textId: Int,
|
||||
) {
|
||||
Espresso.onView(
|
||||
Matchers.allOf(
|
||||
ViewMatchers.withId(id)
|
||||
)
|
||||
ViewMatchers.withId(id),
|
||||
),
|
||||
).check(ViewAssertions.matches(ViewMatchers.withText(textId)))
|
||||
}
|
||||
|
||||
|
@ -132,14 +139,19 @@ internal fun waitUntilViewIsDisplayed(idlingCheck: () -> ViewInteraction): ViewI
|
|||
if (ex is AssertionFailedError || ex is NoMatchingViewException) {
|
||||
SystemClock.sleep(2000L)
|
||||
timeOut += 2000L
|
||||
} else throw ex
|
||||
} else {
|
||||
throw ex
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isReady) return viewInteraction
|
||||
throw IllegalStateException("Timed out waiting for view")
|
||||
}
|
||||
|
||||
internal fun assertViewHasChild(@IdRes id: Int, n: Int) {
|
||||
internal fun assertViewHasChild(
|
||||
@IdRes id: Int,
|
||||
n: Int,
|
||||
) {
|
||||
Espresso.onView(ViewMatchers.withId(id))
|
||||
.check(ViewAssertions.matches(ViewMatchers.hasChildCount(n)))
|
||||
}
|
||||
|
|
|
@ -4,15 +4,15 @@
|
|||
package com.azure.android.communication.mocking
|
||||
|
||||
import androidx.annotation.GuardedBy
|
||||
import com.azure.android.communication.calling.CameraFacing
|
||||
import com.azure.android.communication.calling.VideoDeviceType
|
||||
import com.azure.android.communication.calling.ParticipantState
|
||||
import com.azure.android.communication.calling.MediaStreamType
|
||||
import com.azure.android.communication.calling.CallState
|
||||
import com.azure.android.communication.calling.RemoteVideoStreamsUpdatedListener
|
||||
import com.azure.android.communication.calling.CameraFacing
|
||||
import com.azure.android.communication.calling.MediaStreamType
|
||||
import com.azure.android.communication.calling.ParticipantState
|
||||
import com.azure.android.communication.calling.PropertyChangedListener
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeLobbyErrorCode
|
||||
import com.azure.android.communication.calling.RemoteVideoStreamsUpdatedListener
|
||||
import com.azure.android.communication.calling.VideoDeviceType
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeInternalParticipantRole
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeLobbyErrorCode
|
||||
import com.azure.android.communication.ui.calling.models.CallDiagnosticQuality
|
||||
import com.azure.android.communication.ui.calling.models.MediaCallDiagnostic
|
||||
import com.azure.android.communication.ui.calling.models.MediaCallDiagnosticModel
|
||||
|
@ -29,22 +29,22 @@ import com.azure.android.communication.ui.calling.service.sdk.CallingSDK
|
|||
import com.azure.android.communication.ui.calling.service.sdk.CallingStateWrapper
|
||||
import com.azure.android.communication.ui.calling.service.sdk.CommunicationIdentifier
|
||||
import com.azure.android.communication.ui.calling.service.sdk.DominantSpeakersInfo
|
||||
import com.azure.android.communication.ui.calling.service.sdk.into
|
||||
import com.azure.android.communication.ui.calling.service.sdk.LocalVideoStream
|
||||
import com.azure.android.communication.ui.calling.service.sdk.VideoDeviceInfo
|
||||
import com.azure.android.communication.ui.calling.service.sdk.RemoteVideoStream
|
||||
import com.azure.android.communication.ui.calling.service.sdk.RemoteParticipant
|
||||
import com.azure.android.communication.ui.calling.service.sdk.RemoteVideoStream
|
||||
import com.azure.android.communication.ui.calling.service.sdk.VideoDeviceInfo
|
||||
import com.azure.android.communication.ui.calling.service.sdk.into
|
||||
import com.azure.android.communication.ui.calling.utilities.CoroutineContextProvider
|
||||
import java9.util.concurrent.CompletableFuture
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
internal interface LocalStreamEventObserver {
|
||||
fun onSwitchSource(deviceInfo: VideoDeviceInfo)
|
||||
|
@ -61,7 +61,7 @@ internal class CallEvents {
|
|||
internal class LocalVideoStreamTest(
|
||||
private val callEvents: CallEvents,
|
||||
private val cameraFacing: CameraFacing,
|
||||
private val coroutineScope: CoroutineScope
|
||||
private val coroutineScope: CoroutineScope,
|
||||
) : LocalVideoStream {
|
||||
override val native: Any = 1
|
||||
override val source: VideoDeviceInfo
|
||||
|
@ -104,22 +104,24 @@ internal class TestCallingSDK(private val callEvents: CallEvents, coroutineConte
|
|||
state: ParticipantState = ParticipantState.CONNECTED,
|
||||
isMuted: Boolean = true,
|
||||
isSpeaking: Boolean = false,
|
||||
videoStreams: List<MediaStreamType>? = listOf()
|
||||
videoStreams: List<MediaStreamType>? = listOf(),
|
||||
) {
|
||||
val rpi = RemoteParticipantImpl(
|
||||
identifier = id,
|
||||
displayName = displayName,
|
||||
isMuted = isMuted,
|
||||
isSpeaking = isSpeaking,
|
||||
videoStreams = videoStreams?.mapIndexed { index, type ->
|
||||
RemoteVideoStreamImpl(
|
||||
native = 1,
|
||||
id = index,
|
||||
mediaStreamType = type
|
||||
)
|
||||
} ?: listOf(),
|
||||
state = state
|
||||
)
|
||||
val rpi =
|
||||
RemoteParticipantImpl(
|
||||
identifier = id,
|
||||
displayName = displayName,
|
||||
isMuted = isMuted,
|
||||
isSpeaking = isSpeaking,
|
||||
videoStreams =
|
||||
videoStreams?.mapIndexed { index, type ->
|
||||
RemoteVideoStreamImpl(
|
||||
native = 1,
|
||||
id = index,
|
||||
mediaStreamType = type,
|
||||
)
|
||||
} ?: listOf(),
|
||||
state = state,
|
||||
)
|
||||
synchronized(this) {
|
||||
remoteParticipantsMap[id.id] = rpi
|
||||
}
|
||||
|
@ -127,20 +129,24 @@ internal class TestCallingSDK(private val callEvents: CallEvents, coroutineConte
|
|||
emitRemoteParticipantFlow()
|
||||
}
|
||||
|
||||
suspend fun changeParticipantState(id: String, state: ParticipantState) {
|
||||
suspend fun changeParticipantState(
|
||||
id: String,
|
||||
state: ParticipantState,
|
||||
) {
|
||||
synchronized(this) {
|
||||
if (!remoteParticipantsMap.containsKey(id)) {
|
||||
return
|
||||
}
|
||||
val rpi = remoteParticipantsMap[id]!!
|
||||
remoteParticipantsMap[id] = RemoteParticipantImpl(
|
||||
identifier = rpi.identifier,
|
||||
displayName = rpi.displayName,
|
||||
isMuted = rpi.isMuted,
|
||||
isSpeaking = rpi.isSpeaking,
|
||||
videoStreams = rpi.videoStreams,
|
||||
state = state
|
||||
)
|
||||
remoteParticipantsMap[id] =
|
||||
RemoteParticipantImpl(
|
||||
identifier = rpi.identifier,
|
||||
displayName = rpi.displayName,
|
||||
isMuted = rpi.isMuted,
|
||||
isSpeaking = rpi.isSpeaking,
|
||||
videoStreams = rpi.videoStreams,
|
||||
state = state,
|
||||
)
|
||||
}
|
||||
emitRemoteParticipantFlow()
|
||||
}
|
||||
|
@ -166,37 +172,50 @@ internal class TestCallingSDK(private val callEvents: CallEvents, coroutineConte
|
|||
id: String,
|
||||
isMuted: Boolean? = null,
|
||||
isSpeaking: Boolean? = null,
|
||||
state: ParticipantState? = null
|
||||
state: ParticipantState? = null,
|
||||
) {
|
||||
synchronized(this) {
|
||||
if (!remoteParticipantsMap.containsKey(id)) {
|
||||
return
|
||||
}
|
||||
val rpi = remoteParticipantsMap[id]!!
|
||||
remoteParticipantsMap[id] = RemoteParticipantImpl(
|
||||
identifier = rpi.identifier,
|
||||
displayName = rpi.displayName,
|
||||
isMuted = isMuted ?: rpi.isMuted,
|
||||
isSpeaking = isSpeaking ?: rpi.isSpeaking,
|
||||
videoStreams = rpi.videoStreams,
|
||||
state = state ?: rpi.state
|
||||
)
|
||||
remoteParticipantsMap[id] =
|
||||
RemoteParticipantImpl(
|
||||
identifier = rpi.identifier,
|
||||
displayName = rpi.displayName,
|
||||
isMuted = isMuted ?: rpi.isMuted,
|
||||
isSpeaking = isSpeaking ?: rpi.isSpeaking,
|
||||
videoStreams = rpi.videoStreams,
|
||||
state = state ?: rpi.state,
|
||||
)
|
||||
}
|
||||
emitRemoteParticipantFlow()
|
||||
}
|
||||
|
||||
suspend fun setLowNetworkRecieveQuality(lowNetworkReceiveQuality: Boolean) {
|
||||
val model = NetworkQualityCallDiagnosticModel(NetworkCallDiagnostic.NETWORK_RECEIVE_QUALITY, if (lowNetworkReceiveQuality) CallDiagnosticQuality.BAD else CallDiagnosticQuality.GOOD)
|
||||
val model =
|
||||
NetworkQualityCallDiagnosticModel(
|
||||
NetworkCallDiagnostic.NETWORK_RECEIVE_QUALITY,
|
||||
if (lowNetworkReceiveQuality) CallDiagnosticQuality.BAD else CallDiagnosticQuality.GOOD,
|
||||
)
|
||||
networkQualityCallDiagnosticSharedFlow.emit(model)
|
||||
}
|
||||
|
||||
suspend fun setLowNetworkSendQuality(lowNetworkSendQuality: Boolean) {
|
||||
val model = NetworkQualityCallDiagnosticModel(NetworkCallDiagnostic.NETWORK_SEND_QUALITY, if (lowNetworkSendQuality) CallDiagnosticQuality.BAD else CallDiagnosticQuality.GOOD)
|
||||
val model =
|
||||
NetworkQualityCallDiagnosticModel(
|
||||
NetworkCallDiagnostic.NETWORK_SEND_QUALITY,
|
||||
if (lowNetworkSendQuality) CallDiagnosticQuality.BAD else CallDiagnosticQuality.GOOD,
|
||||
)
|
||||
networkQualityCallDiagnosticSharedFlow.emit(model)
|
||||
}
|
||||
|
||||
suspend fun setLowNetworkReconnectionQuality(lowNetworkReconnectionQuality: Boolean) {
|
||||
val model = NetworkQualityCallDiagnosticModel(NetworkCallDiagnostic.NETWORK_RECONNECTION_QUALITY, if (lowNetworkReconnectionQuality) CallDiagnosticQuality.BAD else CallDiagnosticQuality.GOOD)
|
||||
val model =
|
||||
NetworkQualityCallDiagnosticModel(
|
||||
NetworkCallDiagnostic.NETWORK_RECONNECTION_QUALITY,
|
||||
if (lowNetworkReconnectionQuality) CallDiagnosticQuality.BAD else CallDiagnosticQuality.GOOD,
|
||||
)
|
||||
networkQualityCallDiagnosticSharedFlow.emit(model)
|
||||
}
|
||||
|
||||
|
@ -253,6 +272,7 @@ internal class TestCallingSDK(private val callEvents: CallEvents, coroutineConte
|
|||
override fun setupCall(): CompletableFuture<Void> {
|
||||
return completedNullFuture()
|
||||
}
|
||||
|
||||
override fun dispose() {}
|
||||
|
||||
override fun turnOnVideoAsync(): CompletableFuture<LocalVideoStream> {
|
||||
|
@ -276,11 +296,12 @@ internal class TestCallingSDK(private val callEvents: CallEvents, coroutineConte
|
|||
}
|
||||
|
||||
override fun switchCameraAsync(): CompletableFuture<CameraDeviceSelectionStatus> {
|
||||
localCameraFacing = when (localCameraFacing) {
|
||||
CameraFacing.FRONT -> CameraFacing.BACK
|
||||
CameraFacing.BACK -> CameraFacing.FRONT
|
||||
else -> TODO("Camera modes that aren't Front or Back not yet implemented")
|
||||
}
|
||||
localCameraFacing =
|
||||
when (localCameraFacing) {
|
||||
CameraFacing.FRONT -> CameraFacing.BACK
|
||||
CameraFacing.BACK -> CameraFacing.FRONT
|
||||
else -> TODO("Camera modes that aren't Front or Back not yet implemented")
|
||||
}
|
||||
|
||||
localVideoStream.switchSource(localVideoStream.source.copy(cameraFacing = localCameraFacing)).join()
|
||||
|
||||
|
@ -371,6 +392,7 @@ internal class TestCallingSDK(private val callEvents: CallEvents, coroutineConte
|
|||
}
|
||||
|
||||
override fun getCamerasCountStateFlow(): StateFlow<Int> = getCameraCountStateFlow
|
||||
|
||||
override fun admitAll(): CompletableFuture<CallCompositeLobbyErrorCode?> {
|
||||
return lobbyResultCompletableFuture
|
||||
}
|
||||
|
@ -409,7 +431,7 @@ internal class TestCallingSDK(private val callEvents: CallEvents, coroutineConte
|
|||
when (this.mediaStreamType) {
|
||||
MediaStreamType.VIDEO -> StreamType.VIDEO
|
||||
MediaStreamType.SCREEN_SHARING -> StreamType.SCREEN_SHARING
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -417,7 +439,7 @@ internal class TestCallingSDK(private val callEvents: CallEvents, coroutineConte
|
|||
remoteParticipantsInfoModelSharedFlow.emit(
|
||||
synchronized(this) {
|
||||
this.getRemoteParticipantsMap().mapValues { it.value.asParticipantInfoModel() }
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -428,16 +450,16 @@ internal class TestCallingSDK(private val callEvents: CallEvents, coroutineConte
|
|||
isMuted = this.isMuted,
|
||||
isSpeaking = this.isSpeaking,
|
||||
participantStatus = this.state.into(),
|
||||
|
||||
screenShareVideoStreamModel = this.videoStreams.find {
|
||||
it.mediaStreamType == MediaStreamType.SCREEN_SHARING
|
||||
}?.asVideoStreamModel(),
|
||||
cameraVideoStreamModel = this.videoStreams.find {
|
||||
it.mediaStreamType == MediaStreamType.VIDEO
|
||||
}?.asVideoStreamModel(),
|
||||
|
||||
screenShareVideoStreamModel =
|
||||
this.videoStreams.find {
|
||||
it.mediaStreamType == MediaStreamType.SCREEN_SHARING
|
||||
}?.asVideoStreamModel(),
|
||||
cameraVideoStreamModel =
|
||||
this.videoStreams.find {
|
||||
it.mediaStreamType == MediaStreamType.VIDEO
|
||||
}?.asVideoStreamModel(),
|
||||
modifiedTimestamp = System.currentTimeMillis(),
|
||||
isCameraDisabled = false
|
||||
isCameraDisabled = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -454,7 +476,10 @@ internal fun completedNullFuture(): CompletableFuture<Void> {
|
|||
return CompletableFuture<Void>().also { it.complete(null) }
|
||||
}
|
||||
|
||||
internal fun completedNullFuture(coroutineScope: CoroutineScope, f: suspend () -> Any): CompletableFuture<Void> {
|
||||
internal fun completedNullFuture(
|
||||
coroutineScope: CoroutineScope,
|
||||
f: suspend () -> Any,
|
||||
): CompletableFuture<Void> {
|
||||
val future = CompletableFuture<Void>()
|
||||
coroutineScope.launch {
|
||||
f.invoke()
|
||||
|
|
|
@ -12,12 +12,12 @@ import com.azure.android.communication.calling.CreateViewOptions
|
|||
import com.azure.android.communication.calling.MediaStreamType
|
||||
import com.azure.android.communication.calling.ScalingMode
|
||||
import com.azure.android.communication.ui.calling.presentation.VideoStreamRendererFactory
|
||||
import com.azure.android.communication.ui.calling.service.sdk.RemoteVideoStream
|
||||
import com.azure.android.communication.ui.calling.service.sdk.VideoStreamRenderer
|
||||
import com.azure.android.communication.ui.calling.service.sdk.LocalVideoStream
|
||||
import com.azure.android.communication.ui.calling.service.sdk.VideoStreamRendererView
|
||||
import com.azure.android.communication.ui.calling.service.sdk.RemoteVideoStream
|
||||
import com.azure.android.communication.ui.calling.service.sdk.StreamSize
|
||||
import com.azure.android.communication.ui.calling.service.sdk.VideoDeviceInfo
|
||||
import com.azure.android.communication.ui.calling.service.sdk.VideoStreamRenderer
|
||||
import com.azure.android.communication.ui.calling.service.sdk.VideoStreamRendererView
|
||||
import com.google.android.apps.common.testing.accessibility.framework.replacements.LayoutParams
|
||||
|
||||
internal class TestVideoStreamRendererFactory(private val callEvents: CallEvents) :
|
||||
|
@ -44,37 +44,40 @@ internal enum class VideoType {
|
|||
REMOTE_SCREEN,
|
||||
LOCAL_VIDEO_FRONT,
|
||||
LOCAL_VIDEO_BACK,
|
||||
LOCAL_SCREEN_SHARE
|
||||
LOCAL_SCREEN_SHARE,
|
||||
}
|
||||
|
||||
internal sealed class Stream {
|
||||
data class Local(val stream: LocalVideoStream) : Stream()
|
||||
|
||||
data class Remote(val stream: RemoteVideoStream) : Stream()
|
||||
}
|
||||
|
||||
internal class TestVideoStreamRendererLocalWrapper(
|
||||
private val context: Context,
|
||||
callEvents: CallEvents,
|
||||
stream: Stream
|
||||
stream: Stream,
|
||||
) : VideoStreamRenderer, LocalStreamEventObserver {
|
||||
private val videoType: VideoType
|
||||
|
||||
init {
|
||||
when (stream) {
|
||||
is Stream.Local -> {
|
||||
videoType = when (stream.stream.source.cameraFacing) {
|
||||
CameraFacing.FRONT -> VideoType.LOCAL_VIDEO_FRONT
|
||||
CameraFacing.BACK -> VideoType.LOCAL_VIDEO_BACK
|
||||
else -> TODO("Camera modes that aren't Front or Back not yet implemented")
|
||||
}
|
||||
videoType =
|
||||
when (stream.stream.source.cameraFacing) {
|
||||
CameraFacing.FRONT -> VideoType.LOCAL_VIDEO_FRONT
|
||||
CameraFacing.BACK -> VideoType.LOCAL_VIDEO_BACK
|
||||
else -> TODO("Camera modes that aren't Front or Back not yet implemented")
|
||||
}
|
||||
callEvents.localStreamObservers[stream.stream] = this
|
||||
}
|
||||
is Stream.Remote -> {
|
||||
videoType = if (stream.stream.mediaStreamType == MediaStreamType.SCREEN_SHARING) {
|
||||
VideoType.REMOTE_SCREEN
|
||||
} else {
|
||||
VideoType.REMOTE_VIDEO
|
||||
}
|
||||
videoType =
|
||||
if (stream.stream.mediaStreamType == MediaStreamType.SCREEN_SHARING) {
|
||||
VideoType.REMOTE_SCREEN
|
||||
} else {
|
||||
VideoType.REMOTE_VIDEO
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -90,19 +93,21 @@ internal class TestVideoStreamRendererLocalWrapper(
|
|||
}
|
||||
|
||||
private fun createViewInternal(options: CreateViewOptions? = null): VideoStreamRendererView {
|
||||
val v = synchronized(this) {
|
||||
if (view != null) {
|
||||
view!!
|
||||
} else {
|
||||
TestVideoStreamRendererView(context, videoType).also { view = it }
|
||||
val v =
|
||||
synchronized(this) {
|
||||
if (view != null) {
|
||||
view!!
|
||||
} else {
|
||||
TestVideoStreamRendererView(context, videoType).also { view = it }
|
||||
}
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
override fun dispose() = synchronized(this) {
|
||||
view = null
|
||||
}
|
||||
override fun dispose() =
|
||||
synchronized(this) {
|
||||
view = null
|
||||
}
|
||||
|
||||
override fun getStreamSize(): StreamSize? {
|
||||
return view?.let {
|
||||
|
@ -122,11 +127,12 @@ internal class TestVideoStreamRendererLocalWrapper(
|
|||
}
|
||||
|
||||
internal class TestVideoStreamRendererView(context: Context, videoType: VideoType) : VideoStreamRendererView {
|
||||
private val v = WebView(context).also {
|
||||
it.settings.allowFileAccess = true
|
||||
it.layoutParams = FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
|
||||
it.loadUrl(fileName(videoType))
|
||||
}
|
||||
private val v =
|
||||
WebView(context).also {
|
||||
it.settings.allowFileAccess = true
|
||||
it.layoutParams = FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
|
||||
it.loadUrl(fileName(videoType))
|
||||
}
|
||||
|
||||
override fun dispose() {}
|
||||
|
||||
|
@ -142,13 +148,14 @@ internal class TestVideoStreamRendererView(context: Context, videoType: VideoTyp
|
|||
}
|
||||
|
||||
private fun fileName(videoType: VideoType): String {
|
||||
val f = when (videoType) {
|
||||
VideoType.REMOTE_VIDEO -> "remoteVideo.html"
|
||||
VideoType.REMOTE_SCREEN -> "remoteScreenShare.html"
|
||||
VideoType.LOCAL_VIDEO_BACK -> "localVideoBack.html"
|
||||
VideoType.LOCAL_VIDEO_FRONT -> "localVideoFront.html"
|
||||
VideoType.LOCAL_SCREEN_SHARE -> "localScreenShare.html"
|
||||
}
|
||||
val f =
|
||||
when (videoType) {
|
||||
VideoType.REMOTE_VIDEO -> "remoteVideo.html"
|
||||
VideoType.REMOTE_SCREEN -> "remoteScreenShare.html"
|
||||
VideoType.LOCAL_VIDEO_BACK -> "localVideoBack.html"
|
||||
VideoType.LOCAL_VIDEO_FRONT -> "localVideoFront.html"
|
||||
VideoType.LOCAL_SCREEN_SHARE -> "localScreenShare.html"
|
||||
}
|
||||
return "file:///android_asset/videoStreams/$f"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,47 +4,47 @@ package com.azure.android.communication.ui.calling
|
|||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import com.azure.android.communication.BaseUiTest
|
||||
import com.azure.android.communication.assertViewGone
|
||||
import com.azure.android.communication.common.CommunicationTokenCredential
|
||||
import com.azure.android.communication.common.CommunicationTokenRefreshOptions
|
||||
import com.azure.android.communication.tapWhenDisplayed
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeAudioVideoMode
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeGroupCallLocator
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeLocalOptions
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeRemoteOptions
|
||||
import com.azure.android.communication.waitUntilDisplayed
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
import java.util.UUID
|
||||
import com.azure.android.communication.assertViewGone
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeAudioVideoMode
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeLocalOptions
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
internal class AvModeTest : BaseUiTest() {
|
||||
|
||||
@Test
|
||||
fun testAvModeDisablesCameraButtons() = runTest {
|
||||
injectDependencies(testScheduler)
|
||||
// Launch the UI.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
val callComposite = CallCompositeBuilder().build()
|
||||
val communicationTokenRefreshOptions =
|
||||
CommunicationTokenRefreshOptions({ "token" }, true)
|
||||
val communicationTokenCredential =
|
||||
CommunicationTokenCredential(communicationTokenRefreshOptions)
|
||||
val remoteOptions =
|
||||
CallCompositeRemoteOptions(
|
||||
CallCompositeGroupCallLocator(UUID.fromString("74fce2c1-520f-11ec-97de-71411a9a8e14")),
|
||||
communicationTokenCredential,
|
||||
"test"
|
||||
)
|
||||
fun testAvModeDisablesCameraButtons() =
|
||||
runTest {
|
||||
injectDependencies(testScheduler)
|
||||
// Launch the UI.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
val callComposite = CallCompositeBuilder().build()
|
||||
val communicationTokenRefreshOptions =
|
||||
CommunicationTokenRefreshOptions({ "token" }, true)
|
||||
val communicationTokenCredential =
|
||||
CommunicationTokenCredential(communicationTokenRefreshOptions)
|
||||
val remoteOptions =
|
||||
CallCompositeRemoteOptions(
|
||||
CallCompositeGroupCallLocator(UUID.fromString("74fce2c1-520f-11ec-97de-71411a9a8e14")),
|
||||
communicationTokenCredential,
|
||||
"test",
|
||||
)
|
||||
|
||||
val localOptions = CallCompositeLocalOptions().setAudioVideoMode(CallCompositeAudioVideoMode.AUDIO_ONLY)
|
||||
val localOptions = CallCompositeLocalOptions().setAudioVideoMode(CallCompositeAudioVideoMode.AUDIO_ONLY)
|
||||
|
||||
callComposite.launchTest(appContext, remoteOptions, localOptions)
|
||||
waitUntilDisplayed(joinCallId)
|
||||
assertViewGone(setupCameraButtonId)
|
||||
tapWhenDisplayed(joinCallId)
|
||||
waitUntilDisplayed(endCallId)
|
||||
assertViewGone(callCameraButtonId)
|
||||
}
|
||||
callComposite.launchTest(appContext, remoteOptions, localOptions)
|
||||
waitUntilDisplayed(joinCallId)
|
||||
assertViewGone(setupCameraButtonId)
|
||||
tapWhenDisplayed(joinCallId)
|
||||
waitUntilDisplayed(endCallId)
|
||||
assertViewGone(callCameraButtonId)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,38 +20,40 @@ import java.util.UUID
|
|||
internal class CallHistoryRepositoryTest {
|
||||
@Test
|
||||
@ExperimentalCoroutinesApi
|
||||
fun callHistoryService_onCallStateUpdate_callsRepositoryInsert() = runTest {
|
||||
val context: Context = ApplicationProvider.getApplicationContext()
|
||||
AndroidThreeTen.init(context.applicationContext)
|
||||
fun callHistoryService_onCallStateUpdate_callsRepositoryInsert() =
|
||||
runTest {
|
||||
val context: Context = ApplicationProvider.getApplicationContext()
|
||||
AndroidThreeTen.init(context.applicationContext)
|
||||
|
||||
val repository: CallHistoryRepository = CallHistoryRepositoryImpl(context, DefaultLogger())
|
||||
val repository: CallHistoryRepository = CallHistoryRepositoryImpl(context, DefaultLogger())
|
||||
|
||||
val originalList = repository.getAll()
|
||||
val olderThanMonth = originalList.firstOrNull {
|
||||
// should not have records older then now() - 31 days.
|
||||
// Subtract a min to account possible delay while executing.
|
||||
it.callStartedOn.isBefore(OffsetDateTime.now().minusDays(31).minusMinutes(1))
|
||||
val originalList = repository.getAll()
|
||||
val olderThanMonth =
|
||||
originalList.firstOrNull {
|
||||
// should not have records older then now() - 31 days.
|
||||
// Subtract a min to account possible delay while executing.
|
||||
it.callStartedOn.isBefore(OffsetDateTime.now().minusDays(31).minusMinutes(1))
|
||||
}
|
||||
Assert.assertNull(olderThanMonth)
|
||||
|
||||
var callId = UUID.randomUUID().toString()
|
||||
var callStartDate = OffsetDateTime.now()
|
||||
// inserting a record older then 31 days
|
||||
repository.insert(callId, callStartDate.minusDays(32))
|
||||
// should not return new record
|
||||
var freshList = repository.getAll()
|
||||
Assert.assertEquals(originalList.count(), freshList.count())
|
||||
Assert.assertNull(freshList.find { it.callId == callId })
|
||||
|
||||
// inset new record
|
||||
repository.insert(callId, callStartDate)
|
||||
|
||||
freshList = repository.getAll()
|
||||
|
||||
Assert.assertEquals(originalList.count() + 1, freshList.count())
|
||||
|
||||
val retrievedNewRecord = freshList.find { it.callId == callId }
|
||||
Assert.assertNotNull(retrievedNewRecord)
|
||||
Assert.assertEquals(callStartDate, retrievedNewRecord!!.callStartedOn)
|
||||
}
|
||||
Assert.assertNull(olderThanMonth)
|
||||
|
||||
var callId = UUID.randomUUID().toString()
|
||||
var callStartDate = OffsetDateTime.now()
|
||||
// inserting a record older then 31 days
|
||||
repository.insert(callId, callStartDate.minusDays(32))
|
||||
// should not return new record
|
||||
var freshList = repository.getAll()
|
||||
Assert.assertEquals(originalList.count(), freshList.count())
|
||||
Assert.assertNull(freshList.find { it.callId == callId })
|
||||
|
||||
// inset new record
|
||||
repository.insert(callId, callStartDate)
|
||||
|
||||
freshList = repository.getAll()
|
||||
|
||||
Assert.assertEquals(originalList.count() + 1, freshList.count())
|
||||
|
||||
val retrievedNewRecord = freshList.find { it.callId == callId }
|
||||
Assert.assertNotNull(retrievedNewRecord)
|
||||
Assert.assertEquals(callStartDate, retrievedNewRecord!!.callStartedOn)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,92 +12,93 @@ import com.azure.android.communication.ui.calling.models.CallCompositeCallStateC
|
|||
import com.azure.android.communication.ui.calling.models.CallCompositeGroupCallLocator
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeRemoteOptions
|
||||
import com.azure.android.communication.waitUntilDisplayed
|
||||
import java.util.UUID
|
||||
import java9.util.concurrent.CompletableFuture
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
import java.util.UUID
|
||||
|
||||
internal class CallStateEventsTest : BaseUiTest() {
|
||||
|
||||
@Test
|
||||
fun testCallStateConnectedEvents() = runTest {
|
||||
injectDependencies(testScheduler)
|
||||
fun testCallStateConnectedEvents() =
|
||||
runTest {
|
||||
injectDependencies(testScheduler)
|
||||
|
||||
// Launch the UI.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
val callComposite = CallCompositeBuilder().build()
|
||||
val communicationTokenRefreshOptions =
|
||||
CommunicationTokenRefreshOptions({ "token" }, true)
|
||||
val communicationTokenCredential =
|
||||
CommunicationTokenCredential(communicationTokenRefreshOptions)
|
||||
val remoteOptions =
|
||||
CallCompositeRemoteOptions(
|
||||
CallCompositeGroupCallLocator(UUID.fromString("74fce2c1-520f-11ec-97de-71411a9a8e14")),
|
||||
communicationTokenCredential,
|
||||
"test"
|
||||
)
|
||||
// Launch the UI.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
val callComposite = CallCompositeBuilder().build()
|
||||
val communicationTokenRefreshOptions =
|
||||
CommunicationTokenRefreshOptions({ "token" }, true)
|
||||
val communicationTokenCredential =
|
||||
CommunicationTokenCredential(communicationTokenRefreshOptions)
|
||||
val remoteOptions =
|
||||
CallCompositeRemoteOptions(
|
||||
CallCompositeGroupCallLocator(UUID.fromString("74fce2c1-520f-11ec-97de-71411a9a8e14")),
|
||||
communicationTokenCredential,
|
||||
"test",
|
||||
)
|
||||
|
||||
// assert state is none
|
||||
assert(callComposite.callState == CallCompositeCallStateCode.NONE)
|
||||
// assert state is none
|
||||
assert(callComposite.callState == CallCompositeCallStateCode.NONE)
|
||||
|
||||
val list = mutableListOf<CallCompositeCallStateCode>()
|
||||
val list = mutableListOf<CallCompositeCallStateCode>()
|
||||
|
||||
callComposite.addOnCallStateChangedEventHandler {
|
||||
list.add(it.code)
|
||||
}
|
||||
|
||||
callComposite.launchTest(appContext, remoteOptions, null)
|
||||
|
||||
tapWhenDisplayed(joinCallId)
|
||||
waitUntilDisplayed(endCallId)
|
||||
|
||||
var size = list.size
|
||||
assert(size == 2)
|
||||
assert(list.contains(CallCompositeCallStateCode.CONNECTED))
|
||||
assert(list.contains(CallCompositeCallStateCode.NONE))
|
||||
assert(callComposite.callState == CallCompositeCallStateCode.CONNECTED)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCallStateDisconnectedEvents() = runTest {
|
||||
injectDependencies(testScheduler)
|
||||
|
||||
// Launch the UI.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
val callComposite = CallCompositeBuilder().build()
|
||||
val communicationTokenRefreshOptions =
|
||||
CommunicationTokenRefreshOptions({ "token" }, true)
|
||||
val communicationTokenCredential =
|
||||
CommunicationTokenCredential(communicationTokenRefreshOptions)
|
||||
val remoteOptions =
|
||||
CallCompositeRemoteOptions(
|
||||
CallCompositeGroupCallLocator(UUID.fromString("74fce2c1-520f-11ec-97de-71411a9a8e14")),
|
||||
communicationTokenCredential,
|
||||
"test"
|
||||
)
|
||||
|
||||
// assert state is none
|
||||
assert(callComposite.callState == CallCompositeCallStateCode.NONE)
|
||||
val list = mutableListOf<CallCompositeCallStateCode>()
|
||||
|
||||
val endCallCompletableFuture = CompletableFuture<Void>()
|
||||
callComposite.addOnCallStateChangedEventHandler {
|
||||
list.add(it.code)
|
||||
if (it.code == CallCompositeCallStateCode.DISCONNECTED) {
|
||||
endCallCompletableFuture.complete(null)
|
||||
callComposite.addOnCallStateChangedEventHandler {
|
||||
list.add(it.code)
|
||||
}
|
||||
}
|
||||
callComposite.launchTest(appContext, remoteOptions, null)
|
||||
|
||||
tapWhenDisplayed(joinCallId)
|
||||
waitUntilDisplayed(endCallId)
|
||||
callComposite.launchTest(appContext, remoteOptions, null)
|
||||
|
||||
endCallCompletableFuture.whenComplete { _, _ ->
|
||||
assert(list.size == 3)
|
||||
tapWhenDisplayed(joinCallId)
|
||||
waitUntilDisplayed(endCallId)
|
||||
|
||||
var size = list.size
|
||||
assert(size == 2)
|
||||
assert(list.contains(CallCompositeCallStateCode.CONNECTED))
|
||||
assert(list.contains(CallCompositeCallStateCode.NONE))
|
||||
assert(list.contains(CallCompositeCallStateCode.DISCONNECTED))
|
||||
assert(callComposite.callState == CallCompositeCallStateCode.DISCONNECTED)
|
||||
assert(callComposite.callState == CallCompositeCallStateCode.CONNECTED)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCallStateDisconnectedEvents() =
|
||||
runTest {
|
||||
injectDependencies(testScheduler)
|
||||
|
||||
// Launch the UI.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
val callComposite = CallCompositeBuilder().build()
|
||||
val communicationTokenRefreshOptions =
|
||||
CommunicationTokenRefreshOptions({ "token" }, true)
|
||||
val communicationTokenCredential =
|
||||
CommunicationTokenCredential(communicationTokenRefreshOptions)
|
||||
val remoteOptions =
|
||||
CallCompositeRemoteOptions(
|
||||
CallCompositeGroupCallLocator(UUID.fromString("74fce2c1-520f-11ec-97de-71411a9a8e14")),
|
||||
communicationTokenCredential,
|
||||
"test",
|
||||
)
|
||||
|
||||
// assert state is none
|
||||
assert(callComposite.callState == CallCompositeCallStateCode.NONE)
|
||||
val list = mutableListOf<CallCompositeCallStateCode>()
|
||||
|
||||
val endCallCompletableFuture = CompletableFuture<Void>()
|
||||
callComposite.addOnCallStateChangedEventHandler {
|
||||
list.add(it.code)
|
||||
if (it.code == CallCompositeCallStateCode.DISCONNECTED) {
|
||||
endCallCompletableFuture.complete(null)
|
||||
}
|
||||
}
|
||||
callComposite.launchTest(appContext, remoteOptions, null)
|
||||
|
||||
tapWhenDisplayed(joinCallId)
|
||||
waitUntilDisplayed(endCallId)
|
||||
|
||||
endCallCompletableFuture.whenComplete { _, _ ->
|
||||
assert(list.size == 3)
|
||||
assert(list.contains(CallCompositeCallStateCode.CONNECTED))
|
||||
assert(list.contains(CallCompositeCallStateCode.NONE))
|
||||
assert(list.contains(CallCompositeCallStateCode.DISCONNECTED))
|
||||
assert(callComposite.callState == CallCompositeCallStateCode.DISCONNECTED)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,93 +12,94 @@ import com.azure.android.communication.ui.calling.models.CallCompositeCallStateC
|
|||
import com.azure.android.communication.ui.calling.models.CallCompositeGroupCallLocator
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeRemoteOptions
|
||||
import com.azure.android.communication.waitUntilDisplayed
|
||||
import java.util.UUID
|
||||
import java9.util.concurrent.CompletableFuture
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
import java.util.UUID
|
||||
|
||||
internal class CompositeExitAPITest : BaseUiTest() {
|
||||
|
||||
@Test
|
||||
fun testCompositeExitSuccessWhenStateIsConnected() = runTest {
|
||||
injectDependencies(testScheduler)
|
||||
fun testCompositeExitSuccessWhenStateIsConnected() =
|
||||
runTest {
|
||||
injectDependencies(testScheduler)
|
||||
|
||||
// Launch the UI.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
val callComposite = CallCompositeBuilder().build()
|
||||
val communicationTokenRefreshOptions =
|
||||
CommunicationTokenRefreshOptions({ "token" }, true)
|
||||
val communicationTokenCredential =
|
||||
CommunicationTokenCredential(communicationTokenRefreshOptions)
|
||||
val remoteOptions =
|
||||
CallCompositeRemoteOptions(
|
||||
CallCompositeGroupCallLocator(UUID.fromString("74fce2c1-520f-11ec-97de-71411a9a8e14")),
|
||||
communicationTokenCredential,
|
||||
"test"
|
||||
)
|
||||
// Launch the UI.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
val callComposite = CallCompositeBuilder().build()
|
||||
val communicationTokenRefreshOptions =
|
||||
CommunicationTokenRefreshOptions({ "token" }, true)
|
||||
val communicationTokenCredential =
|
||||
CommunicationTokenCredential(communicationTokenRefreshOptions)
|
||||
val remoteOptions =
|
||||
CallCompositeRemoteOptions(
|
||||
CallCompositeGroupCallLocator(UUID.fromString("74fce2c1-520f-11ec-97de-71411a9a8e14")),
|
||||
communicationTokenCredential,
|
||||
"test",
|
||||
)
|
||||
|
||||
// assert state is none
|
||||
assert(callComposite.callState == CallCompositeCallStateCode.NONE)
|
||||
|
||||
var isExitCompositeReceived = false
|
||||
val exitCallCompletableFuture = CompletableFuture<Void>()
|
||||
callComposite.addOnDismissedEventHandler {
|
||||
isExitCompositeReceived = true
|
||||
exitCallCompletableFuture.complete(null)
|
||||
}
|
||||
|
||||
callComposite.launchTest(appContext, remoteOptions, null)
|
||||
|
||||
tapWhenDisplayed(joinCallId)
|
||||
waitUntilDisplayed(endCallId)
|
||||
|
||||
assert(callComposite.callState == CallCompositeCallStateCode.CONNECTED)
|
||||
// end call
|
||||
callComposite.dismiss()
|
||||
|
||||
exitCallCompletableFuture.whenComplete { _, _ ->
|
||||
assert(isExitCompositeReceived)
|
||||
assert(callComposite.callState == CallCompositeCallStateCode.DISCONNECTED)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCompositeExitSuccessWhenStateIsNone() = runTest {
|
||||
injectDependencies(testScheduler)
|
||||
|
||||
// Launch the UI.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
val callComposite = CallCompositeBuilder().build()
|
||||
val communicationTokenRefreshOptions =
|
||||
CommunicationTokenRefreshOptions({ "token" }, true)
|
||||
val communicationTokenCredential =
|
||||
CommunicationTokenCredential(communicationTokenRefreshOptions)
|
||||
val remoteOptions =
|
||||
CallCompositeRemoteOptions(
|
||||
CallCompositeGroupCallLocator(UUID.fromString("74fce2c1-520f-11ec-97de-71411a9a8e14")),
|
||||
communicationTokenCredential,
|
||||
"test"
|
||||
)
|
||||
|
||||
// assert state is none
|
||||
assert(callComposite.callState == CallCompositeCallStateCode.NONE)
|
||||
var isExitCompositeReceived = false
|
||||
val exitCallCompletableFuture = CompletableFuture<Void>()
|
||||
callComposite.addOnDismissedEventHandler {
|
||||
isExitCompositeReceived = true
|
||||
exitCallCompletableFuture.complete(null)
|
||||
}
|
||||
callComposite.launchTest(appContext, remoteOptions, null)
|
||||
|
||||
waitUntilDisplayed(joinCallId)
|
||||
|
||||
assert(callComposite.callState == CallCompositeCallStateCode.NONE)
|
||||
// end call
|
||||
callComposite.dismiss()
|
||||
|
||||
exitCallCompletableFuture.whenComplete { _, _ ->
|
||||
assert(isExitCompositeReceived)
|
||||
// assert state is none
|
||||
assert(callComposite.callState == CallCompositeCallStateCode.NONE)
|
||||
|
||||
var isExitCompositeReceived = false
|
||||
val exitCallCompletableFuture = CompletableFuture<Void>()
|
||||
callComposite.addOnDismissedEventHandler {
|
||||
isExitCompositeReceived = true
|
||||
exitCallCompletableFuture.complete(null)
|
||||
}
|
||||
|
||||
callComposite.launchTest(appContext, remoteOptions, null)
|
||||
|
||||
tapWhenDisplayed(joinCallId)
|
||||
waitUntilDisplayed(endCallId)
|
||||
|
||||
assert(callComposite.callState == CallCompositeCallStateCode.CONNECTED)
|
||||
// end call
|
||||
callComposite.dismiss()
|
||||
|
||||
exitCallCompletableFuture.whenComplete { _, _ ->
|
||||
assert(isExitCompositeReceived)
|
||||
assert(callComposite.callState == CallCompositeCallStateCode.DISCONNECTED)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCompositeExitSuccessWhenStateIsNone() =
|
||||
runTest {
|
||||
injectDependencies(testScheduler)
|
||||
|
||||
// Launch the UI.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
val callComposite = CallCompositeBuilder().build()
|
||||
val communicationTokenRefreshOptions =
|
||||
CommunicationTokenRefreshOptions({ "token" }, true)
|
||||
val communicationTokenCredential =
|
||||
CommunicationTokenCredential(communicationTokenRefreshOptions)
|
||||
val remoteOptions =
|
||||
CallCompositeRemoteOptions(
|
||||
CallCompositeGroupCallLocator(UUID.fromString("74fce2c1-520f-11ec-97de-71411a9a8e14")),
|
||||
communicationTokenCredential,
|
||||
"test",
|
||||
)
|
||||
|
||||
// assert state is none
|
||||
assert(callComposite.callState == CallCompositeCallStateCode.NONE)
|
||||
var isExitCompositeReceived = false
|
||||
val exitCallCompletableFuture = CompletableFuture<Void>()
|
||||
callComposite.addOnDismissedEventHandler {
|
||||
isExitCompositeReceived = true
|
||||
exitCallCompletableFuture.complete(null)
|
||||
}
|
||||
callComposite.launchTest(appContext, remoteOptions, null)
|
||||
|
||||
waitUntilDisplayed(joinCallId)
|
||||
|
||||
assert(callComposite.callState == CallCompositeCallStateCode.NONE)
|
||||
// end call
|
||||
callComposite.dismiss()
|
||||
|
||||
exitCallCompletableFuture.whenComplete { _, _ ->
|
||||
assert(isExitCompositeReceived)
|
||||
assert(callComposite.callState == CallCompositeCallStateCode.NONE)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,485 +1,502 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
package com.azure.android.communication.ui.calling
|
||||
|
||||
import android.content.Context
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import com.azure.android.communication.BaseUiTest
|
||||
import com.azure.android.communication.assertViewHasChild
|
||||
import com.azure.android.communication.assertViewNotDisplayed
|
||||
import com.azure.android.communication.assertViewText
|
||||
import com.azure.android.communication.calling.MediaStreamType
|
||||
import com.azure.android.communication.calling.ParticipantState
|
||||
import com.azure.android.communication.common.CommunicationTokenCredential
|
||||
import com.azure.android.communication.common.CommunicationTokenRefreshOptions
|
||||
import com.azure.android.communication.tapOnScreen
|
||||
import com.azure.android.communication.tapWhenDisplayed
|
||||
import com.azure.android.communication.tapWithTextWhenDisplayed
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeGroupCallLocator
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeInternalParticipantRole
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeRemoteOptions
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeTeamsMeetingLinkLocator
|
||||
import com.azure.android.communication.ui.calling.service.sdk.CommunicationIdentifier
|
||||
import com.azure.android.communication.waitUntilDisplayed
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
import java.util.UUID
|
||||
import com.azure.android.communication.ui.calling.implementation.R
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeLobbyErrorCode
|
||||
import java9.util.concurrent.CompletableFuture
|
||||
|
||||
internal class LobbyTest : BaseUiTest() {
|
||||
|
||||
@Test
|
||||
fun testOnGridViewLobbyParticipantAreNotVisible() = runTest {
|
||||
injectDependencies(testScheduler)
|
||||
|
||||
val expectedParticipantCountOnGridView = 2
|
||||
val expectedParticipantCountOnParticipantList = 3
|
||||
val expectedParticipantCountOnFloatingHeader = 2
|
||||
|
||||
lobbyParticipantsVisibilityTests(
|
||||
expectedParticipantCountOnFloatingHeader,
|
||||
expectedParticipantCountOnGridView,
|
||||
expectedParticipantCountOnParticipantList
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnGridViewAndParticipantListAllConnectedParticipantsAreVisible() = runTest {
|
||||
injectDependencies(testScheduler)
|
||||
|
||||
val expectedParticipantCountOnGridView = 2
|
||||
val expectedParticipantCountOnParticipantList = 3
|
||||
val expectedParticipantCountOnFloatingHeader = 2
|
||||
|
||||
lobbyParticipantsVisibilityTests(
|
||||
expectedParticipantCountOnFloatingHeader,
|
||||
expectedParticipantCountOnGridView,
|
||||
expectedParticipantCountOnParticipantList,
|
||||
false
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnLobbyParticipantNotDisplayedIfParticipantRoleIsAttendee() = runTest {
|
||||
injectDependencies(testScheduler)
|
||||
// Launch the UI.
|
||||
joinTeamsCall()
|
||||
|
||||
callingSDK.setParticipantRoleSharedFlow(CallCompositeInternalParticipantRole.ATTENDEE)
|
||||
|
||||
callingSDK.addRemoteParticipant(
|
||||
CommunicationIdentifier.CommunicationUserIdentifier("ACS User 2"),
|
||||
displayName = "ACS User 2",
|
||||
isMuted = false,
|
||||
isSpeaking = true,
|
||||
videoStreams = listOf(MediaStreamType.VIDEO),
|
||||
state = ParticipantState.IN_LOBBY
|
||||
)
|
||||
|
||||
// assert lobby header is not displayed
|
||||
assertViewNotDisplayed(lobbyHeaderId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnLobbyParticipantAddLobbyHeaderIsDisplayed() = runTest {
|
||||
injectDependencies(testScheduler)
|
||||
// Launch the UI.
|
||||
val appContext = joinTeamsCall()
|
||||
|
||||
callingSDK.setParticipantRoleSharedFlow(CallCompositeInternalParticipantRole.PRESENTER)
|
||||
|
||||
callingSDK.addRemoteParticipant(
|
||||
CommunicationIdentifier.CommunicationUserIdentifier("ACS User 2"),
|
||||
displayName = "ACS User 2",
|
||||
isMuted = false,
|
||||
isSpeaking = true,
|
||||
videoStreams = listOf(MediaStreamType.VIDEO),
|
||||
state = ParticipantState.IN_LOBBY
|
||||
)
|
||||
|
||||
waitUntilDisplayed(lobbyHeaderId)
|
||||
|
||||
// assert lobby header is displayed
|
||||
assertViewText(lobbyHeaderText, appContext!!.getString(R.string.azure_communication_ui_calling_lobby_header_text))
|
||||
|
||||
callingSDK.removeParticipant("ACS User 2")
|
||||
|
||||
// assert lobby header is not displayed
|
||||
assertViewNotDisplayed(lobbyHeaderId)
|
||||
|
||||
callingSDK.addRemoteParticipant(
|
||||
CommunicationIdentifier.CommunicationUserIdentifier("ACS User 3"),
|
||||
displayName = "ACS User 3",
|
||||
isMuted = false,
|
||||
isSpeaking = true,
|
||||
videoStreams = listOf(MediaStreamType.VIDEO),
|
||||
state = ParticipantState.IN_LOBBY
|
||||
)
|
||||
|
||||
waitUntilDisplayed(lobbyHeaderId)
|
||||
|
||||
tapWhenDisplayed(lobbyHeaderOpenParticipantListButton)
|
||||
// one local + one remote + 2 texts (calling, lobby)
|
||||
assertViewHasChild(bottomDrawer, 4)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnLobbyHeaderCloseButtonPressLobbyHeaderIsClosed() = runTest {
|
||||
injectDependencies(testScheduler)
|
||||
// launch the UI.
|
||||
joinTeamsCall()
|
||||
|
||||
callingSDK.setParticipantRoleSharedFlow(CallCompositeInternalParticipantRole.PRESENTER)
|
||||
|
||||
callingSDK.addRemoteParticipant(
|
||||
CommunicationIdentifier.CommunicationUserIdentifier("ACS User 2"),
|
||||
displayName = "ACS User 2",
|
||||
isMuted = false,
|
||||
isSpeaking = true,
|
||||
videoStreams = listOf(MediaStreamType.VIDEO),
|
||||
state = ParticipantState.IN_LOBBY
|
||||
)
|
||||
|
||||
waitUntilDisplayed(lobbyHeaderId)
|
||||
tapWhenDisplayed(lobbyHeaderCloseButton)
|
||||
assertViewNotDisplayed(lobbyHeaderId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnAdmitErrorLobbyErrorHeaderIsDisplayed() = runTest {
|
||||
injectDependencies(testScheduler)
|
||||
|
||||
val buttonTextToClick = "Admit"
|
||||
|
||||
// Launch the UI.
|
||||
val appContext = joinTeamsCall()
|
||||
|
||||
lobbyButtonActionsTest(buttonTextToClick, appContext)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnDeclineErrorLobbyErrorHeaderIsDisplayed() = runTest {
|
||||
injectDependencies(testScheduler)
|
||||
|
||||
val buttonTextToClick = "Decline"
|
||||
|
||||
// Launch the UI.
|
||||
val appContext = joinTeamsCall()
|
||||
|
||||
lobbyButtonActionsTest(buttonTextToClick, appContext)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnAdmitAllErrorLobbyErrorHeaderIsDisplayed() = runTest {
|
||||
injectDependencies(testScheduler)
|
||||
// Launch the UI.
|
||||
val appContext = joinTeamsCall()
|
||||
|
||||
callingSDK.setParticipantRoleSharedFlow(CallCompositeInternalParticipantRole.PRESENTER)
|
||||
|
||||
callingSDK.addRemoteParticipant(
|
||||
CommunicationIdentifier.CommunicationUserIdentifier("ACS User 2"),
|
||||
displayName = "ACS User 2",
|
||||
isMuted = false,
|
||||
isSpeaking = true,
|
||||
videoStreams = listOf(MediaStreamType.VIDEO),
|
||||
state = ParticipantState.IN_LOBBY
|
||||
)
|
||||
|
||||
waitUntilDisplayed(lobbyHeaderId)
|
||||
|
||||
tapWhenDisplayed(lobbyHeaderOpenParticipantListButton)
|
||||
// one local + one remote + 2 texts (calling, lobby)
|
||||
assertViewHasChild(bottomDrawer, 4)
|
||||
|
||||
val lobbyActionResult = CompletableFuture<CallCompositeLobbyErrorCode?>()
|
||||
lobbyActionResult.complete(CallCompositeLobbyErrorCode.LOBBY_MEETING_ROLE_NOT_ALLOWED)
|
||||
callingSDK.setLobbyResultCompletableFuture(lobbyActionResult)
|
||||
|
||||
// click on admit all
|
||||
tapWithTextWhenDisplayed("Admit all")
|
||||
|
||||
// to close participant list
|
||||
tapOnScreen()
|
||||
|
||||
waitUntilDisplayed(lobbyErrorHeaderId)
|
||||
|
||||
// assert error text
|
||||
assertViewText(
|
||||
lobbyErrorHeaderText,
|
||||
appContext!!.getString(R.string.azure_communication_ui_calling_error_lobby_meeting_role_not_allowded)
|
||||
)
|
||||
|
||||
// close lobby error header
|
||||
tapWhenDisplayed(lobbyErrorHeaderCloseButton)
|
||||
|
||||
// assert lobby error header is not displayed
|
||||
assertViewNotDisplayed(lobbyErrorHeaderId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnAdmitAllSuccess() = runTest {
|
||||
injectDependencies(testScheduler)
|
||||
// Launch the UI.
|
||||
joinTeamsCall()
|
||||
|
||||
callingSDK.setParticipantRoleSharedFlow(CallCompositeInternalParticipantRole.PRESENTER)
|
||||
|
||||
callingSDK.addRemoteParticipant(
|
||||
CommunicationIdentifier.CommunicationUserIdentifier("ACS User 2"),
|
||||
displayName = "ACS User 2",
|
||||
isMuted = false,
|
||||
isSpeaking = true,
|
||||
videoStreams = listOf(MediaStreamType.VIDEO),
|
||||
state = ParticipantState.IN_LOBBY
|
||||
)
|
||||
|
||||
waitUntilDisplayed(lobbyHeaderId)
|
||||
|
||||
tapWhenDisplayed(lobbyHeaderOpenParticipantListButton)
|
||||
// one local + one remote + 2 texts (calling, lobby)
|
||||
assertViewHasChild(bottomDrawer, 4)
|
||||
|
||||
val lobbyActionResult = CompletableFuture<CallCompositeLobbyErrorCode?>()
|
||||
callingSDK.setLobbyResultCompletableFuture(lobbyActionResult)
|
||||
|
||||
// click on admit all
|
||||
tapWithTextWhenDisplayed("Admit all")
|
||||
|
||||
// to close participant list
|
||||
tapOnScreen()
|
||||
|
||||
callingSDK.changeParticipantState("ACS User 2", ParticipantState.CONNECTED)
|
||||
lobbyActionResult.complete(null)
|
||||
|
||||
// assert lobby error header is not displayed
|
||||
assertViewNotDisplayed(lobbyErrorHeaderId)
|
||||
|
||||
// tap on screen
|
||||
tapOnScreen()
|
||||
tapWhenDisplayed(participantListOpenButton)
|
||||
|
||||
// one local + one remote + 1 texts (calling)
|
||||
assertViewHasChild(bottomDrawer, 3)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnLobbyErrorMessageUIForRoleNotPermitted() = runTest {
|
||||
injectDependencies(testScheduler)
|
||||
|
||||
val buttonTextToClick = "Admit"
|
||||
|
||||
// Launch the UI.
|
||||
val appContext = joinTeamsCall()
|
||||
|
||||
lobbyButtonActionsTest(
|
||||
buttonTextToClick, appContext,
|
||||
CallCompositeLobbyErrorCode.LOBBY_MEETING_ROLE_NOT_ALLOWED,
|
||||
R.string.azure_communication_ui_calling_error_lobby_meeting_role_not_allowded
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnLobbyErrorMessageUIForMeetingTypeNotSupported() = runTest {
|
||||
injectDependencies(testScheduler)
|
||||
|
||||
val buttonTextToClick = "Admit"
|
||||
|
||||
// Launch the UI.
|
||||
val appContext = joinTeamsCall()
|
||||
|
||||
lobbyButtonActionsTest(
|
||||
buttonTextToClick, appContext,
|
||||
CallCompositeLobbyErrorCode.LOBBY_CONVERSATION_TYPE_NOT_SUPPORTED,
|
||||
R.string.azure_communication_ui_calling_error_lobby_conversation_type_not_supported
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnLobbyErrorMessageUIForFailedToRemove() = runTest {
|
||||
injectDependencies(testScheduler)
|
||||
|
||||
val buttonTextToClick = "Admit"
|
||||
|
||||
// Launch the UI.
|
||||
val appContext = joinTeamsCall()
|
||||
|
||||
lobbyButtonActionsTest(
|
||||
buttonTextToClick, appContext,
|
||||
CallCompositeLobbyErrorCode.REMOVE_PARTICIPANT_OPERATION_FAILURE,
|
||||
R.string.azure_communication_ui_calling_error_lobby_failed_to_remove_participant
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnLobbyErrorMessageUIForUnknownError() = runTest {
|
||||
injectDependencies(testScheduler)
|
||||
|
||||
val buttonTextToClick = "Admit"
|
||||
|
||||
// Launch the UI.
|
||||
val appContext = joinTeamsCall()
|
||||
|
||||
lobbyButtonActionsTest(
|
||||
buttonTextToClick, appContext,
|
||||
CallCompositeLobbyErrorCode.UNKNOWN_ERROR,
|
||||
R.string.azure_communication_ui_calling_error_lobby_unknown
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnLobbyErrorMessageUIForLobbyDisabledError() = runTest {
|
||||
injectDependencies(testScheduler)
|
||||
|
||||
val buttonTextToClick = "Admit"
|
||||
|
||||
// Launch the UI.
|
||||
val appContext = joinTeamsCall()
|
||||
|
||||
lobbyButtonActionsTest(
|
||||
buttonTextToClick, appContext,
|
||||
CallCompositeLobbyErrorCode.LOBBY_DISABLED_BY_CONFIGURATIONS,
|
||||
R.string.azure_communication_ui_calling_error_lobby_disabled_by_configuration
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun lobbyButtonActionsTest(
|
||||
buttonTextToClick: String,
|
||||
appContext: Context?,
|
||||
errorCode: CallCompositeLobbyErrorCode = CallCompositeLobbyErrorCode.LOBBY_MEETING_ROLE_NOT_ALLOWED,
|
||||
errorUIId: Int = R.string.azure_communication_ui_calling_error_lobby_meeting_role_not_allowded
|
||||
|
||||
) {
|
||||
callingSDK.setParticipantRoleSharedFlow(CallCompositeInternalParticipantRole.PRESENTER)
|
||||
|
||||
callingSDK.addRemoteParticipant(
|
||||
CommunicationIdentifier.CommunicationUserIdentifier("ACS User 2"),
|
||||
displayName = "ACS User 2",
|
||||
isMuted = false,
|
||||
isSpeaking = true,
|
||||
videoStreams = listOf(MediaStreamType.VIDEO),
|
||||
state = ParticipantState.IN_LOBBY
|
||||
)
|
||||
|
||||
waitUntilDisplayed(lobbyHeaderId)
|
||||
|
||||
tapWhenDisplayed(lobbyHeaderOpenParticipantListButton)
|
||||
// one local + one remote + 2 texts (calling, lobby)
|
||||
assertViewHasChild(bottomDrawer, 4)
|
||||
|
||||
val lobbyActionResult = CompletableFuture<CallCompositeLobbyErrorCode?>()
|
||||
lobbyActionResult.complete(errorCode)
|
||||
callingSDK.setLobbyResultCompletableFuture(lobbyActionResult)
|
||||
|
||||
tapWithTextWhenDisplayed("ACS User 2")
|
||||
|
||||
tapWithTextWhenDisplayed(buttonTextToClick)
|
||||
|
||||
// to close participant list
|
||||
tapOnScreen()
|
||||
|
||||
waitUntilDisplayed(lobbyErrorHeaderId)
|
||||
|
||||
// assert error text
|
||||
assertViewText(
|
||||
lobbyErrorHeaderText,
|
||||
appContext!!.getString(errorUIId)
|
||||
)
|
||||
|
||||
// close lobby error header
|
||||
tapWhenDisplayed(lobbyErrorHeaderCloseButton)
|
||||
|
||||
// assert lobby error header is not displayed
|
||||
assertViewNotDisplayed(lobbyErrorHeaderId)
|
||||
}
|
||||
|
||||
private fun joinTeamsCall(): Context? {
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
val callComposite = CallCompositeBuilder().build()
|
||||
val communicationTokenRefreshOptions =
|
||||
CommunicationTokenRefreshOptions({ "token" }, true)
|
||||
val communicationTokenCredential =
|
||||
CommunicationTokenCredential(communicationTokenRefreshOptions)
|
||||
val remoteOptions =
|
||||
CallCompositeRemoteOptions(
|
||||
CallCompositeTeamsMeetingLinkLocator("https:teams.meeting"),
|
||||
communicationTokenCredential,
|
||||
"test"
|
||||
)
|
||||
|
||||
callComposite.launchTest(appContext, remoteOptions, null)
|
||||
|
||||
tapWhenDisplayed(joinCallId)
|
||||
waitUntilDisplayed(endCallId)
|
||||
return appContext
|
||||
}
|
||||
|
||||
private suspend fun lobbyParticipantsVisibilityTests(
|
||||
expectedParticipantCountOnFloatingHeader: Int,
|
||||
expectedParticipantCountOnGridView: Int,
|
||||
expectedParticipantCountOnParticipantList: Int,
|
||||
addLobbyUser: Boolean = true
|
||||
) {
|
||||
// Launch the UI.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
val callComposite = CallCompositeBuilder().build()
|
||||
val communicationTokenRefreshOptions =
|
||||
CommunicationTokenRefreshOptions({ "token" }, true)
|
||||
val communicationTokenCredential =
|
||||
CommunicationTokenCredential(communicationTokenRefreshOptions)
|
||||
val remoteOptions =
|
||||
CallCompositeRemoteOptions(
|
||||
CallCompositeGroupCallLocator(UUID.fromString("74fce2c1-520f-11ec-97de-71411a9a8e14")),
|
||||
communicationTokenCredential,
|
||||
"test"
|
||||
)
|
||||
|
||||
callComposite.launchTest(appContext, remoteOptions, null)
|
||||
|
||||
tapWhenDisplayed(joinCallId)
|
||||
waitUntilDisplayed(endCallId)
|
||||
|
||||
callingSDK.addRemoteParticipant(
|
||||
CommunicationIdentifier.CommunicationUserIdentifier("ACS User 1"),
|
||||
displayName = "ACS User 1",
|
||||
isMuted = false,
|
||||
isSpeaking = true,
|
||||
videoStreams = listOf(MediaStreamType.VIDEO)
|
||||
)
|
||||
|
||||
callingSDK.addRemoteParticipant(
|
||||
CommunicationIdentifier.CommunicationUserIdentifier("ACS User 2"),
|
||||
displayName = "ACS User 2",
|
||||
isMuted = false,
|
||||
isSpeaking = true,
|
||||
videoStreams = listOf(MediaStreamType.VIDEO)
|
||||
)
|
||||
|
||||
if (addLobbyUser) {
|
||||
callingSDK.addRemoteParticipant(
|
||||
CommunicationIdentifier.CommunicationUserIdentifier("Lobby State"),
|
||||
displayName = "Lobby State",
|
||||
state = ParticipantState.IN_LOBBY,
|
||||
isMuted = false,
|
||||
isSpeaking = true,
|
||||
videoStreams = listOf(MediaStreamType.VIDEO)
|
||||
)
|
||||
}
|
||||
|
||||
waitUntilDisplayed(participantContainerId)
|
||||
|
||||
assertViewText(
|
||||
participantCountId,
|
||||
"Call with $expectedParticipantCountOnFloatingHeader people"
|
||||
)
|
||||
|
||||
assertViewHasChild(participantContainerId, expectedParticipantCountOnGridView)
|
||||
|
||||
tapWhenDisplayed(participantListOpenButton)
|
||||
|
||||
// 1 local
|
||||
assertViewHasChild(bottomDrawer, expectedParticipantCountOnParticipantList + 1)
|
||||
}
|
||||
}
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
package com.azure.android.communication.ui.calling
|
||||
|
||||
import android.content.Context
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import com.azure.android.communication.BaseUiTest
|
||||
import com.azure.android.communication.assertViewHasChild
|
||||
import com.azure.android.communication.assertViewNotDisplayed
|
||||
import com.azure.android.communication.assertViewText
|
||||
import com.azure.android.communication.calling.MediaStreamType
|
||||
import com.azure.android.communication.calling.ParticipantState
|
||||
import com.azure.android.communication.common.CommunicationTokenCredential
|
||||
import com.azure.android.communication.common.CommunicationTokenRefreshOptions
|
||||
import com.azure.android.communication.tapOnScreen
|
||||
import com.azure.android.communication.tapWhenDisplayed
|
||||
import com.azure.android.communication.tapWithTextWhenDisplayed
|
||||
import com.azure.android.communication.ui.calling.implementation.R
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeGroupCallLocator
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeInternalParticipantRole
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeLobbyErrorCode
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeRemoteOptions
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeTeamsMeetingLinkLocator
|
||||
import com.azure.android.communication.ui.calling.service.sdk.CommunicationIdentifier
|
||||
import com.azure.android.communication.waitUntilDisplayed
|
||||
import java9.util.concurrent.CompletableFuture
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
import java.util.UUID
|
||||
|
||||
internal class LobbyTest : BaseUiTest() {
|
||||
@Test
|
||||
fun testOnGridViewLobbyParticipantAreNotVisible() =
|
||||
runTest {
|
||||
injectDependencies(testScheduler)
|
||||
|
||||
val expectedParticipantCountOnGridView = 2
|
||||
val expectedParticipantCountOnParticipantList = 3
|
||||
val expectedParticipantCountOnFloatingHeader = 2
|
||||
|
||||
lobbyParticipantsVisibilityTests(
|
||||
expectedParticipantCountOnFloatingHeader,
|
||||
expectedParticipantCountOnGridView,
|
||||
expectedParticipantCountOnParticipantList,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnGridViewAndParticipantListAllConnectedParticipantsAreVisible() =
|
||||
runTest {
|
||||
injectDependencies(testScheduler)
|
||||
|
||||
val expectedParticipantCountOnGridView = 2
|
||||
val expectedParticipantCountOnParticipantList = 3
|
||||
val expectedParticipantCountOnFloatingHeader = 2
|
||||
|
||||
lobbyParticipantsVisibilityTests(
|
||||
expectedParticipantCountOnFloatingHeader,
|
||||
expectedParticipantCountOnGridView,
|
||||
expectedParticipantCountOnParticipantList,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnLobbyParticipantNotDisplayedIfParticipantRoleIsAttendee() =
|
||||
runTest {
|
||||
injectDependencies(testScheduler)
|
||||
// Launch the UI.
|
||||
joinTeamsCall()
|
||||
|
||||
callingSDK.setParticipantRoleSharedFlow(CallCompositeInternalParticipantRole.ATTENDEE)
|
||||
|
||||
callingSDK.addRemoteParticipant(
|
||||
CommunicationIdentifier.CommunicationUserIdentifier("ACS User 2"),
|
||||
displayName = "ACS User 2",
|
||||
isMuted = false,
|
||||
isSpeaking = true,
|
||||
videoStreams = listOf(MediaStreamType.VIDEO),
|
||||
state = ParticipantState.IN_LOBBY,
|
||||
)
|
||||
|
||||
// assert lobby header is not displayed
|
||||
assertViewNotDisplayed(lobbyHeaderId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnLobbyParticipantAddLobbyHeaderIsDisplayed() =
|
||||
runTest {
|
||||
injectDependencies(testScheduler)
|
||||
// Launch the UI.
|
||||
val appContext = joinTeamsCall()
|
||||
|
||||
callingSDK.setParticipantRoleSharedFlow(CallCompositeInternalParticipantRole.PRESENTER)
|
||||
|
||||
callingSDK.addRemoteParticipant(
|
||||
CommunicationIdentifier.CommunicationUserIdentifier("ACS User 2"),
|
||||
displayName = "ACS User 2",
|
||||
isMuted = false,
|
||||
isSpeaking = true,
|
||||
videoStreams = listOf(MediaStreamType.VIDEO),
|
||||
state = ParticipantState.IN_LOBBY,
|
||||
)
|
||||
|
||||
waitUntilDisplayed(lobbyHeaderId)
|
||||
|
||||
// assert lobby header is displayed
|
||||
assertViewText(lobbyHeaderText, appContext!!.getString(R.string.azure_communication_ui_calling_lobby_header_text))
|
||||
|
||||
callingSDK.removeParticipant("ACS User 2")
|
||||
|
||||
// assert lobby header is not displayed
|
||||
assertViewNotDisplayed(lobbyHeaderId)
|
||||
|
||||
callingSDK.addRemoteParticipant(
|
||||
CommunicationIdentifier.CommunicationUserIdentifier("ACS User 3"),
|
||||
displayName = "ACS User 3",
|
||||
isMuted = false,
|
||||
isSpeaking = true,
|
||||
videoStreams = listOf(MediaStreamType.VIDEO),
|
||||
state = ParticipantState.IN_LOBBY,
|
||||
)
|
||||
|
||||
waitUntilDisplayed(lobbyHeaderId)
|
||||
|
||||
tapWhenDisplayed(lobbyHeaderOpenParticipantListButton)
|
||||
// one local + one remote + 2 texts (calling, lobby)
|
||||
assertViewHasChild(bottomDrawer, 4)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnLobbyHeaderCloseButtonPressLobbyHeaderIsClosed() =
|
||||
runTest {
|
||||
injectDependencies(testScheduler)
|
||||
// launch the UI.
|
||||
joinTeamsCall()
|
||||
|
||||
callingSDK.setParticipantRoleSharedFlow(CallCompositeInternalParticipantRole.PRESENTER)
|
||||
|
||||
callingSDK.addRemoteParticipant(
|
||||
CommunicationIdentifier.CommunicationUserIdentifier("ACS User 2"),
|
||||
displayName = "ACS User 2",
|
||||
isMuted = false,
|
||||
isSpeaking = true,
|
||||
videoStreams = listOf(MediaStreamType.VIDEO),
|
||||
state = ParticipantState.IN_LOBBY,
|
||||
)
|
||||
|
||||
waitUntilDisplayed(lobbyHeaderId)
|
||||
tapWhenDisplayed(lobbyHeaderCloseButton)
|
||||
assertViewNotDisplayed(lobbyHeaderId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnAdmitErrorLobbyErrorHeaderIsDisplayed() =
|
||||
runTest {
|
||||
injectDependencies(testScheduler)
|
||||
|
||||
val buttonTextToClick = "Admit"
|
||||
|
||||
// Launch the UI.
|
||||
val appContext = joinTeamsCall()
|
||||
|
||||
lobbyButtonActionsTest(buttonTextToClick, appContext)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnDeclineErrorLobbyErrorHeaderIsDisplayed() =
|
||||
runTest {
|
||||
injectDependencies(testScheduler)
|
||||
|
||||
val buttonTextToClick = "Decline"
|
||||
|
||||
// Launch the UI.
|
||||
val appContext = joinTeamsCall()
|
||||
|
||||
lobbyButtonActionsTest(buttonTextToClick, appContext)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnAdmitAllErrorLobbyErrorHeaderIsDisplayed() =
|
||||
runTest {
|
||||
injectDependencies(testScheduler)
|
||||
// Launch the UI.
|
||||
val appContext = joinTeamsCall()
|
||||
|
||||
callingSDK.setParticipantRoleSharedFlow(CallCompositeInternalParticipantRole.PRESENTER)
|
||||
|
||||
callingSDK.addRemoteParticipant(
|
||||
CommunicationIdentifier.CommunicationUserIdentifier("ACS User 2"),
|
||||
displayName = "ACS User 2",
|
||||
isMuted = false,
|
||||
isSpeaking = true,
|
||||
videoStreams = listOf(MediaStreamType.VIDEO),
|
||||
state = ParticipantState.IN_LOBBY,
|
||||
)
|
||||
|
||||
waitUntilDisplayed(lobbyHeaderId)
|
||||
|
||||
tapWhenDisplayed(lobbyHeaderOpenParticipantListButton)
|
||||
// one local + one remote + 2 texts (calling, lobby)
|
||||
assertViewHasChild(bottomDrawer, 4)
|
||||
|
||||
val lobbyActionResult = CompletableFuture<CallCompositeLobbyErrorCode?>()
|
||||
lobbyActionResult.complete(CallCompositeLobbyErrorCode.LOBBY_MEETING_ROLE_NOT_ALLOWED)
|
||||
callingSDK.setLobbyResultCompletableFuture(lobbyActionResult)
|
||||
|
||||
// click on admit all
|
||||
tapWithTextWhenDisplayed("Admit all")
|
||||
|
||||
// to close participant list
|
||||
tapOnScreen()
|
||||
|
||||
waitUntilDisplayed(lobbyErrorHeaderId)
|
||||
|
||||
// assert error text
|
||||
assertViewText(
|
||||
lobbyErrorHeaderText,
|
||||
appContext!!.getString(R.string.azure_communication_ui_calling_error_lobby_meeting_role_not_allowded),
|
||||
)
|
||||
|
||||
// close lobby error header
|
||||
tapWhenDisplayed(lobbyErrorHeaderCloseButton)
|
||||
|
||||
// assert lobby error header is not displayed
|
||||
assertViewNotDisplayed(lobbyErrorHeaderId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnAdmitAllSuccess() =
|
||||
runTest {
|
||||
injectDependencies(testScheduler)
|
||||
// Launch the UI.
|
||||
joinTeamsCall()
|
||||
|
||||
callingSDK.setParticipantRoleSharedFlow(CallCompositeInternalParticipantRole.PRESENTER)
|
||||
|
||||
callingSDK.addRemoteParticipant(
|
||||
CommunicationIdentifier.CommunicationUserIdentifier("ACS User 2"),
|
||||
displayName = "ACS User 2",
|
||||
isMuted = false,
|
||||
isSpeaking = true,
|
||||
videoStreams = listOf(MediaStreamType.VIDEO),
|
||||
state = ParticipantState.IN_LOBBY,
|
||||
)
|
||||
|
||||
waitUntilDisplayed(lobbyHeaderId)
|
||||
|
||||
tapWhenDisplayed(lobbyHeaderOpenParticipantListButton)
|
||||
// one local + one remote + 2 texts (calling, lobby)
|
||||
assertViewHasChild(bottomDrawer, 4)
|
||||
|
||||
val lobbyActionResult = CompletableFuture<CallCompositeLobbyErrorCode?>()
|
||||
callingSDK.setLobbyResultCompletableFuture(lobbyActionResult)
|
||||
|
||||
// click on admit all
|
||||
tapWithTextWhenDisplayed("Admit all")
|
||||
|
||||
// to close participant list
|
||||
tapOnScreen()
|
||||
|
||||
callingSDK.changeParticipantState("ACS User 2", ParticipantState.CONNECTED)
|
||||
lobbyActionResult.complete(null)
|
||||
|
||||
// assert lobby error header is not displayed
|
||||
assertViewNotDisplayed(lobbyErrorHeaderId)
|
||||
|
||||
// tap on screen
|
||||
tapOnScreen()
|
||||
tapWhenDisplayed(participantListOpenButton)
|
||||
|
||||
// one local + one remote + 1 texts (calling)
|
||||
assertViewHasChild(bottomDrawer, 3)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnLobbyErrorMessageUIForRoleNotPermitted() =
|
||||
runTest {
|
||||
injectDependencies(testScheduler)
|
||||
|
||||
val buttonTextToClick = "Admit"
|
||||
|
||||
// Launch the UI.
|
||||
val appContext = joinTeamsCall()
|
||||
|
||||
lobbyButtonActionsTest(
|
||||
buttonTextToClick,
|
||||
appContext,
|
||||
CallCompositeLobbyErrorCode.LOBBY_MEETING_ROLE_NOT_ALLOWED,
|
||||
R.string.azure_communication_ui_calling_error_lobby_meeting_role_not_allowded,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnLobbyErrorMessageUIForMeetingTypeNotSupported() =
|
||||
runTest {
|
||||
injectDependencies(testScheduler)
|
||||
|
||||
val buttonTextToClick = "Admit"
|
||||
|
||||
// Launch the UI.
|
||||
val appContext = joinTeamsCall()
|
||||
|
||||
lobbyButtonActionsTest(
|
||||
buttonTextToClick,
|
||||
appContext,
|
||||
CallCompositeLobbyErrorCode.LOBBY_CONVERSATION_TYPE_NOT_SUPPORTED,
|
||||
R.string.azure_communication_ui_calling_error_lobby_conversation_type_not_supported,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnLobbyErrorMessageUIForFailedToRemove() =
|
||||
runTest {
|
||||
injectDependencies(testScheduler)
|
||||
|
||||
val buttonTextToClick = "Admit"
|
||||
|
||||
// Launch the UI.
|
||||
val appContext = joinTeamsCall()
|
||||
|
||||
lobbyButtonActionsTest(
|
||||
buttonTextToClick,
|
||||
appContext,
|
||||
CallCompositeLobbyErrorCode.REMOVE_PARTICIPANT_OPERATION_FAILURE,
|
||||
R.string.azure_communication_ui_calling_error_lobby_failed_to_remove_participant,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnLobbyErrorMessageUIForUnknownError() =
|
||||
runTest {
|
||||
injectDependencies(testScheduler)
|
||||
|
||||
val buttonTextToClick = "Admit"
|
||||
|
||||
// Launch the UI.
|
||||
val appContext = joinTeamsCall()
|
||||
|
||||
lobbyButtonActionsTest(
|
||||
buttonTextToClick,
|
||||
appContext,
|
||||
CallCompositeLobbyErrorCode.UNKNOWN_ERROR,
|
||||
R.string.azure_communication_ui_calling_error_lobby_unknown,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnLobbyErrorMessageUIForLobbyDisabledError() =
|
||||
runTest {
|
||||
injectDependencies(testScheduler)
|
||||
|
||||
val buttonTextToClick = "Admit"
|
||||
|
||||
// Launch the UI.
|
||||
val appContext = joinTeamsCall()
|
||||
|
||||
lobbyButtonActionsTest(
|
||||
buttonTextToClick,
|
||||
appContext,
|
||||
CallCompositeLobbyErrorCode.LOBBY_DISABLED_BY_CONFIGURATIONS,
|
||||
R.string.azure_communication_ui_calling_error_lobby_disabled_by_configuration,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun lobbyButtonActionsTest(
|
||||
buttonTextToClick: String,
|
||||
appContext: Context?,
|
||||
errorCode: CallCompositeLobbyErrorCode = CallCompositeLobbyErrorCode.LOBBY_MEETING_ROLE_NOT_ALLOWED,
|
||||
errorUIId: Int = R.string.azure_communication_ui_calling_error_lobby_meeting_role_not_allowded,
|
||||
) {
|
||||
callingSDK.setParticipantRoleSharedFlow(CallCompositeInternalParticipantRole.PRESENTER)
|
||||
|
||||
callingSDK.addRemoteParticipant(
|
||||
CommunicationIdentifier.CommunicationUserIdentifier("ACS User 2"),
|
||||
displayName = "ACS User 2",
|
||||
isMuted = false,
|
||||
isSpeaking = true,
|
||||
videoStreams = listOf(MediaStreamType.VIDEO),
|
||||
state = ParticipantState.IN_LOBBY,
|
||||
)
|
||||
|
||||
waitUntilDisplayed(lobbyHeaderId)
|
||||
|
||||
tapWhenDisplayed(lobbyHeaderOpenParticipantListButton)
|
||||
// one local + one remote + 2 texts (calling, lobby)
|
||||
assertViewHasChild(bottomDrawer, 4)
|
||||
|
||||
val lobbyActionResult = CompletableFuture<CallCompositeLobbyErrorCode?>()
|
||||
lobbyActionResult.complete(errorCode)
|
||||
callingSDK.setLobbyResultCompletableFuture(lobbyActionResult)
|
||||
|
||||
tapWithTextWhenDisplayed("ACS User 2")
|
||||
|
||||
tapWithTextWhenDisplayed(buttonTextToClick)
|
||||
|
||||
// to close participant list
|
||||
tapOnScreen()
|
||||
|
||||
waitUntilDisplayed(lobbyErrorHeaderId)
|
||||
|
||||
// assert error text
|
||||
assertViewText(
|
||||
lobbyErrorHeaderText,
|
||||
appContext!!.getString(errorUIId),
|
||||
)
|
||||
|
||||
// close lobby error header
|
||||
tapWhenDisplayed(lobbyErrorHeaderCloseButton)
|
||||
|
||||
// assert lobby error header is not displayed
|
||||
assertViewNotDisplayed(lobbyErrorHeaderId)
|
||||
}
|
||||
|
||||
private fun joinTeamsCall(): Context? {
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
val callComposite = CallCompositeBuilder().build()
|
||||
val communicationTokenRefreshOptions =
|
||||
CommunicationTokenRefreshOptions({ "token" }, true)
|
||||
val communicationTokenCredential =
|
||||
CommunicationTokenCredential(communicationTokenRefreshOptions)
|
||||
val remoteOptions =
|
||||
CallCompositeRemoteOptions(
|
||||
CallCompositeTeamsMeetingLinkLocator("https:teams.meeting"),
|
||||
communicationTokenCredential,
|
||||
"test",
|
||||
)
|
||||
|
||||
callComposite.launchTest(appContext, remoteOptions, null)
|
||||
|
||||
tapWhenDisplayed(joinCallId)
|
||||
waitUntilDisplayed(endCallId)
|
||||
return appContext
|
||||
}
|
||||
|
||||
private suspend fun lobbyParticipantsVisibilityTests(
|
||||
expectedParticipantCountOnFloatingHeader: Int,
|
||||
expectedParticipantCountOnGridView: Int,
|
||||
expectedParticipantCountOnParticipantList: Int,
|
||||
addLobbyUser: Boolean = true,
|
||||
) {
|
||||
// Launch the UI.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
val callComposite = CallCompositeBuilder().build()
|
||||
val communicationTokenRefreshOptions =
|
||||
CommunicationTokenRefreshOptions({ "token" }, true)
|
||||
val communicationTokenCredential =
|
||||
CommunicationTokenCredential(communicationTokenRefreshOptions)
|
||||
val remoteOptions =
|
||||
CallCompositeRemoteOptions(
|
||||
CallCompositeGroupCallLocator(UUID.fromString("74fce2c1-520f-11ec-97de-71411a9a8e14")),
|
||||
communicationTokenCredential,
|
||||
"test",
|
||||
)
|
||||
|
||||
callComposite.launchTest(appContext, remoteOptions, null)
|
||||
|
||||
tapWhenDisplayed(joinCallId)
|
||||
waitUntilDisplayed(endCallId)
|
||||
|
||||
callingSDK.addRemoteParticipant(
|
||||
CommunicationIdentifier.CommunicationUserIdentifier("ACS User 1"),
|
||||
displayName = "ACS User 1",
|
||||
isMuted = false,
|
||||
isSpeaking = true,
|
||||
videoStreams = listOf(MediaStreamType.VIDEO),
|
||||
)
|
||||
|
||||
callingSDK.addRemoteParticipant(
|
||||
CommunicationIdentifier.CommunicationUserIdentifier("ACS User 2"),
|
||||
displayName = "ACS User 2",
|
||||
isMuted = false,
|
||||
isSpeaking = true,
|
||||
videoStreams = listOf(MediaStreamType.VIDEO),
|
||||
)
|
||||
|
||||
if (addLobbyUser) {
|
||||
callingSDK.addRemoteParticipant(
|
||||
CommunicationIdentifier.CommunicationUserIdentifier("Lobby State"),
|
||||
displayName = "Lobby State",
|
||||
state = ParticipantState.IN_LOBBY,
|
||||
isMuted = false,
|
||||
isSpeaking = true,
|
||||
videoStreams = listOf(MediaStreamType.VIDEO),
|
||||
)
|
||||
}
|
||||
|
||||
waitUntilDisplayed(participantContainerId)
|
||||
|
||||
assertViewText(
|
||||
participantCountId,
|
||||
"Call with $expectedParticipantCountOnFloatingHeader people",
|
||||
)
|
||||
|
||||
assertViewHasChild(participantContainerId, expectedParticipantCountOnGridView)
|
||||
|
||||
tapWhenDisplayed(participantListOpenButton)
|
||||
|
||||
// 1 local
|
||||
assertViewHasChild(bottomDrawer, expectedParticipantCountOnParticipantList + 1)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,117 +5,120 @@ package com.azure.android.communication.ui.calling
|
|||
|
||||
import com.azure.android.communication.BaseUiTest
|
||||
import com.azure.android.communication.assertViewText
|
||||
import org.junit.Test
|
||||
import com.azure.android.communication.calling.MediaStreamType
|
||||
import com.azure.android.communication.tapWhenDisplayed
|
||||
import com.azure.android.communication.ui.calling.service.sdk.CommunicationIdentifier
|
||||
import com.azure.android.communication.waitUntilDisplayed
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
internal class RemoteParticipantCounterTest : BaseUiTest() {
|
||||
@Test
|
||||
fun testInitiallyPopulatedCallDisplaysCorrectParticipantCounter() = runTest {
|
||||
injectDependencies(testScheduler)
|
||||
fun testInitiallyPopulatedCallDisplaysCorrectParticipantCounter() =
|
||||
runTest {
|
||||
injectDependencies(testScheduler)
|
||||
|
||||
// Add participants that are already present on the call before we join it.
|
||||
callingSDK.addRemoteParticipant(
|
||||
CommunicationIdentifier.MicrosoftTeamsUserIdentifier("Teams User 1", true),
|
||||
displayName = "Teams User 1"
|
||||
)
|
||||
callingSDK.addRemoteParticipant(
|
||||
CommunicationIdentifier.MicrosoftTeamsUserIdentifier("Teams User 2", false),
|
||||
displayName = "Teams User 2",
|
||||
videoStreams = listOf(MediaStreamType.VIDEO)
|
||||
)
|
||||
callingSDK.addRemoteParticipant(
|
||||
CommunicationIdentifier.PhoneNumberIdentifier("16047891234"),
|
||||
displayName = "16047891234"
|
||||
)
|
||||
callingSDK.addRemoteParticipant(
|
||||
CommunicationIdentifier.CommunicationUserIdentifier("ACS User 1"),
|
||||
displayName = "ACS User 1",
|
||||
videoStreams = listOf(MediaStreamType.SCREEN_SHARING)
|
||||
)
|
||||
|
||||
// Launch the UI.
|
||||
launchComposite()
|
||||
tapWhenDisplayed(joinCallId)
|
||||
waitUntilDisplayed(endCallId)
|
||||
|
||||
// Verify we're displaying correct participant count.
|
||||
assertViewText(participantCountId, "Call with 4 people")
|
||||
|
||||
// One of the remote participants drops from the call.
|
||||
callingSDK.removeParticipant("ACS User 1")
|
||||
|
||||
// Verify we're displaying a reduced participant count.
|
||||
assertViewText(participantCountId, "Call with 3 people")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInitiallyEmptyCallDisplaysCorrectParticipantCounter() = runTest {
|
||||
injectDependencies(testScheduler)
|
||||
|
||||
// Launch the UI.
|
||||
launchComposite()
|
||||
tapWhenDisplayed(joinCallId)
|
||||
waitUntilDisplayed(endCallId)
|
||||
|
||||
// Initial label with no remote participants present.
|
||||
assertViewText(participantCountId, "Waiting for others to join")
|
||||
|
||||
// Add participants that are already present on the call before we join it.
|
||||
callingSDK.addRemoteParticipant(
|
||||
CommunicationIdentifier.MicrosoftTeamsUserIdentifier("Teams User 1", true),
|
||||
displayName = "Teams User 1"
|
||||
)
|
||||
|
||||
callingSDK.addRemoteParticipant(
|
||||
CommunicationIdentifier.MicrosoftTeamsUserIdentifier("Teams User 2", false),
|
||||
displayName = "Teams User 2",
|
||||
videoStreams = listOf(MediaStreamType.VIDEO)
|
||||
)
|
||||
|
||||
// Verify we're displaying correct participant count.
|
||||
assertViewText(participantCountId, "Call with 2 people")
|
||||
|
||||
// One of the remote participants drops from the call.
|
||||
callingSDK.removeParticipant("Teams User 1")
|
||||
|
||||
// Verify we're displaying a reduced participant count.
|
||||
assertViewText(participantCountId, "Call with 1 person")
|
||||
|
||||
// Back to where we started, no participants.
|
||||
callingSDK.removeParticipant("Teams User 2")
|
||||
assertViewText(participantCountId, "Waiting for others to join")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLargeNumberOfParticipants() = runTest {
|
||||
injectDependencies(testScheduler)
|
||||
|
||||
// Launch the UI.
|
||||
launchComposite()
|
||||
tapWhenDisplayed(joinCallId)
|
||||
waitUntilDisplayed(endCallId)
|
||||
|
||||
val maxNumberOfPeople = 200
|
||||
|
||||
// Add a bunch of remote participants into the call.
|
||||
repeat(maxNumberOfPeople) { id ->
|
||||
// Add participants that are already present on the call before we join it.
|
||||
callingSDK.addRemoteParticipant(
|
||||
CommunicationIdentifier.CommunicationUserIdentifier("ACS User $id"),
|
||||
displayName = "ACS User $id",
|
||||
videoStreams = listOf(MediaStreamType.VIDEO)
|
||||
CommunicationIdentifier.MicrosoftTeamsUserIdentifier("Teams User 1", true),
|
||||
displayName = "Teams User 1",
|
||||
)
|
||||
callingSDK.addRemoteParticipant(
|
||||
CommunicationIdentifier.MicrosoftTeamsUserIdentifier("Teams User 2", false),
|
||||
displayName = "Teams User 2",
|
||||
videoStreams = listOf(MediaStreamType.VIDEO),
|
||||
)
|
||||
callingSDK.addRemoteParticipant(
|
||||
CommunicationIdentifier.PhoneNumberIdentifier("16047891234"),
|
||||
displayName = "16047891234",
|
||||
)
|
||||
callingSDK.addRemoteParticipant(
|
||||
CommunicationIdentifier.CommunicationUserIdentifier("ACS User 1"),
|
||||
displayName = "ACS User 1",
|
||||
videoStreams = listOf(MediaStreamType.SCREEN_SHARING),
|
||||
)
|
||||
|
||||
if (id == 0) {
|
||||
assertViewText(participantCountId, "Call with 1 person")
|
||||
} else {
|
||||
assertViewText(participantCountId, "Call with ${id + 1} people")
|
||||
}
|
||||
// Launch the UI.
|
||||
launchComposite()
|
||||
tapWhenDisplayed(joinCallId)
|
||||
waitUntilDisplayed(endCallId)
|
||||
|
||||
// Verify we're displaying correct participant count.
|
||||
assertViewText(participantCountId, "Call with 4 people")
|
||||
|
||||
// One of the remote participants drops from the call.
|
||||
callingSDK.removeParticipant("ACS User 1")
|
||||
|
||||
// Verify we're displaying a reduced participant count.
|
||||
assertViewText(participantCountId, "Call with 3 people")
|
||||
}
|
||||
|
||||
assertViewText(participantCountId, "Call with $maxNumberOfPeople people")
|
||||
}
|
||||
@Test
|
||||
fun testInitiallyEmptyCallDisplaysCorrectParticipantCounter() =
|
||||
runTest {
|
||||
injectDependencies(testScheduler)
|
||||
|
||||
// Launch the UI.
|
||||
launchComposite()
|
||||
tapWhenDisplayed(joinCallId)
|
||||
waitUntilDisplayed(endCallId)
|
||||
|
||||
// Initial label with no remote participants present.
|
||||
assertViewText(participantCountId, "Waiting for others to join")
|
||||
|
||||
// Add participants that are already present on the call before we join it.
|
||||
callingSDK.addRemoteParticipant(
|
||||
CommunicationIdentifier.MicrosoftTeamsUserIdentifier("Teams User 1", true),
|
||||
displayName = "Teams User 1",
|
||||
)
|
||||
|
||||
callingSDK.addRemoteParticipant(
|
||||
CommunicationIdentifier.MicrosoftTeamsUserIdentifier("Teams User 2", false),
|
||||
displayName = "Teams User 2",
|
||||
videoStreams = listOf(MediaStreamType.VIDEO),
|
||||
)
|
||||
|
||||
// Verify we're displaying correct participant count.
|
||||
assertViewText(participantCountId, "Call with 2 people")
|
||||
|
||||
// One of the remote participants drops from the call.
|
||||
callingSDK.removeParticipant("Teams User 1")
|
||||
|
||||
// Verify we're displaying a reduced participant count.
|
||||
assertViewText(participantCountId, "Call with 1 person")
|
||||
|
||||
// Back to where we started, no participants.
|
||||
callingSDK.removeParticipant("Teams User 2")
|
||||
assertViewText(participantCountId, "Waiting for others to join")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLargeNumberOfParticipants() =
|
||||
runTest {
|
||||
injectDependencies(testScheduler)
|
||||
|
||||
// Launch the UI.
|
||||
launchComposite()
|
||||
tapWhenDisplayed(joinCallId)
|
||||
waitUntilDisplayed(endCallId)
|
||||
|
||||
val maxNumberOfPeople = 200
|
||||
|
||||
// Add a bunch of remote participants into the call.
|
||||
repeat(maxNumberOfPeople) { id ->
|
||||
callingSDK.addRemoteParticipant(
|
||||
CommunicationIdentifier.CommunicationUserIdentifier("ACS User $id"),
|
||||
displayName = "ACS User $id",
|
||||
videoStreams = listOf(MediaStreamType.VIDEO),
|
||||
)
|
||||
|
||||
if (id == 0) {
|
||||
assertViewText(participantCountId, "Call with 1 person")
|
||||
} else {
|
||||
assertViewText(participantCountId, "Call with ${id + 1} people")
|
||||
}
|
||||
}
|
||||
|
||||
assertViewText(participantCountId, "Call with $maxNumberOfPeople people")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,54 +14,55 @@ import org.junit.Test
|
|||
|
||||
internal class RemoteParticipantEventsTest : BaseUiTest() {
|
||||
@Test
|
||||
fun testRemoteParticipantBasicEvents() = runTest {
|
||||
injectDependencies(testScheduler)
|
||||
fun testRemoteParticipantBasicEvents() =
|
||||
runTest {
|
||||
injectDependencies(testScheduler)
|
||||
|
||||
// A demonstration of how to invoke remote participant events.
|
||||
// A demonstration of how to invoke remote participant events.
|
||||
|
||||
// Launch the UI.
|
||||
launchComposite()
|
||||
tapWhenDisplayed(joinCallId)
|
||||
waitUntilDisplayed(endCallId)
|
||||
// Launch the UI.
|
||||
launchComposite()
|
||||
tapWhenDisplayed(joinCallId)
|
||||
waitUntilDisplayed(endCallId)
|
||||
|
||||
"ACS User 1".also { userId ->
|
||||
callingSDK.addRemoteParticipant(
|
||||
CommunicationIdentifier.CommunicationUserIdentifier(userId),
|
||||
displayName = userId,
|
||||
isMuted = true,
|
||||
isSpeaking = false,
|
||||
videoStreams = listOf(MediaStreamType.VIDEO),
|
||||
)
|
||||
|
||||
// todo assertions
|
||||
// verify muted icon is displayed
|
||||
|
||||
callingSDK.changeParticipant(
|
||||
userId,
|
||||
isMuted = false,
|
||||
isSpeaking = true,
|
||||
)
|
||||
|
||||
// verify muted icon is absent
|
||||
// verify isSpeaking frame
|
||||
|
||||
callingSDK.changeParticipant(
|
||||
userId,
|
||||
state = ParticipantState.DISCONNECTED,
|
||||
)
|
||||
|
||||
// verify participant state change
|
||||
}
|
||||
|
||||
"ACS User 1".also { userId ->
|
||||
callingSDK.addRemoteParticipant(
|
||||
CommunicationIdentifier.CommunicationUserIdentifier(userId),
|
||||
displayName = userId,
|
||||
isMuted = true,
|
||||
isSpeaking = false,
|
||||
videoStreams = listOf(MediaStreamType.VIDEO)
|
||||
)
|
||||
|
||||
// todo assertions
|
||||
// verify muted icon is displayed
|
||||
|
||||
callingSDK.changeParticipant(
|
||||
userId,
|
||||
CommunicationIdentifier.CommunicationUserIdentifier("ACS User 2"),
|
||||
displayName = "ACS User 2",
|
||||
isMuted = false,
|
||||
isSpeaking = true,
|
||||
videoStreams = listOf(MediaStreamType.SCREEN_SHARING),
|
||||
)
|
||||
|
||||
// verify muted icon is absent
|
||||
// verify main speaker switched
|
||||
// verify isSpeaking frame
|
||||
|
||||
callingSDK.changeParticipant(
|
||||
userId,
|
||||
state = ParticipantState.DISCONNECTED
|
||||
)
|
||||
|
||||
// verify participant state change
|
||||
}
|
||||
|
||||
callingSDK.addRemoteParticipant(
|
||||
CommunicationIdentifier.CommunicationUserIdentifier("ACS User 2"),
|
||||
displayName = "ACS User 2",
|
||||
isMuted = false,
|
||||
isSpeaking = true,
|
||||
videoStreams = listOf(MediaStreamType.SCREEN_SHARING)
|
||||
)
|
||||
|
||||
// verify main speaker switched
|
||||
// verify isSpeaking frame
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,94 +1,95 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
package com.azure.android.communication.ui.calling
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import com.azure.android.communication.BaseUiTest
|
||||
import com.azure.android.communication.common.CommunicationTokenCredential
|
||||
import com.azure.android.communication.common.CommunicationTokenRefreshOptions
|
||||
import com.azure.android.communication.tapOnText
|
||||
import com.azure.android.communication.tapWhenDisplayed
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeGroupCallLocator
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeRemoteOptions
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeUserReportedIssueEvent
|
||||
import com.azure.android.communication.waitUntilDisplayed
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
import java.util.UUID
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.action.ViewActions
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
import com.azure.android.communication.assertTextNotDisplayed
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
internal class SupportFormTest : BaseUiTest() {
|
||||
|
||||
@Test
|
||||
fun testSupportFormIsDisplayedAndSendsEvent() = runTest {
|
||||
injectDependencies(testScheduler)
|
||||
// Launch the UI.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
val callComposite = CallCompositeBuilder().build()
|
||||
val communicationTokenRefreshOptions =
|
||||
CommunicationTokenRefreshOptions({ "token" }, true)
|
||||
val communicationTokenCredential =
|
||||
CommunicationTokenCredential(communicationTokenRefreshOptions)
|
||||
val remoteOptions =
|
||||
CallCompositeRemoteOptions(
|
||||
CallCompositeGroupCallLocator(UUID.fromString("74fce2c1-520f-11ec-97de-71411a9a8e14")),
|
||||
communicationTokenCredential,
|
||||
"test"
|
||||
)
|
||||
|
||||
var event: CallCompositeUserReportedIssueEvent? = null
|
||||
|
||||
callComposite.addOnUserReportedEventHandler {
|
||||
event = it
|
||||
}
|
||||
|
||||
callComposite.launchTest(appContext, remoteOptions, null)
|
||||
|
||||
tapWhenDisplayed(joinCallId)
|
||||
waitUntilDisplayed(endCallId)
|
||||
tapWhenDisplayed(moreOptionsId)
|
||||
tapOnText(showSupportFormTextId)
|
||||
waitUntilDisplayed(userMessageEditTextId)
|
||||
|
||||
val testMessage = "Test support message"
|
||||
onView(withId(userMessageEditTextId))
|
||||
.perform(ViewActions.typeText(testMessage))
|
||||
|
||||
tapWhenDisplayed(sendButtonId)
|
||||
|
||||
assertNotNull(event)
|
||||
assertEquals(testMessage, event?.userMessage)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSupportFormIsNotDisplayedWhenNoHandler() = runTest {
|
||||
injectDependencies(testScheduler)
|
||||
// Launch the UI.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
val callComposite = CallCompositeBuilder().build()
|
||||
val communicationTokenRefreshOptions =
|
||||
CommunicationTokenRefreshOptions({ "token" }, true)
|
||||
val communicationTokenCredential =
|
||||
CommunicationTokenCredential(communicationTokenRefreshOptions)
|
||||
val remoteOptions =
|
||||
CallCompositeRemoteOptions(
|
||||
CallCompositeGroupCallLocator(UUID.fromString("74fce2c1-520f-11ec-97de-71411a9a8e14")),
|
||||
communicationTokenCredential,
|
||||
"test"
|
||||
)
|
||||
|
||||
callComposite.launchTest(appContext, remoteOptions, null)
|
||||
|
||||
tapWhenDisplayed(joinCallId)
|
||||
waitUntilDisplayed(endCallId)
|
||||
tapWhenDisplayed(moreOptionsId)
|
||||
assertTextNotDisplayed(showSupportFormTextId)
|
||||
}
|
||||
}
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
package com.azure.android.communication.ui.calling
|
||||
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.action.ViewActions
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import com.azure.android.communication.BaseUiTest
|
||||
import com.azure.android.communication.assertTextNotDisplayed
|
||||
import com.azure.android.communication.common.CommunicationTokenCredential
|
||||
import com.azure.android.communication.common.CommunicationTokenRefreshOptions
|
||||
import com.azure.android.communication.tapOnText
|
||||
import com.azure.android.communication.tapWhenDisplayed
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeGroupCallLocator
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeRemoteOptions
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeUserReportedIssueEvent
|
||||
import com.azure.android.communication.waitUntilDisplayed
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Test
|
||||
import java.util.UUID
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
internal class SupportFormTest : BaseUiTest() {
|
||||
@Test
|
||||
fun testSupportFormIsDisplayedAndSendsEvent() =
|
||||
runTest {
|
||||
injectDependencies(testScheduler)
|
||||
// Launch the UI.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
val callComposite = CallCompositeBuilder().build()
|
||||
val communicationTokenRefreshOptions =
|
||||
CommunicationTokenRefreshOptions({ "token" }, true)
|
||||
val communicationTokenCredential =
|
||||
CommunicationTokenCredential(communicationTokenRefreshOptions)
|
||||
val remoteOptions =
|
||||
CallCompositeRemoteOptions(
|
||||
CallCompositeGroupCallLocator(UUID.fromString("74fce2c1-520f-11ec-97de-71411a9a8e14")),
|
||||
communicationTokenCredential,
|
||||
"test",
|
||||
)
|
||||
|
||||
var event: CallCompositeUserReportedIssueEvent? = null
|
||||
|
||||
callComposite.addOnUserReportedEventHandler {
|
||||
event = it
|
||||
}
|
||||
|
||||
callComposite.launchTest(appContext, remoteOptions, null)
|
||||
|
||||
tapWhenDisplayed(joinCallId)
|
||||
waitUntilDisplayed(endCallId)
|
||||
tapWhenDisplayed(moreOptionsId)
|
||||
tapOnText(showSupportFormTextId)
|
||||
waitUntilDisplayed(userMessageEditTextId)
|
||||
|
||||
val testMessage = "Test support message"
|
||||
onView(withId(userMessageEditTextId))
|
||||
.perform(ViewActions.typeText(testMessage))
|
||||
|
||||
tapWhenDisplayed(sendButtonId)
|
||||
|
||||
assertNotNull(event)
|
||||
assertEquals(testMessage, event?.userMessage)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSupportFormIsNotDisplayedWhenNoHandler() =
|
||||
runTest {
|
||||
injectDependencies(testScheduler)
|
||||
// Launch the UI.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
val callComposite = CallCompositeBuilder().build()
|
||||
val communicationTokenRefreshOptions =
|
||||
CommunicationTokenRefreshOptions({ "token" }, true)
|
||||
val communicationTokenCredential =
|
||||
CommunicationTokenCredential(communicationTokenRefreshOptions)
|
||||
val remoteOptions =
|
||||
CallCompositeRemoteOptions(
|
||||
CallCompositeGroupCallLocator(UUID.fromString("74fce2c1-520f-11ec-97de-71411a9a8e14")),
|
||||
communicationTokenCredential,
|
||||
"test",
|
||||
)
|
||||
|
||||
callComposite.launchTest(appContext, remoteOptions, null)
|
||||
|
||||
tapWhenDisplayed(joinCallId)
|
||||
waitUntilDisplayed(endCallId)
|
||||
tapWhenDisplayed(moreOptionsId)
|
||||
assertTextNotDisplayed(showSupportFormTextId)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,224 +7,232 @@ import com.azure.android.communication.BaseUiTest
|
|||
import com.azure.android.communication.assertDisplayed
|
||||
import com.azure.android.communication.assertViewGone
|
||||
import com.azure.android.communication.assertViewText
|
||||
import com.azure.android.communication.ui.calling.implementation.R
|
||||
import com.azure.android.communication.tapWhenDisplayed
|
||||
import com.azure.android.communication.ui.calling.implementation.R
|
||||
import com.azure.android.communication.waitUntilDisplayed
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
internal class ToastNotificationTest : BaseUiTest() {
|
||||
@Test
|
||||
fun testShowLowNetworkReceiveQualityToastNotification() = runTest {
|
||||
injectDependencies(testScheduler)
|
||||
fun testShowLowNetworkReceiveQualityToastNotification() =
|
||||
runTest {
|
||||
injectDependencies(testScheduler)
|
||||
|
||||
// Launch the UI.
|
||||
launchComposite()
|
||||
tapWhenDisplayed(joinCallId)
|
||||
waitUntilDisplayed(endCallId)
|
||||
// Launch the UI.
|
||||
launchComposite()
|
||||
tapWhenDisplayed(joinCallId)
|
||||
waitUntilDisplayed(endCallId)
|
||||
|
||||
callingSDK.setLowNetworkRecieveQuality(true)
|
||||
callingSDK.setLowNetworkRecieveQuality(true)
|
||||
|
||||
// Check that the toast notification appeared
|
||||
waitUntilDisplayed(toastNotificationId)
|
||||
// Check that the toast notification appeared
|
||||
waitUntilDisplayed(toastNotificationId)
|
||||
|
||||
// Assert toast notification appears with correct text
|
||||
assertDisplayed(toastNotificationId)
|
||||
assertDisplayed(toastNotificationIconId)
|
||||
assertViewText(toastNotificationMessageId, R.string.azure_communication_ui_calling_diagnostics_network_quality_low)
|
||||
// Assert toast notification appears with correct text
|
||||
assertDisplayed(toastNotificationId)
|
||||
assertDisplayed(toastNotificationIconId)
|
||||
assertViewText(toastNotificationMessageId, R.string.azure_communication_ui_calling_diagnostics_network_quality_low)
|
||||
|
||||
callingSDK.setLowNetworkRecieveQuality(false)
|
||||
callingSDK.setLowNetworkRecieveQuality(false)
|
||||
|
||||
assertViewGone(toastNotificationId)
|
||||
assertViewGone(toastNotificationIconId)
|
||||
}
|
||||
assertViewGone(toastNotificationId)
|
||||
assertViewGone(toastNotificationIconId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testShowLowNetworkSendQualityToastNotification() = runTest {
|
||||
injectDependencies(testScheduler)
|
||||
fun testShowLowNetworkSendQualityToastNotification() =
|
||||
runTest {
|
||||
injectDependencies(testScheduler)
|
||||
|
||||
// Launch the UI.
|
||||
launchComposite()
|
||||
tapWhenDisplayed(joinCallId)
|
||||
waitUntilDisplayed(endCallId)
|
||||
// Launch the UI.
|
||||
launchComposite()
|
||||
tapWhenDisplayed(joinCallId)
|
||||
waitUntilDisplayed(endCallId)
|
||||
|
||||
callingSDK.setLowNetworkSendQuality(true)
|
||||
callingSDK.setLowNetworkSendQuality(true)
|
||||
|
||||
// Check that the toast notification appeared
|
||||
waitUntilDisplayed(toastNotificationId)
|
||||
// Check that the toast notification appeared
|
||||
waitUntilDisplayed(toastNotificationId)
|
||||
|
||||
// Assert toast notification appears with correct text
|
||||
assertDisplayed(toastNotificationId)
|
||||
assertDisplayed(toastNotificationIconId)
|
||||
assertViewText(toastNotificationMessageId, R.string.azure_communication_ui_calling_diagnostics_network_quality_low)
|
||||
// Assert toast notification appears with correct text
|
||||
assertDisplayed(toastNotificationId)
|
||||
assertDisplayed(toastNotificationIconId)
|
||||
assertViewText(toastNotificationMessageId, R.string.azure_communication_ui_calling_diagnostics_network_quality_low)
|
||||
|
||||
callingSDK.setLowNetworkSendQuality(false)
|
||||
callingSDK.setLowNetworkSendQuality(false)
|
||||
|
||||
assertViewGone(toastNotificationId)
|
||||
assertViewGone(toastNotificationIconId)
|
||||
}
|
||||
assertViewGone(toastNotificationId)
|
||||
assertViewGone(toastNotificationIconId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testShowLowNetworkReconnectionQualityToastNotification() = runTest {
|
||||
injectDependencies(testScheduler)
|
||||
fun testShowLowNetworkReconnectionQualityToastNotification() =
|
||||
runTest {
|
||||
injectDependencies(testScheduler)
|
||||
|
||||
// Launch the UI.
|
||||
launchComposite()
|
||||
tapWhenDisplayed(joinCallId)
|
||||
waitUntilDisplayed(endCallId)
|
||||
// Launch the UI.
|
||||
launchComposite()
|
||||
tapWhenDisplayed(joinCallId)
|
||||
waitUntilDisplayed(endCallId)
|
||||
|
||||
callingSDK.setLowNetworkReconnectionQuality(true)
|
||||
callingSDK.setLowNetworkReconnectionQuality(true)
|
||||
|
||||
// Check that the toast notification appeared
|
||||
waitUntilDisplayed(toastNotificationId)
|
||||
// Check that the toast notification appeared
|
||||
waitUntilDisplayed(toastNotificationId)
|
||||
|
||||
// Assert toast notification appears with correct text
|
||||
assertDisplayed(toastNotificationId)
|
||||
assertDisplayed(toastNotificationIconId)
|
||||
assertViewText(toastNotificationMessageId, R.string.azure_communication_ui_calling_diagnostics_network_reconnecting)
|
||||
// Assert toast notification appears with correct text
|
||||
assertDisplayed(toastNotificationId)
|
||||
assertDisplayed(toastNotificationIconId)
|
||||
assertViewText(toastNotificationMessageId, R.string.azure_communication_ui_calling_diagnostics_network_reconnecting)
|
||||
|
||||
// Stop speaking while muted
|
||||
callingSDK.setLowNetworkReconnectionQuality(false)
|
||||
// Stop speaking while muted
|
||||
callingSDK.setLowNetworkReconnectionQuality(false)
|
||||
|
||||
assertViewGone(toastNotificationId)
|
||||
assertViewGone(toastNotificationIconId)
|
||||
}
|
||||
assertViewGone(toastNotificationId)
|
||||
assertViewGone(toastNotificationIconId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testShowNetworkUnavailableToastNotification() = runTest {
|
||||
injectDependencies(testScheduler)
|
||||
fun testShowNetworkUnavailableToastNotification() =
|
||||
runTest {
|
||||
injectDependencies(testScheduler)
|
||||
|
||||
// Launch the UI.
|
||||
launchComposite()
|
||||
tapWhenDisplayed(joinCallId)
|
||||
waitUntilDisplayed(endCallId)
|
||||
// Launch the UI.
|
||||
launchComposite()
|
||||
tapWhenDisplayed(joinCallId)
|
||||
waitUntilDisplayed(endCallId)
|
||||
|
||||
callingSDK.setNetworkUnavailable(true)
|
||||
callingSDK.setNetworkUnavailable(true)
|
||||
|
||||
// Check that the toast notification appeared
|
||||
waitUntilDisplayed(toastNotificationId)
|
||||
// Check that the toast notification appeared
|
||||
waitUntilDisplayed(toastNotificationId)
|
||||
|
||||
// Assert toast notification appears with correct text
|
||||
assertDisplayed(toastNotificationId)
|
||||
assertDisplayed(toastNotificationIconId)
|
||||
assertViewText(toastNotificationMessageId, R.string.azure_communication_ui_calling_diagnostics_network_was_lost)
|
||||
// Assert toast notification appears with correct text
|
||||
assertDisplayed(toastNotificationId)
|
||||
assertDisplayed(toastNotificationIconId)
|
||||
assertViewText(toastNotificationMessageId, R.string.azure_communication_ui_calling_diagnostics_network_was_lost)
|
||||
|
||||
// Stop speaking while muted
|
||||
callingSDK.setNetworkUnavailable(false)
|
||||
// Stop speaking while muted
|
||||
callingSDK.setNetworkUnavailable(false)
|
||||
|
||||
// Assert toast notification is still shown even after UFD is set to false
|
||||
assertDisplayed(toastNotificationId)
|
||||
assertDisplayed(toastNotificationIconId)
|
||||
assertViewText(toastNotificationMessageId, R.string.azure_communication_ui_calling_diagnostics_network_was_lost)
|
||||
}
|
||||
// Assert toast notification is still shown even after UFD is set to false
|
||||
assertDisplayed(toastNotificationId)
|
||||
assertDisplayed(toastNotificationIconId)
|
||||
assertViewText(toastNotificationMessageId, R.string.azure_communication_ui_calling_diagnostics_network_was_lost)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testShowNetworkRelaysUnreachableToastNotification() = runTest {
|
||||
injectDependencies(testScheduler)
|
||||
fun testShowNetworkRelaysUnreachableToastNotification() =
|
||||
runTest {
|
||||
injectDependencies(testScheduler)
|
||||
|
||||
// Launch the UI.
|
||||
launchComposite()
|
||||
tapWhenDisplayed(joinCallId)
|
||||
waitUntilDisplayed(endCallId)
|
||||
// Launch the UI.
|
||||
launchComposite()
|
||||
tapWhenDisplayed(joinCallId)
|
||||
waitUntilDisplayed(endCallId)
|
||||
|
||||
callingSDK.setNetworkRelaysUnreachable(true)
|
||||
callingSDK.setNetworkRelaysUnreachable(true)
|
||||
|
||||
// Check that the toast notification appeared
|
||||
waitUntilDisplayed(toastNotificationId)
|
||||
// Check that the toast notification appeared
|
||||
waitUntilDisplayed(toastNotificationId)
|
||||
|
||||
// Assert toast notification appears with correct text
|
||||
assertDisplayed(toastNotificationId)
|
||||
assertDisplayed(toastNotificationIconId)
|
||||
assertViewText(toastNotificationMessageId, R.string.azure_communication_ui_calling_diagnostics_network_was_lost)
|
||||
// Assert toast notification appears with correct text
|
||||
assertDisplayed(toastNotificationId)
|
||||
assertDisplayed(toastNotificationIconId)
|
||||
assertViewText(toastNotificationMessageId, R.string.azure_communication_ui_calling_diagnostics_network_was_lost)
|
||||
|
||||
// Stop speaking while muted
|
||||
callingSDK.setNetworkRelaysUnreachable(false)
|
||||
// Stop speaking while muted
|
||||
callingSDK.setNetworkRelaysUnreachable(false)
|
||||
|
||||
// Assert toast notification is still shown even after UFD is set to false
|
||||
assertDisplayed(toastNotificationId)
|
||||
assertDisplayed(toastNotificationIconId)
|
||||
assertViewText(toastNotificationMessageId, R.string.azure_communication_ui_calling_diagnostics_network_was_lost)
|
||||
}
|
||||
// Assert toast notification is still shown even after UFD is set to false
|
||||
assertDisplayed(toastNotificationId)
|
||||
assertDisplayed(toastNotificationIconId)
|
||||
assertViewText(toastNotificationMessageId, R.string.azure_communication_ui_calling_diagnostics_network_was_lost)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testShowSpeakingWhileMutedToastNotification() = runTest {
|
||||
injectDependencies(testScheduler)
|
||||
fun testShowSpeakingWhileMutedToastNotification() =
|
||||
runTest {
|
||||
injectDependencies(testScheduler)
|
||||
|
||||
// Launch the UI.
|
||||
launchComposite()
|
||||
tapWhenDisplayed(joinCallId)
|
||||
waitUntilDisplayed(endCallId)
|
||||
// Launch the UI.
|
||||
launchComposite()
|
||||
tapWhenDisplayed(joinCallId)
|
||||
waitUntilDisplayed(endCallId)
|
||||
|
||||
callingSDK.setSpeakingWhileMuted(true)
|
||||
callingSDK.setSpeakingWhileMuted(true)
|
||||
|
||||
// Check that the toast notification appeared
|
||||
waitUntilDisplayed(toastNotificationId)
|
||||
// Check that the toast notification appeared
|
||||
waitUntilDisplayed(toastNotificationId)
|
||||
|
||||
// Assert toast notification appears with correct text
|
||||
assertDisplayed(toastNotificationId)
|
||||
assertDisplayed(toastNotificationIconId)
|
||||
assertViewText(toastNotificationMessageId, R.string.azure_communication_ui_calling_diagnostics_you_are_muted)
|
||||
// Assert toast notification appears with correct text
|
||||
assertDisplayed(toastNotificationId)
|
||||
assertDisplayed(toastNotificationIconId)
|
||||
assertViewText(toastNotificationMessageId, R.string.azure_communication_ui_calling_diagnostics_you_are_muted)
|
||||
|
||||
// Stop speaking while muted
|
||||
callingSDK.setSpeakingWhileMuted(false)
|
||||
// Stop speaking while muted
|
||||
callingSDK.setSpeakingWhileMuted(false)
|
||||
|
||||
assertViewGone(toastNotificationId)
|
||||
assertViewGone(toastNotificationIconId)
|
||||
}
|
||||
assertViewGone(toastNotificationId)
|
||||
assertViewGone(toastNotificationIconId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testShowCameraStartFailedToastNotification() = runTest {
|
||||
injectDependencies(testScheduler)
|
||||
fun testShowCameraStartFailedToastNotification() =
|
||||
runTest {
|
||||
injectDependencies(testScheduler)
|
||||
|
||||
// Launch the UI.
|
||||
launchComposite()
|
||||
tapWhenDisplayed(joinCallId)
|
||||
waitUntilDisplayed(endCallId)
|
||||
// Launch the UI.
|
||||
launchComposite()
|
||||
tapWhenDisplayed(joinCallId)
|
||||
waitUntilDisplayed(endCallId)
|
||||
|
||||
callingSDK.setCameraStartFailed(true)
|
||||
callingSDK.setCameraStartFailed(true)
|
||||
|
||||
// Check that the toast notification appeared
|
||||
waitUntilDisplayed(toastNotificationId)
|
||||
// Check that the toast notification appeared
|
||||
waitUntilDisplayed(toastNotificationId)
|
||||
|
||||
// Assert toast notification appears with correct text
|
||||
assertDisplayed(toastNotificationId)
|
||||
assertDisplayed(toastNotificationIconId)
|
||||
assertViewText(toastNotificationMessageId, R.string.azure_communication_ui_calling_diagnostics_unable_to_start_camera)
|
||||
// Assert toast notification appears with correct text
|
||||
assertDisplayed(toastNotificationId)
|
||||
assertDisplayed(toastNotificationIconId)
|
||||
assertViewText(toastNotificationMessageId, R.string.azure_communication_ui_calling_diagnostics_unable_to_start_camera)
|
||||
|
||||
// Stop speaking while muted
|
||||
callingSDK.setCameraStartFailed(false)
|
||||
// Stop speaking while muted
|
||||
callingSDK.setCameraStartFailed(false)
|
||||
|
||||
// Assert toast notification is still shown even after UFD is set to false
|
||||
assertDisplayed(toastNotificationId)
|
||||
assertDisplayed(toastNotificationIconId)
|
||||
assertViewText(toastNotificationMessageId, R.string.azure_communication_ui_calling_diagnostics_unable_to_start_camera)
|
||||
}
|
||||
// Assert toast notification is still shown even after UFD is set to false
|
||||
assertDisplayed(toastNotificationId)
|
||||
assertDisplayed(toastNotificationIconId)
|
||||
assertViewText(toastNotificationMessageId, R.string.azure_communication_ui_calling_diagnostics_unable_to_start_camera)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testShowCameraStartTimedOutToastNotification() = runTest {
|
||||
injectDependencies(testScheduler)
|
||||
fun testShowCameraStartTimedOutToastNotification() =
|
||||
runTest {
|
||||
injectDependencies(testScheduler)
|
||||
|
||||
// Launch the UI.
|
||||
launchComposite()
|
||||
tapWhenDisplayed(joinCallId)
|
||||
waitUntilDisplayed(endCallId)
|
||||
// Launch the UI.
|
||||
launchComposite()
|
||||
tapWhenDisplayed(joinCallId)
|
||||
waitUntilDisplayed(endCallId)
|
||||
|
||||
callingSDK.setCameraStartTimedOut(true)
|
||||
callingSDK.setCameraStartTimedOut(true)
|
||||
|
||||
// Check that the toast notification appeared
|
||||
waitUntilDisplayed(toastNotificationId)
|
||||
// Check that the toast notification appeared
|
||||
waitUntilDisplayed(toastNotificationId)
|
||||
|
||||
// Assert toast notification appears with correct text
|
||||
assertDisplayed(toastNotificationId)
|
||||
assertDisplayed(toastNotificationIconId)
|
||||
assertViewText(toastNotificationMessageId, R.string.azure_communication_ui_calling_diagnostics_unable_to_start_camera)
|
||||
// Assert toast notification appears with correct text
|
||||
assertDisplayed(toastNotificationId)
|
||||
assertDisplayed(toastNotificationIconId)
|
||||
assertViewText(toastNotificationMessageId, R.string.azure_communication_ui_calling_diagnostics_unable_to_start_camera)
|
||||
|
||||
// Stop speaking while muted
|
||||
callingSDK.setCameraStartTimedOut(false)
|
||||
// Stop speaking while muted
|
||||
callingSDK.setCameraStartTimedOut(false)
|
||||
|
||||
// Assert toast notification is still shown even after UFD is set to false
|
||||
assertDisplayed(toastNotificationId)
|
||||
assertDisplayed(toastNotificationIconId)
|
||||
assertViewText(toastNotificationMessageId, R.string.azure_communication_ui_calling_diagnostics_unable_to_start_camera)
|
||||
}
|
||||
// Assert toast notification is still shown even after UFD is set to false
|
||||
assertDisplayed(toastNotificationId)
|
||||
assertDisplayed(toastNotificationIconId)
|
||||
assertViewText(toastNotificationMessageId, R.string.azure_communication_ui_calling_diagnostics_unable_to_start_camera)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,146 +8,162 @@ import com.azure.android.communication.assertDisplayed
|
|||
import com.azure.android.communication.assertNotDisplayed
|
||||
import com.azure.android.communication.assertViewText
|
||||
import com.azure.android.communication.tap
|
||||
import com.azure.android.communication.ui.calling.implementation.R
|
||||
import com.azure.android.communication.tapWhenDisplayed
|
||||
import com.azure.android.communication.ui.calling.implementation.R
|
||||
import com.azure.android.communication.waitUntilDisplayed
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
internal class UpperBarMessageNotificationTest : BaseUiTest() {
|
||||
@Test
|
||||
fun testNoSpeakerDevicesAvailableUpperBarMessageNotification() = runTest {
|
||||
injectDependencies(testScheduler)
|
||||
fun testNoSpeakerDevicesAvailableUpperBarMessageNotification() =
|
||||
runTest {
|
||||
injectDependencies(testScheduler)
|
||||
|
||||
// Launch the UI.
|
||||
launchComposite()
|
||||
tapWhenDisplayed(joinCallId)
|
||||
waitUntilDisplayed(endCallId)
|
||||
// Launch the UI.
|
||||
launchComposite()
|
||||
tapWhenDisplayed(joinCallId)
|
||||
waitUntilDisplayed(endCallId)
|
||||
|
||||
callingSDK.setNoSpeakerDevicesAvailable(true)
|
||||
callingSDK.setNoSpeakerDevicesAvailable(true)
|
||||
|
||||
// Check that the upper message bar notification appeared
|
||||
waitUntilDisplayed(upperMessageBarNotificationId)
|
||||
// Check that the upper message bar notification appeared
|
||||
waitUntilDisplayed(upperMessageBarNotificationId)
|
||||
|
||||
// Assert notification appears with correct text
|
||||
assertDisplayed(upperMessageBarNotificationId)
|
||||
assertDisplayed(upperMessageBarNotificationIconId)
|
||||
assertViewText(upperMessageBarNotificationMessageId, R.string.azure_communication_ui_calling_diagnostics_unable_to_locate_speaker)
|
||||
// Assert notification appears with correct text
|
||||
assertDisplayed(upperMessageBarNotificationId)
|
||||
assertDisplayed(upperMessageBarNotificationIconId)
|
||||
assertViewText(
|
||||
upperMessageBarNotificationMessageId,
|
||||
R.string.azure_communication_ui_calling_diagnostics_unable_to_locate_speaker,
|
||||
)
|
||||
|
||||
callingSDK.setNoSpeakerDevicesAvailable(false)
|
||||
callingSDK.setNoSpeakerDevicesAvailable(false)
|
||||
|
||||
// Upper Bar Message Notification not present anymore due to UFD state change
|
||||
assertNotDisplayed(upperMessageBarNotificationId)
|
||||
// Upper Bar Message Notification not present anymore due to UFD state change
|
||||
assertNotDisplayed(upperMessageBarNotificationId)
|
||||
|
||||
// Show the Upper Message Bar notification again and dismiss it using the X button
|
||||
callingSDK.setNoSpeakerDevicesAvailable(true)
|
||||
// Show the Upper Message Bar notification again and dismiss it using the X button
|
||||
callingSDK.setNoSpeakerDevicesAvailable(true)
|
||||
|
||||
// Dismiss the notification pressing the X button
|
||||
tap(upperMessageBarNotificationDismissButtonId)
|
||||
// Dismiss the notification pressing the X button
|
||||
tap(upperMessageBarNotificationDismissButtonId)
|
||||
|
||||
// Upper Bar Message Notification not present anymore
|
||||
assertNotDisplayed(upperMessageBarNotificationId)
|
||||
}
|
||||
// Upper Bar Message Notification not present anymore
|
||||
assertNotDisplayed(upperMessageBarNotificationId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNoMicrophoneDevicesAvailableUpperBarMessageNotification() = runTest {
|
||||
injectDependencies(testScheduler)
|
||||
fun testNoMicrophoneDevicesAvailableUpperBarMessageNotification() =
|
||||
runTest {
|
||||
injectDependencies(testScheduler)
|
||||
|
||||
// Launch the UI.
|
||||
launchComposite()
|
||||
tapWhenDisplayed(joinCallId)
|
||||
waitUntilDisplayed(endCallId)
|
||||
// Launch the UI.
|
||||
launchComposite()
|
||||
tapWhenDisplayed(joinCallId)
|
||||
waitUntilDisplayed(endCallId)
|
||||
|
||||
callingSDK.setNoMicrophoneDevicesAvailable(true)
|
||||
callingSDK.setNoMicrophoneDevicesAvailable(true)
|
||||
|
||||
// Check that the upper message bar notification appeared
|
||||
waitUntilDisplayed(upperMessageBarNotificationId)
|
||||
// Check that the upper message bar notification appeared
|
||||
waitUntilDisplayed(upperMessageBarNotificationId)
|
||||
|
||||
// Assert notification appears with correct text
|
||||
assertDisplayed(upperMessageBarNotificationId)
|
||||
assertDisplayed(upperMessageBarNotificationIconId)
|
||||
assertViewText(upperMessageBarNotificationMessageId, R.string.azure_communication_ui_calling_diagnostics_unable_to_locate_microphone)
|
||||
// Assert notification appears with correct text
|
||||
assertDisplayed(upperMessageBarNotificationId)
|
||||
assertDisplayed(upperMessageBarNotificationIconId)
|
||||
assertViewText(
|
||||
upperMessageBarNotificationMessageId,
|
||||
R.string.azure_communication_ui_calling_diagnostics_unable_to_locate_microphone,
|
||||
)
|
||||
|
||||
callingSDK.setNoMicrophoneDevicesAvailable(false)
|
||||
callingSDK.setNoMicrophoneDevicesAvailable(false)
|
||||
|
||||
// Upper Bar Message Notification not present anymore
|
||||
assertNotDisplayed(upperMessageBarNotificationId)
|
||||
// Upper Bar Message Notification not present anymore
|
||||
assertNotDisplayed(upperMessageBarNotificationId)
|
||||
|
||||
// Show the Upper Message Bar notification again and dismiss it using the X button
|
||||
callingSDK.setNoMicrophoneDevicesAvailable(true)
|
||||
// Show the Upper Message Bar notification again and dismiss it using the X button
|
||||
callingSDK.setNoMicrophoneDevicesAvailable(true)
|
||||
|
||||
// Dismiss the notification pressing the X button
|
||||
tap(upperMessageBarNotificationDismissButtonId)
|
||||
// Dismiss the notification pressing the X button
|
||||
tap(upperMessageBarNotificationDismissButtonId)
|
||||
|
||||
// Upper Bar Message Notification not present anymore
|
||||
assertNotDisplayed(upperMessageBarNotificationId)
|
||||
}
|
||||
// Upper Bar Message Notification not present anymore
|
||||
assertNotDisplayed(upperMessageBarNotificationId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMicrophoneNotFunctioningUpperBarMessageNotification() = runTest {
|
||||
injectDependencies(testScheduler)
|
||||
fun testMicrophoneNotFunctioningUpperBarMessageNotification() =
|
||||
runTest {
|
||||
injectDependencies(testScheduler)
|
||||
|
||||
// Launch the UI.
|
||||
launchComposite()
|
||||
tapWhenDisplayed(joinCallId)
|
||||
waitUntilDisplayed(endCallId)
|
||||
// Launch the UI.
|
||||
launchComposite()
|
||||
tapWhenDisplayed(joinCallId)
|
||||
waitUntilDisplayed(endCallId)
|
||||
|
||||
callingSDK.setMicrophoneNotFunctioning(true)
|
||||
callingSDK.setMicrophoneNotFunctioning(true)
|
||||
|
||||
// Check that the upper message bar notification appeared
|
||||
waitUntilDisplayed(upperMessageBarNotificationId)
|
||||
// Check that the upper message bar notification appeared
|
||||
waitUntilDisplayed(upperMessageBarNotificationId)
|
||||
|
||||
// Assert notification appears with correct text
|
||||
assertDisplayed(upperMessageBarNotificationId)
|
||||
assertDisplayed(upperMessageBarNotificationIconId)
|
||||
assertViewText(upperMessageBarNotificationMessageId, R.string.azure_communication_ui_calling_diagnostics_microphone_not_working_as_expected)
|
||||
// Assert notification appears with correct text
|
||||
assertDisplayed(upperMessageBarNotificationId)
|
||||
assertDisplayed(upperMessageBarNotificationIconId)
|
||||
assertViewText(
|
||||
upperMessageBarNotificationMessageId,
|
||||
R.string.azure_communication_ui_calling_diagnostics_microphone_not_working_as_expected,
|
||||
)
|
||||
|
||||
callingSDK.setMicrophoneNotFunctioning(false)
|
||||
callingSDK.setMicrophoneNotFunctioning(false)
|
||||
|
||||
// Upper Bar Message Notification not present anymore
|
||||
assertNotDisplayed(upperMessageBarNotificationId)
|
||||
// Upper Bar Message Notification not present anymore
|
||||
assertNotDisplayed(upperMessageBarNotificationId)
|
||||
|
||||
// Show the Upper Message Bar notification again and dismiss it using the X button
|
||||
callingSDK.setMicrophoneNotFunctioning(true)
|
||||
// Show the Upper Message Bar notification again and dismiss it using the X button
|
||||
callingSDK.setMicrophoneNotFunctioning(true)
|
||||
|
||||
// Dismiss the notification pressing the X button
|
||||
tap(upperMessageBarNotificationDismissButtonId)
|
||||
// Dismiss the notification pressing the X button
|
||||
tap(upperMessageBarNotificationDismissButtonId)
|
||||
|
||||
// Upper Bar Message Notification not present anymore
|
||||
assertNotDisplayed(upperMessageBarNotificationId)
|
||||
}
|
||||
// Upper Bar Message Notification not present anymore
|
||||
assertNotDisplayed(upperMessageBarNotificationId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSpeakerNotFunctioningUpperBarMessageNotification() = runTest {
|
||||
injectDependencies(testScheduler)
|
||||
fun testSpeakerNotFunctioningUpperBarMessageNotification() =
|
||||
runTest {
|
||||
injectDependencies(testScheduler)
|
||||
|
||||
// Launch the UI.
|
||||
launchComposite()
|
||||
tapWhenDisplayed(joinCallId)
|
||||
waitUntilDisplayed(endCallId)
|
||||
// Launch the UI.
|
||||
launchComposite()
|
||||
tapWhenDisplayed(joinCallId)
|
||||
waitUntilDisplayed(endCallId)
|
||||
|
||||
callingSDK.setSpeakerNotFunctioning(true)
|
||||
callingSDK.setSpeakerNotFunctioning(true)
|
||||
|
||||
// Check that the upper message bar notification appeared
|
||||
waitUntilDisplayed(upperMessageBarNotificationId)
|
||||
// Check that the upper message bar notification appeared
|
||||
waitUntilDisplayed(upperMessageBarNotificationId)
|
||||
|
||||
// Assert notification appears with correct text
|
||||
assertDisplayed(upperMessageBarNotificationId)
|
||||
assertDisplayed(upperMessageBarNotificationIconId)
|
||||
assertViewText(upperMessageBarNotificationMessageId, R.string.azure_communication_ui_calling_diagnostics_speaker_not_working_as_expected)
|
||||
// Assert notification appears with correct text
|
||||
assertDisplayed(upperMessageBarNotificationId)
|
||||
assertDisplayed(upperMessageBarNotificationIconId)
|
||||
assertViewText(
|
||||
upperMessageBarNotificationMessageId,
|
||||
R.string.azure_communication_ui_calling_diagnostics_speaker_not_working_as_expected,
|
||||
)
|
||||
|
||||
callingSDK.setSpeakerNotFunctioning(false)
|
||||
callingSDK.setSpeakerNotFunctioning(false)
|
||||
|
||||
// Upper Bar Message Notification not present anymore
|
||||
assertNotDisplayed(upperMessageBarNotificationId)
|
||||
// Upper Bar Message Notification not present anymore
|
||||
assertNotDisplayed(upperMessageBarNotificationId)
|
||||
|
||||
// Show the Upper Message Bar notification again and dismiss it using the X button
|
||||
callingSDK.setSpeakerNotFunctioning(true)
|
||||
// Show the Upper Message Bar notification again and dismiss it using the X button
|
||||
callingSDK.setSpeakerNotFunctioning(true)
|
||||
|
||||
// Dismiss the notification pressing the X button
|
||||
tap(upperMessageBarNotificationDismissButtonId)
|
||||
// Dismiss the notification pressing the X button
|
||||
tap(upperMessageBarNotificationDismissButtonId)
|
||||
|
||||
// Upper Bar Message Notification not present anymore
|
||||
assertNotDisplayed(upperMessageBarNotificationId)
|
||||
}
|
||||
// Upper Bar Message Notification not present anymore
|
||||
assertNotDisplayed(upperMessageBarNotificationId)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ internal fun launchComposite() {
|
|||
CallCompositeRemoteOptions(
|
||||
CallCompositeGroupCallLocator(UUID.fromString("74fce2c1-520f-11ec-97de-71411a9a8e14")),
|
||||
communicationTokenCredential,
|
||||
"test"
|
||||
"test",
|
||||
)
|
||||
|
||||
callComposite.launchTest(appContext, remoteOptions, null)
|
||||
|
|
|
@ -10,12 +10,14 @@ import com.azure.android.communication.ui.calling.presentation.manager.DebugInfo
|
|||
import com.azure.android.communication.ui.calling.presentation.manager.DebugInfoManagerImpl
|
||||
import java.io.File
|
||||
|
||||
internal fun createDebugInfoManager(context: Context, getLogFiles: () -> List<File>): DebugInfoManager {
|
||||
internal fun createDebugInfoManager(
|
||||
context: Context,
|
||||
getLogFiles: () -> List<File>,
|
||||
): DebugInfoManager {
|
||||
return DebugInfoManagerImpl(CallHistoryRepositoryImpl(context, DefaultLogger()), getLogFiles)
|
||||
}
|
||||
|
||||
internal fun CallComposite.getDiContainer() =
|
||||
CallComposite.diContainer
|
||||
internal fun CallComposite.getDiContainer() = CallComposite.diContainer
|
||||
|
||||
internal fun CallComposite.onExit() {
|
||||
CallComposite.diContainer = null
|
||||
|
|
|
@ -7,7 +7,6 @@ import com.azure.android.communication.ui.calling.configuration.CallCompositeCon
|
|||
import java.lang.IllegalStateException
|
||||
|
||||
internal class CallCompositeInstanceManager {
|
||||
|
||||
/**
|
||||
* CallCompositeInstance Storage
|
||||
*
|
||||
|
@ -25,7 +24,10 @@ internal class CallCompositeInstanceManager {
|
|||
* Store a Config by Instance ID
|
||||
*/
|
||||
@JvmStatic
|
||||
fun putCallComposite(id: Int, callComposite: CallComposite) {
|
||||
fun putCallComposite(
|
||||
id: Int,
|
||||
callComposite: CallComposite,
|
||||
) {
|
||||
instances[id] = callComposite
|
||||
}
|
||||
|
||||
|
@ -39,11 +41,12 @@ internal class CallCompositeInstanceManager {
|
|||
* May return null if the Configuration becomes garbage collected
|
||||
*/
|
||||
@JvmStatic
|
||||
fun getCallComposite(id: Int): CallComposite = instances[id]
|
||||
?: throw CallCompositeException(
|
||||
"This ID is not valid, and no entry exists in the map. Please file a bug, this is an error in the composite",
|
||||
IllegalStateException()
|
||||
)
|
||||
fun getCallComposite(id: Int): CallComposite =
|
||||
instances[id]
|
||||
?: throw CallCompositeException(
|
||||
"This ID is not valid, and no entry exists in the map. Please file a bug, this is an error in the composite",
|
||||
IllegalStateException(),
|
||||
)
|
||||
|
||||
/**
|
||||
* Check if CallComposite exists
|
||||
|
|
|
@ -17,7 +17,7 @@ internal data class CallConfiguration(
|
|||
val displayName: String,
|
||||
val groupId: UUID?,
|
||||
val meetingLink: String?,
|
||||
val callType: CallType
|
||||
val callType: CallType,
|
||||
) {
|
||||
val diagnosticConfig = DiagnosticConfig()
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ internal data class RemoteParticipantViewData(
|
|||
|
||||
internal interface RemoteParticipantsConfigurationHandler {
|
||||
fun onSetParticipantViewData(data: RemoteParticipantViewData): CallCompositeSetParticipantViewDataResult
|
||||
|
||||
fun onRemoveParticipantViewData(identifier: String)
|
||||
}
|
||||
|
||||
|
@ -31,7 +32,7 @@ internal class RemoteParticipantsConfiguration {
|
|||
): CallCompositeSetParticipantViewDataResult {
|
||||
handler?.get()?.let {
|
||||
return@setParticipantViewData it.onSetParticipantViewData(
|
||||
RemoteParticipantViewData(identifier, participantViewData)
|
||||
RemoteParticipantViewData(identifier, participantViewData),
|
||||
)
|
||||
}
|
||||
return CallCompositeSetParticipantViewDataResult.PARTICIPANT_NOT_IN_CALL
|
||||
|
|
|
@ -5,9 +5,9 @@ package com.azure.android.communication.ui.calling.configuration.events
|
|||
|
||||
import com.azure.android.communication.ui.calling.CallCompositeEventHandler
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeCallStateChangedEvent
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeDismissedEvent
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeErrorEvent
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositePictureInPictureChangedEvent
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeDismissedEvent
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeRemoteParticipantJoinedEvent
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeUserReportedIssueEvent
|
||||
|
||||
|
@ -27,11 +27,9 @@ internal class CallCompositeEventsHandler {
|
|||
|
||||
fun getOnErrorHandlers() = errorHandlers.asIterable()
|
||||
|
||||
fun addOnErrorEventHandler(errorHandler: CallCompositeEventHandler<CallCompositeErrorEvent>) =
|
||||
errorHandlers.add(errorHandler)
|
||||
fun addOnErrorEventHandler(errorHandler: CallCompositeEventHandler<CallCompositeErrorEvent>) = errorHandlers.add(errorHandler)
|
||||
|
||||
fun removeOnErrorEventHandler(errorHandler: CallCompositeEventHandler<CallCompositeErrorEvent>) =
|
||||
errorHandlers.remove(errorHandler)
|
||||
fun removeOnErrorEventHandler(errorHandler: CallCompositeEventHandler<CallCompositeErrorEvent>) = errorHandlers.remove(errorHandler)
|
||||
|
||||
fun getOnRemoteParticipantJoinedHandlers() = remoteParticipantJoinedHandlers.asIterable()
|
||||
|
||||
|
@ -42,6 +40,7 @@ internal class CallCompositeEventsHandler {
|
|||
remoteParticipantJoinedHandlers.remove(handler)
|
||||
|
||||
fun getOnMultitaskingStateChangedEventHandlers() = multitaskingStateChangedEvent.asIterable()
|
||||
|
||||
fun addOnMultitaskingStateChangedEventHandler(handler: CallCompositeEventHandler<CallCompositePictureInPictureChangedEvent>) =
|
||||
multitaskingStateChangedEvent.add(handler)
|
||||
|
||||
|
|
|
@ -15,16 +15,22 @@ import org.threeten.bp.OffsetDateTime
|
|||
import org.threeten.bp.ZoneId
|
||||
|
||||
internal interface CallHistoryRepository {
|
||||
suspend fun insert(callId: String, callDateTime: OffsetDateTime)
|
||||
suspend fun insert(
|
||||
callId: String,
|
||||
callDateTime: OffsetDateTime,
|
||||
)
|
||||
|
||||
suspend fun getAll(): List<CallHistoryRecordData>
|
||||
}
|
||||
|
||||
internal class CallHistoryRepositoryImpl(
|
||||
private val context: Context,
|
||||
private val logger: Logger
|
||||
private val logger: Logger,
|
||||
) : CallHistoryRepository {
|
||||
|
||||
override suspend fun insert(callId: String, callDateTime: OffsetDateTime) {
|
||||
override suspend fun insert(
|
||||
callId: String,
|
||||
callDateTime: OffsetDateTime,
|
||||
) {
|
||||
return withContext(Dispatchers.IO) {
|
||||
// SQLite does not allow concurrent writes. Need to queue them via lock.
|
||||
synchronized(dbAccessLock) {
|
||||
|
@ -32,10 +38,11 @@ internal class CallHistoryRepositoryImpl(
|
|||
// reliable event when to dispose it.
|
||||
DbHelper(context).writableDatabase
|
||||
.use { db ->
|
||||
val values = ContentValues().apply {
|
||||
put(CallHistoryContract.COLUMN_NAME_CALL_ID, callId)
|
||||
put(CallHistoryContract.COLUMN_NAME_CALL_DATE, callDateTime.toInstant().toEpochMilli())
|
||||
}
|
||||
val values =
|
||||
ContentValues().apply {
|
||||
put(CallHistoryContract.COLUMN_NAME_CALL_ID, callId)
|
||||
put(CallHistoryContract.COLUMN_NAME_CALL_DATE, callDateTime.toInstant().toEpochMilli())
|
||||
}
|
||||
|
||||
val result = db.insert(CallHistoryContract.TABLE_NAME, null, values)
|
||||
if (result == -1L) {
|
||||
|
@ -64,7 +71,7 @@ internal class CallHistoryRepositoryImpl(
|
|||
"${CallHistoryContract.COLUMN_NAME_CALL_DATE}, " +
|
||||
"${CallHistoryContract.COLUMN_NAME_CALL_ID} " +
|
||||
"FROM ${CallHistoryContract.TABLE_NAME}",
|
||||
null
|
||||
null,
|
||||
).use {
|
||||
if (it.moveToFirst()) {
|
||||
val idColumnIndex = it.getColumnIndexOrThrow(CallHistoryContract.COLUMN_NAME_ID)
|
||||
|
@ -75,10 +82,12 @@ internal class CallHistoryRepositoryImpl(
|
|||
CallHistoryRecordData(
|
||||
id = it.getInt(idColumnIndex),
|
||||
callId = it.getString(nameColumnIndex),
|
||||
callStartedOn = OffsetDateTime.ofInstant(
|
||||
Instant.ofEpochMilli(it.getLong(dateColumnIndex)), ZoneId.systemDefault()
|
||||
),
|
||||
)
|
||||
callStartedOn =
|
||||
OffsetDateTime.ofInstant(
|
||||
Instant.ofEpochMilli(it.getLong(dateColumnIndex)),
|
||||
ZoneId.systemDefault(),
|
||||
),
|
||||
),
|
||||
)
|
||||
} while (it.moveToNext())
|
||||
}
|
||||
|
@ -92,7 +101,8 @@ internal class CallHistoryRepositoryImpl(
|
|||
val threshold = OffsetDateTime.now().minusDays(31).toInstant().toEpochMilli()
|
||||
db.delete(
|
||||
CallHistoryContract.TABLE_NAME,
|
||||
"${CallHistoryContract.COLUMN_NAME_CALL_DATE} < ?", arrayOf(threshold.toString())
|
||||
"${CallHistoryContract.COLUMN_NAME_CALL_DATE} < ?",
|
||||
arrayOf(threshold.toString()),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -14,10 +14,18 @@ internal class DbHelper(context: Context) : SQLiteOpenHelper(context, DATABASE_N
|
|||
db.execSQL(CallHistoryContract.SQL_CREATE_CALL_HISTORY_INDEX)
|
||||
}
|
||||
|
||||
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
override fun onUpgrade(
|
||||
db: SQLiteDatabase,
|
||||
oldVersion: Int,
|
||||
newVersion: Int,
|
||||
) {
|
||||
}
|
||||
|
||||
override fun onDowngrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
override fun onDowngrade(
|
||||
db: SQLiteDatabase,
|
||||
oldVersion: Int,
|
||||
newVersion: Int,
|
||||
) {
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
@ -28,7 +36,6 @@ internal class DbHelper(context: Context) : SQLiteOpenHelper(context, DATABASE_N
|
|||
}
|
||||
|
||||
internal object CallHistoryContract {
|
||||
|
||||
const val COLUMN_NAME_ID = BaseColumns._ID
|
||||
const val TABLE_NAME = "call_history"
|
||||
|
||||
|
|
|
@ -20,13 +20,13 @@ import com.azure.android.communication.ui.calling.presentation.manager.AvatarVie
|
|||
import com.azure.android.communication.ui.calling.presentation.manager.CompositeExitManager
|
||||
import com.azure.android.communication.ui.calling.presentation.manager.DebugInfoManager
|
||||
import com.azure.android.communication.ui.calling.presentation.manager.LifecycleManager
|
||||
import com.azure.android.communication.ui.calling.presentation.manager.MultitaskingManager
|
||||
import com.azure.android.communication.ui.calling.presentation.manager.NetworkManager
|
||||
import com.azure.android.communication.ui.calling.presentation.manager.PermissionManager
|
||||
import com.azure.android.communication.ui.calling.presentation.navigation.NavigationRouter
|
||||
import com.azure.android.communication.ui.calling.redux.Store
|
||||
import com.azure.android.communication.ui.calling.redux.middleware.handler.CallingMiddlewareActionHandler
|
||||
import com.azure.android.communication.ui.calling.redux.state.ReduxState
|
||||
import com.azure.android.communication.ui.calling.presentation.manager.MultitaskingManager
|
||||
import com.azure.android.communication.ui.calling.service.CallHistoryService
|
||||
import com.azure.android.communication.ui.calling.service.CallingService
|
||||
import com.azure.android.communication.ui.calling.service.NotificationService
|
||||
|
|
|
@ -22,18 +22,18 @@ import com.azure.android.communication.ui.calling.presentation.manager.AudioFocu
|
|||
import com.azure.android.communication.ui.calling.presentation.manager.AudioModeManager
|
||||
import com.azure.android.communication.ui.calling.presentation.manager.AudioSessionManager
|
||||
import com.azure.android.communication.ui.calling.presentation.manager.AvatarViewManager
|
||||
import com.azure.android.communication.ui.calling.presentation.manager.CompositeExitManager
|
||||
import com.azure.android.communication.ui.calling.presentation.manager.CameraStatusHook
|
||||
import com.azure.android.communication.ui.calling.presentation.manager.CompositeExitManager
|
||||
import com.azure.android.communication.ui.calling.presentation.manager.DebugInfoManager
|
||||
import com.azure.android.communication.ui.calling.presentation.manager.DebugInfoManagerImpl
|
||||
import com.azure.android.communication.ui.calling.presentation.manager.LifecycleManagerImpl
|
||||
import com.azure.android.communication.ui.calling.presentation.manager.MeetingJoinedHook
|
||||
import com.azure.android.communication.ui.calling.presentation.manager.MicStatusHook
|
||||
import com.azure.android.communication.ui.calling.presentation.manager.MultitaskingManager
|
||||
import com.azure.android.communication.ui.calling.presentation.manager.NetworkManager
|
||||
import com.azure.android.communication.ui.calling.presentation.manager.ParticipantAddedOrRemovedHook
|
||||
import com.azure.android.communication.ui.calling.presentation.manager.PermissionManager
|
||||
import com.azure.android.communication.ui.calling.presentation.manager.SwitchCameraStatusHook
|
||||
|
||||
import com.azure.android.communication.ui.calling.presentation.navigation.NavigationRouterImpl
|
||||
import com.azure.android.communication.ui.calling.redux.AppStore
|
||||
import com.azure.android.communication.ui.calling.redux.Middleware
|
||||
|
@ -49,14 +49,13 @@ import com.azure.android.communication.ui.calling.redux.reducer.LocalParticipant
|
|||
import com.azure.android.communication.ui.calling.redux.reducer.NavigationReducerImpl
|
||||
import com.azure.android.communication.ui.calling.redux.reducer.ParticipantStateReducerImpl
|
||||
import com.azure.android.communication.ui.calling.redux.reducer.PermissionStateReducerImpl
|
||||
import com.azure.android.communication.ui.calling.redux.reducer.PipReducerImpl
|
||||
import com.azure.android.communication.ui.calling.redux.reducer.Reducer
|
||||
import com.azure.android.communication.ui.calling.redux.state.AppReduxState
|
||||
import com.azure.android.communication.ui.calling.redux.state.ReduxState
|
||||
import com.azure.android.communication.ui.calling.service.CallingService
|
||||
import com.azure.android.communication.ui.calling.presentation.manager.MultitaskingManager
|
||||
import com.azure.android.communication.ui.calling.redux.reducer.PipReducerImpl
|
||||
import com.azure.android.communication.ui.calling.service.CallHistoryService
|
||||
import com.azure.android.communication.ui.calling.service.CallHistoryServiceImpl
|
||||
import com.azure.android.communication.ui.calling.service.CallingService
|
||||
import com.azure.android.communication.ui.calling.service.NotificationService
|
||||
import com.azure.android.communication.ui.calling.service.sdk.CallingSDK
|
||||
import com.azure.android.communication.ui.calling.service.sdk.CallingSDKEventHandler
|
||||
|
@ -72,7 +71,6 @@ internal class DependencyInjectionContainerImpl(
|
|||
private val customVideoStreamRendererFactory: VideoStreamRendererFactory?,
|
||||
private val customCoroutineContextProvider: CoroutineContextProvider?,
|
||||
) : DependencyInjectionContainer {
|
||||
|
||||
override var callCompositeActivityWeakReference: WeakReference<CallCompositeActivity> = WeakReference(null)
|
||||
|
||||
override val configuration by lazy {
|
||||
|
@ -86,7 +84,7 @@ internal class DependencyInjectionContainerImpl(
|
|||
override val callingMiddlewareActionHandler by lazy {
|
||||
CallingMiddlewareActionHandlerImpl(
|
||||
callingService,
|
||||
coroutineContextProvider
|
||||
coroutineContextProvider,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -102,7 +100,7 @@ internal class DependencyInjectionContainerImpl(
|
|||
VideoViewManager(
|
||||
callingSDKWrapper,
|
||||
applicationContext,
|
||||
customVideoStreamRendererFactory ?: VideoStreamRendererFactoryImpl()
|
||||
customVideoStreamRendererFactory ?: VideoStreamRendererFactoryImpl(),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -151,7 +149,7 @@ internal class DependencyInjectionContainerImpl(
|
|||
override val callHistoryService: CallHistoryService by lazy {
|
||||
CallHistoryServiceImpl(
|
||||
appStore,
|
||||
callHistoryRepository
|
||||
callHistoryRepository,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -160,7 +158,7 @@ internal class DependencyInjectionContainerImpl(
|
|||
coroutineContextProvider,
|
||||
appStore,
|
||||
configuration.callCompositeLocalOptions,
|
||||
configuration.remoteParticipantsConfiguration
|
||||
configuration.remoteParticipantsConfiguration,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -173,7 +171,7 @@ internal class DependencyInjectionContainerImpl(
|
|||
ParticipantAddedOrRemovedHook(),
|
||||
MicStatusHook(),
|
||||
SwitchCameraStatusHook(),
|
||||
)
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -190,7 +188,7 @@ internal class DependencyInjectionContainerImpl(
|
|||
initialState,
|
||||
appReduxStateReducer,
|
||||
appMiddleware,
|
||||
storeDispatcher
|
||||
storeDispatcher,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -217,7 +215,7 @@ internal class DependencyInjectionContainerImpl(
|
|||
configuration.callConfig?.displayName,
|
||||
localOptions?.isCameraOn == true,
|
||||
localOptions?.isMicrophoneOn == true,
|
||||
localOptions?.audioVideoMode ?: CallCompositeAudioVideoMode.AUDIO_AND_VIDEO
|
||||
localOptions?.audioVideoMode ?: CallCompositeAudioVideoMode.AUDIO_AND_VIDEO,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -239,7 +237,7 @@ internal class DependencyInjectionContainerImpl(
|
|||
private val callingMiddleware: Middleware<ReduxState> by lazy {
|
||||
CallingMiddlewareImpl(
|
||||
callingMiddlewareActionHandler,
|
||||
logger
|
||||
logger,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -254,7 +252,7 @@ internal class DependencyInjectionContainerImpl(
|
|||
navigationReducer,
|
||||
audioSessionReducer,
|
||||
pipReducer,
|
||||
callDiagnosticsReducer
|
||||
callDiagnosticsReducer,
|
||||
) as Reducer<ReduxState>
|
||||
}
|
||||
//endregion
|
||||
|
@ -269,7 +267,7 @@ internal class DependencyInjectionContainerImpl(
|
|||
?: CallingSDKWrapper(
|
||||
applicationContext,
|
||||
callingSDKEventHandler,
|
||||
configuration.callConfig
|
||||
configuration.callConfig,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -4,5 +4,5 @@ import com.azure.android.communication.ui.calling.models.CallCompositeEventCode
|
|||
|
||||
internal class CallStateError(
|
||||
val errorCode: ErrorCode,
|
||||
val callCompositeEventCode: CallCompositeEventCode? = null
|
||||
val callCompositeEventCode: CallCompositeEventCode? = null,
|
||||
)
|
||||
|
|
|
@ -34,24 +34,24 @@ import com.azure.android.communication.ui.calling.features.interfaces.SupportFil
|
|||
*/
|
||||
internal class ACSFeatureFactory
|
||||
/**
|
||||
* Constructor used by the factory
|
||||
*/
|
||||
private constructor() {
|
||||
companion object {
|
||||
/**
|
||||
* Return and instance of the required feature
|
||||
*
|
||||
* @param feature interface of the required feature
|
||||
* @param <F> type of the feature interface
|
||||
* @return requested interface feature
|
||||
</F> */
|
||||
inline fun <reified F : ACSFeature> getFeature(): F {
|
||||
return featureList[F::class.java]!! as F
|
||||
}
|
||||
* Constructor used by the factory
|
||||
*/
|
||||
private constructor() {
|
||||
companion object {
|
||||
/**
|
||||
* Return and instance of the required feature
|
||||
*
|
||||
* @param feature interface of the required feature
|
||||
* @param <F> type of the feature interface
|
||||
* @return requested interface feature
|
||||
</F> */
|
||||
inline fun <reified F : ACSFeature> getFeature(): F {
|
||||
return featureList[F::class.java]!! as F
|
||||
}
|
||||
|
||||
private val featureList: Map<Class<*>, ACSFeature> =
|
||||
mapOf(
|
||||
SupportFilesFeature::class.java to SupportFilesFeatureImpl()
|
||||
)
|
||||
private val featureList: Map<Class<*>, ACSFeature> =
|
||||
mapOf(
|
||||
SupportFilesFeature::class.java to SupportFilesFeatureImpl(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,5 +9,8 @@ import com.azure.android.communication.ui.calling.features.ACSFeature
|
|||
import java.io.File
|
||||
|
||||
abstract class SupportFilesFeature : ACSFeature {
|
||||
abstract fun getSupportFiles(client: CallClient, context: Context): List<File>
|
||||
abstract fun getSupportFiles(
|
||||
client: CallClient,
|
||||
context: Context,
|
||||
): List<File>
|
||||
}
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
package com.azure.android.communication.ui.calling.handlers
|
||||
|
||||
import com.azure.android.communication.ui.calling.configuration.CallCompositeConfiguration
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeCallStateCode
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeCallStateChangedEvent
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeCallStateCode
|
||||
import com.azure.android.communication.ui.calling.redux.Store
|
||||
import com.azure.android.communication.ui.calling.redux.state.CallingStatus
|
||||
import com.azure.android.communication.ui.calling.redux.state.ReduxState
|
||||
|
|
|
@ -51,9 +51,10 @@ internal class RemoteParticipantHandler(
|
|||
try {
|
||||
if (joinedParticipant.isNotEmpty()) {
|
||||
val participantIdMap = remoteParticipantsCollection.getRemoteParticipantsMap()
|
||||
val identifiers = joinedParticipant.map {
|
||||
participantIdMap.getValue(it).identifier.into()
|
||||
}
|
||||
val identifiers =
|
||||
joinedParticipant.map {
|
||||
participantIdMap.getValue(it).identifier.into()
|
||||
}
|
||||
val eventArgs = CallCompositeRemoteParticipantJoinedEvent(identifiers)
|
||||
configuration.callCompositeEventsHandler.getOnRemoteParticipantJoinedHandlers()
|
||||
.forEach { it.handle(eventArgs) }
|
||||
|
|
|
@ -6,7 +6,6 @@ package com.azure.android.communication.ui.calling.logger
|
|||
import android.util.Log
|
||||
|
||||
internal class DefaultLogger : Logger {
|
||||
|
||||
private val tag = "communication.ui"
|
||||
|
||||
override fun info(message: String) {
|
||||
|
@ -21,7 +20,10 @@ internal class DefaultLogger : Logger {
|
|||
Log.w(tag, message)
|
||||
}
|
||||
|
||||
override fun error(message: String, error: Throwable?) {
|
||||
override fun error(
|
||||
message: String,
|
||||
error: Throwable?,
|
||||
) {
|
||||
if (error != null) {
|
||||
Log.e(tag, message, error)
|
||||
} else {
|
||||
|
|
|
@ -5,7 +5,13 @@ package com.azure.android.communication.ui.calling.logger
|
|||
|
||||
internal interface Logger {
|
||||
fun info(message: String)
|
||||
|
||||
fun debug(message: String)
|
||||
|
||||
fun warning(message: String)
|
||||
fun error(message: String, error: Throwable? = null)
|
||||
|
||||
fun error(
|
||||
message: String,
|
||||
error: Throwable? = null,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -6,9 +6,14 @@ package com.azure.android.communication.ui.calling.models
|
|||
import org.threeten.bp.OffsetDateTime
|
||||
import java.io.File
|
||||
|
||||
internal fun buildCallCompositeDebugInfo(callHistoryRecordList: List<CallCompositeCallHistoryRecord>, getLogFiles: () -> List<File>) =
|
||||
CallCompositeDebugInfo(callHistoryRecordList, getLogFiles)
|
||||
internal fun buildCallCompositeDebugInfo(
|
||||
callHistoryRecordList: List<CallCompositeCallHistoryRecord>,
|
||||
getLogFiles: () -> List<File>,
|
||||
) = CallCompositeDebugInfo(callHistoryRecordList, getLogFiles)
|
||||
|
||||
internal fun buildCallHistoryRecord(callStartedOn: OffsetDateTime, callIds: List<String>): CallCompositeCallHistoryRecord {
|
||||
internal fun buildCallHistoryRecord(
|
||||
callStartedOn: OffsetDateTime,
|
||||
callIds: List<String>,
|
||||
): CallCompositeCallHistoryRecord {
|
||||
return CallCompositeCallHistoryRecord(callStartedOn, callIds)
|
||||
}
|
||||
|
|
|
@ -9,5 +9,5 @@ internal enum class CallCompositeInternalParticipantRole {
|
|||
CONSUMER,
|
||||
PRESENTER,
|
||||
ORGANIZER,
|
||||
COORGANIZER;
|
||||
COORGANIZER,
|
||||
}
|
||||
|
|
|
@ -8,5 +8,5 @@ internal enum class CallCompositeLobbyErrorCode {
|
|||
LOBBY_CONVERSATION_TYPE_NOT_SUPPORTED,
|
||||
LOBBY_MEETING_ROLE_NOT_ALLOWED,
|
||||
REMOVE_PARTICIPANT_OPERATION_FAILURE,
|
||||
UNKNOWN_ERROR
|
||||
UNKNOWN_ERROR,
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ internal enum class NetworkCallDiagnostic {
|
|||
NETWORK_RECEIVE_QUALITY,
|
||||
NETWORK_SEND_QUALITY,
|
||||
NETWORK_UNAVAILABLE,
|
||||
NETWORK_RELAYS_UNREACHABLE
|
||||
NETWORK_RELAYS_UNREACHABLE,
|
||||
}
|
||||
|
||||
internal enum class MediaCallDiagnostic {
|
||||
|
@ -25,19 +25,19 @@ internal enum class MediaCallDiagnostic {
|
|||
CAMERA_FROZEN,
|
||||
CAMERA_START_FAILED,
|
||||
CAMERA_START_TIMED_OUT,
|
||||
CAMERA_PERMISSION_DENIED
|
||||
CAMERA_PERMISSION_DENIED,
|
||||
}
|
||||
|
||||
internal enum class CallDiagnosticQuality {
|
||||
UNKNOWN,
|
||||
GOOD,
|
||||
POOR,
|
||||
BAD;
|
||||
BAD,
|
||||
}
|
||||
|
||||
internal data class CallDiagnosticModel<DiagnosticKind, DiagnosticValue>(
|
||||
val diagnosticKind: DiagnosticKind,
|
||||
val diagnosticValue: DiagnosticValue
|
||||
val diagnosticValue: DiagnosticValue,
|
||||
)
|
||||
|
||||
// Alias types representing different types of Call Diagnostics
|
||||
|
|
|
@ -9,7 +9,6 @@ internal data class ToastNotificationModel(
|
|||
val networkCallDiagnostic: NetworkCallDiagnostic?,
|
||||
val mediaCallDiagnostic: MediaCallDiagnostic?,
|
||||
) {
|
||||
|
||||
fun isEmpty(): Boolean {
|
||||
return notificationIconId == 0 && notificationMessageId == 0
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ package com.azure.android.communication.ui.calling.models
|
|||
internal data class UpperMessageBarNotificationModel(
|
||||
val notificationIconId: Int,
|
||||
val notificationMessageId: Int,
|
||||
val mediaCallDiagnostic: MediaCallDiagnostic?
|
||||
val mediaCallDiagnostic: MediaCallDiagnostic?,
|
||||
) {
|
||||
fun isEmpty(): Boolean {
|
||||
return notificationIconId == 0 && notificationMessageId == 0
|
||||
|
|
|
@ -27,8 +27,8 @@ import androidx.core.content.ContextCompat
|
|||
import androidx.fragment.app.FragmentTransaction
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.azure.android.communication.ui.calling.CallCompositeException
|
||||
import com.azure.android.communication.ui.calling.implementation.R
|
||||
import com.azure.android.communication.ui.calling.CallCompositeInstanceManager
|
||||
import com.azure.android.communication.ui.calling.implementation.R
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeSupportedLocale
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeSupportedScreenOrientation
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeUserReportedIssueEvent
|
||||
|
@ -126,7 +126,7 @@ internal open class CallCompositeActivity : AppCompatActivity() {
|
|||
this,
|
||||
getAudioPermissionLauncher(),
|
||||
getCameraPermissionLauncher(),
|
||||
lifecycleScope
|
||||
lifecycleScope,
|
||||
)
|
||||
|
||||
audioSessionManager.onCreate(savedInstanceState)
|
||||
|
@ -152,7 +152,7 @@ internal open class CallCompositeActivity : AppCompatActivity() {
|
|||
store.dispatch(PipAction.HideEntered())
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
// Probably can follow the above pattern now with function declarations
|
||||
|
@ -189,8 +189,11 @@ internal open class CallCompositeActivity : AppCompatActivity() {
|
|||
activity?.packageManager?.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE) == true
|
||||
) {
|
||||
store.dispatch(
|
||||
if (isInPictureInPictureMode) PipAction.PipModeEntered()
|
||||
else PipAction.ShowNormalEntered()
|
||||
if (isInPictureInPictureMode) {
|
||||
PipAction.PipModeEntered()
|
||||
} else {
|
||||
PipAction.ShowNormalEntered()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -241,13 +244,15 @@ internal open class CallCompositeActivity : AppCompatActivity() {
|
|||
activity?.packageManager?.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE) == true &&
|
||||
store.getCurrentState().navigationState.navigationState == NavigationStatus.IN_CALL
|
||||
) {
|
||||
val params = PictureInPictureParams
|
||||
.Builder()
|
||||
.setAspectRatio(Rational(1, 1))
|
||||
.build()
|
||||
val params =
|
||||
PictureInPictureParams
|
||||
.Builder()
|
||||
.setAspectRatio(Rational(1, 1))
|
||||
.build()
|
||||
|
||||
if (enterPictureInPictureMode(params))
|
||||
if (enterPictureInPictureMode(params)) {
|
||||
reduxStartPipMode()
|
||||
}
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
// on some samsung devices(API 26) enterPictureInPictureMode crashes even FEATURE_PICTURE_IN_PICTURE is true
|
||||
|
@ -257,7 +262,7 @@ internal open class CallCompositeActivity : AppCompatActivity() {
|
|||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
override fun onPictureInPictureModeChanged(
|
||||
isInPictureInPictureMode: Boolean,
|
||||
newConfig: Configuration
|
||||
newConfig: Configuration,
|
||||
) {
|
||||
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
|
||||
store.dispatch(if (isInPictureInPictureMode) PipAction.PipModeEntered() else PipAction.ShowNormalEntered())
|
||||
|
@ -284,28 +289,31 @@ internal open class CallCompositeActivity : AppCompatActivity() {
|
|||
}
|
||||
|
||||
fun hide() {
|
||||
if (!configuration.enableMultitasking)
|
||||
if (!configuration.enableMultitasking) {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: should we enter PiP if we are on the setup screen?
|
||||
if (configuration.enableSystemPiPWhenMultitasking &&
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
|
||||
activity?.packageManager?.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE) == true
|
||||
) {
|
||||
val params = PictureInPictureParams
|
||||
.Builder()
|
||||
.setAspectRatio(Rational(1, 1))
|
||||
.build()
|
||||
val params =
|
||||
PictureInPictureParams
|
||||
.Builder()
|
||||
.setAspectRatio(Rational(1, 1))
|
||||
.build()
|
||||
var enteredPiPSucceeded = false
|
||||
try {
|
||||
enteredPiPSucceeded = enterPictureInPictureMode(params)
|
||||
} catch (_: Exception) {
|
||||
// on some samsung devices(API 26) enterPictureInPictureMode crashes even FEATURE_PICTURE_IN_PICTURE is true
|
||||
}
|
||||
if (enteredPiPSucceeded)
|
||||
if (enteredPiPSucceeded) {
|
||||
reduxStartPipMode()
|
||||
else
|
||||
} else {
|
||||
activity?.moveTaskToBack(true)
|
||||
}
|
||||
} else {
|
||||
activity?.moveTaskToBack(true)
|
||||
}
|
||||
|
@ -317,9 +325,9 @@ internal open class CallCompositeActivity : AppCompatActivity() {
|
|||
ColorDrawable(
|
||||
ContextCompat.getColor(
|
||||
this,
|
||||
R.color.azure_communication_ui_calling_color_background
|
||||
)
|
||||
)
|
||||
R.color.azure_communication_ui_calling_color_background,
|
||||
),
|
||||
),
|
||||
)
|
||||
supportActionBar?.setHomeAsUpIndicator(R.drawable.azure_communication_ui_calling_ic_fluent_arrow_left_24_filled)
|
||||
}
|
||||
|
@ -332,7 +340,7 @@ internal open class CallCompositeActivity : AppCompatActivity() {
|
|||
container.configuration.callCompositeEventsHandler.getOnUserReportedHandlers().forEach {
|
||||
try {
|
||||
it.handle(
|
||||
event
|
||||
event,
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
// Ignore any exception from the user handler
|
||||
|
@ -342,18 +350,19 @@ internal open class CallCompositeActivity : AppCompatActivity() {
|
|||
|
||||
private fun configureLocalization() {
|
||||
val config: Configuration = resources.configuration
|
||||
val locale = when (configuration.localizationConfig) {
|
||||
null -> {
|
||||
supportedOSLocale()
|
||||
}
|
||||
|
||||
else -> {
|
||||
configuration.localizationConfig!!.layoutDirection?.let {
|
||||
window?.decorView?.layoutDirection = it
|
||||
val locale =
|
||||
when (configuration.localizationConfig) {
|
||||
null -> {
|
||||
supportedOSLocale()
|
||||
}
|
||||
|
||||
else -> {
|
||||
configuration.localizationConfig!!.layoutDirection?.let {
|
||||
window?.decorView?.layoutDirection = it
|
||||
}
|
||||
configuration.localizationConfig!!.locale
|
||||
}
|
||||
configuration.localizationConfig!!.locale
|
||||
}
|
||||
}
|
||||
config.setLocale(locale)
|
||||
|
||||
resources.updateConfiguration(config, resources.displayMetrics)
|
||||
|
@ -372,7 +381,7 @@ internal open class CallCompositeActivity : AppCompatActivity() {
|
|||
|
||||
private fun getCameraPermissionLauncher(): ActivityResultLauncher<String> {
|
||||
return registerForActivityResult(
|
||||
ActivityResultContracts.RequestPermission()
|
||||
ActivityResultContracts.RequestPermission(),
|
||||
) {
|
||||
permissionManager.setCameraPermissionsState()
|
||||
}
|
||||
|
@ -380,7 +389,7 @@ internal open class CallCompositeActivity : AppCompatActivity() {
|
|||
|
||||
private fun getAudioPermissionLauncher(): ActivityResultLauncher<Array<String>> {
|
||||
return registerForActivityResult(
|
||||
ActivityResultContracts.RequestMultiplePermissions()
|
||||
ActivityResultContracts.RequestMultiplePermissions(),
|
||||
) {
|
||||
permissionManager.setAudioPermissionsState()
|
||||
}
|
||||
|
@ -446,10 +455,11 @@ internal open class CallCompositeActivity : AppCompatActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
val fragment = supportFragmentManager.fragmentFactory.instantiate(
|
||||
classLoader,
|
||||
fragmentClassName
|
||||
)
|
||||
val fragment =
|
||||
supportFragmentManager.fragmentFactory.instantiate(
|
||||
classLoader,
|
||||
fragmentClassName,
|
||||
)
|
||||
val transaction: FragmentTransaction = supportFragmentManager.beginTransaction()
|
||||
|
||||
// For accessibility, we are going to turn it off during the transaction
|
||||
|
@ -469,18 +479,20 @@ internal open class CallCompositeActivity : AppCompatActivity() {
|
|||
|
||||
private fun setStatusBarColor() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
window.statusBarColor = ContextCompat.getColor(
|
||||
this,
|
||||
R.color.azure_communication_ui_calling_color_status_bar
|
||||
)
|
||||
val isNightMode = this.resources.configuration.uiMode
|
||||
.and(Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
|
||||
window.statusBarColor =
|
||||
ContextCompat.getColor(
|
||||
this,
|
||||
R.color.azure_communication_ui_calling_color_status_bar,
|
||||
)
|
||||
val isNightMode =
|
||||
this.resources.configuration.uiMode
|
||||
.and(Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
|
||||
|
||||
if (isNightMode) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
window.insetsController?.setSystemBarsAppearance(
|
||||
0,
|
||||
APPEARANCE_LIGHT_STATUS_BARS
|
||||
APPEARANCE_LIGHT_STATUS_BARS,
|
||||
)
|
||||
} else {
|
||||
window.clearFlags(0)
|
||||
|
@ -489,7 +501,7 @@ internal open class CallCompositeActivity : AppCompatActivity() {
|
|||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
window.insetsController?.setSystemBarsAppearance(
|
||||
APPEARANCE_LIGHT_STATUS_BARS,
|
||||
APPEARANCE_LIGHT_STATUS_BARS
|
||||
APPEARANCE_LIGHT_STATUS_BARS,
|
||||
)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
|
|
|
@ -5,11 +5,11 @@ package com.azure.android.communication.ui.calling.presentation
|
|||
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import com.azure.android.communication.ui.calling.implementation.R
|
||||
import com.azure.android.communication.ui.calling.CallCompositeException
|
||||
import com.azure.android.communication.ui.calling.CallCompositeInstanceManager
|
||||
import com.azure.android.communication.ui.calling.di.DependencyInjectionContainer
|
||||
import com.azure.android.communication.ui.calling.getDiContainer
|
||||
import com.azure.android.communication.ui.calling.implementation.R
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeAudioVideoMode
|
||||
import com.azure.android.communication.ui.calling.presentation.fragment.calling.CallingViewModel
|
||||
import com.azure.android.communication.ui.calling.presentation.fragment.factories.CallingViewModelFactory
|
||||
|
@ -33,6 +33,7 @@ internal class DependencyInjectionContainerHolder(
|
|||
private const val commonMessage =
|
||||
"Please ensure that you have set a valid instanceId before retrieving the container."
|
||||
}
|
||||
|
||||
// Instance ID to locate Configuration. -1 is invalid.
|
||||
var instanceId: Int = -1
|
||||
set(value) {
|
||||
|
@ -58,7 +59,7 @@ internal class DependencyInjectionContainerHolder(
|
|||
SetupViewModel(
|
||||
container.appStore,
|
||||
SetupViewModelFactory(container.appStore, application),
|
||||
container.networkManager
|
||||
container.networkManager,
|
||||
)
|
||||
}
|
||||
val callingViewModel by lazy {
|
||||
|
@ -70,12 +71,12 @@ internal class DependencyInjectionContainerHolder(
|
|||
application.resources.getInteger(R.integer.azure_communication_ui_calling_max_remote_participants),
|
||||
container.debugInfoManager,
|
||||
container.configuration.callCompositeEventsHandler.getOnUserReportedHandlers().toList().isNotEmpty(),
|
||||
container.configuration.enableMultitasking
|
||||
container.configuration.enableMultitasking,
|
||||
),
|
||||
container.networkManager,
|
||||
container.configuration.enableMultitasking,
|
||||
container.configuration.callCompositeLocalOptions?.audioVideoMode
|
||||
?: CallCompositeAudioVideoMode.AUDIO_AND_VIDEO
|
||||
?: CallCompositeAudioVideoMode.AUDIO_AND_VIDEO,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,16 +6,16 @@ package com.azure.android.communication.ui.calling.presentation
|
|||
import android.content.Context
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.azure.android.communication.calling.CreateViewOptions
|
||||
import com.azure.android.communication.calling.MediaStreamType
|
||||
import com.azure.android.communication.calling.ScalingMode
|
||||
import com.azure.android.communication.calling.CreateViewOptions
|
||||
import com.azure.android.communication.ui.calling.service.sdk.CallingSDK
|
||||
import com.azure.android.communication.ui.calling.service.sdk.LocalVideoStream
|
||||
import com.azure.android.communication.ui.calling.service.sdk.RemoteVideoStream
|
||||
import com.azure.android.communication.ui.calling.service.sdk.VideoStreamRenderer
|
||||
import com.azure.android.communication.ui.calling.service.sdk.VideoStreamRendererLocalWrapper
|
||||
import com.azure.android.communication.ui.calling.service.sdk.VideoStreamRendererView
|
||||
import com.azure.android.communication.ui.calling.service.sdk.VideoStreamRendererRemoteWrapper
|
||||
import com.azure.android.communication.ui.calling.service.sdk.VideoStreamRendererView
|
||||
import com.azure.android.communication.ui.calling.utilities.isAndroidTV
|
||||
|
||||
internal class VideoViewManager(
|
||||
|
@ -65,9 +65,7 @@ internal class VideoViewManager(
|
|||
localParticipantVideoRendererMap.clear()
|
||||
}
|
||||
|
||||
fun removeRemoteParticipantVideoRenderer(
|
||||
userVideoStreams: List<Pair<String, String>>,
|
||||
) {
|
||||
fun removeRemoteParticipantVideoRenderer(userVideoStreams: List<Pair<String, String>>) {
|
||||
removeRemoteParticipantRenderer(userVideoStreams)
|
||||
val remoteParticipants = callingSDKWrapper.getRemoteParticipantsMap()
|
||||
|
||||
|
@ -92,7 +90,7 @@ internal class VideoViewManager(
|
|||
val videoStreamRenderer =
|
||||
videoStreamRendererFactory.getLocalParticipantVideoStreamRenderer(
|
||||
videoStream,
|
||||
context
|
||||
context,
|
||||
)
|
||||
val rendererView = videoStreamRenderer.createView()
|
||||
localParticipantVideoRendererMap[videoStreamID] =
|
||||
|
@ -102,7 +100,10 @@ internal class VideoViewManager(
|
|||
}
|
||||
}
|
||||
|
||||
fun getLocalVideoRenderer(videoStreamID: String, scalingMode: ScalingMode): View? {
|
||||
fun getLocalVideoRenderer(
|
||||
videoStreamID: String,
|
||||
scalingMode: ScalingMode,
|
||||
): View? {
|
||||
var rendererView: VideoStreamRendererView? = null
|
||||
if (localParticipantVideoRendererMap.containsKey(videoStreamID)) {
|
||||
rendererView = localParticipantVideoRendererMap[videoStreamID]?.rendererView
|
||||
|
@ -125,7 +126,7 @@ internal class VideoViewManager(
|
|||
} else if (updateRemoteParticipantVideoRenderer(
|
||||
participantID,
|
||||
videoStreamId,
|
||||
context
|
||||
context,
|
||||
)
|
||||
) {
|
||||
rendererView = remoteParticipantVideoRendererMap[uniqueID]?.rendererView
|
||||
|
@ -146,37 +147,41 @@ internal class VideoViewManager(
|
|||
val uniqueID = generateUniqueKey(userID, videoStreamID)
|
||||
|
||||
if (!remoteParticipantVideoRendererMap.containsKey(uniqueID)) {
|
||||
|
||||
if (remoteParticipants.containsKey(userID) &&
|
||||
remoteParticipants[userID]?.videoStreams != null &&
|
||||
remoteParticipants[userID]?.videoStreams?.size!! > 0
|
||||
) {
|
||||
val stream = remoteParticipants[userID]?.videoStreams?.find { videoStream ->
|
||||
videoStream.id.toString() == videoStreamID
|
||||
}
|
||||
val stream =
|
||||
remoteParticipants[userID]?.videoStreams?.find { videoStream ->
|
||||
videoStream.id.toString() == videoStreamID
|
||||
}
|
||||
|
||||
if (stream != null) {
|
||||
val isScreenShare = stream.mediaStreamType == MediaStreamType.SCREEN_SHARING
|
||||
val videoStreamRenderer =
|
||||
videoStreamRendererFactory.getRemoteParticipantVideoStreamRenderer(
|
||||
stream,
|
||||
context
|
||||
context,
|
||||
)
|
||||
|
||||
val forceFitMode = isAndroidTV && !isScreenShare && remoteParticipants.size <= 1
|
||||
|
||||
val rendererView =
|
||||
if (isScreenShare || forceFitMode) videoStreamRenderer.createView(
|
||||
CreateViewOptions(
|
||||
ScalingMode.FIT
|
||||
if (isScreenShare || forceFitMode) {
|
||||
videoStreamRenderer.createView(
|
||||
CreateViewOptions(
|
||||
ScalingMode.FIT,
|
||||
),
|
||||
)
|
||||
) else videoStreamRenderer.createView()
|
||||
} else {
|
||||
videoStreamRenderer.createView()
|
||||
}
|
||||
|
||||
remoteParticipantVideoRendererMap[uniqueID] =
|
||||
VideoRenderer(
|
||||
rendererView,
|
||||
videoStreamRenderer,
|
||||
isScreenShare
|
||||
isScreenShare,
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
@ -187,9 +192,10 @@ internal class VideoViewManager(
|
|||
}
|
||||
|
||||
private fun removeLocalParticipantRenderer(videoStreamID: String?) {
|
||||
val removedLocalStreams = localParticipantVideoRendererMap.filter { (streamID, _) ->
|
||||
streamID !== videoStreamID
|
||||
}
|
||||
val removedLocalStreams =
|
||||
localParticipantVideoRendererMap.filter { (streamID, _) ->
|
||||
streamID !== videoStreamID
|
||||
}
|
||||
|
||||
removedLocalStreams.values.forEach { videoStream ->
|
||||
destroyVideoRenderer(videoStream)
|
||||
|
@ -205,9 +211,10 @@ internal class VideoViewManager(
|
|||
userWithVideoStreamList.forEach { (userID, streamID) ->
|
||||
uniqueIDStreamList = uniqueIDStreamList.plus(generateUniqueKey(userID, streamID))
|
||||
}
|
||||
val removedRemoteStreams = remoteParticipantVideoRendererMap.filter { (streamID, _) ->
|
||||
streamID !in uniqueIDStreamList
|
||||
}
|
||||
val removedRemoteStreams =
|
||||
remoteParticipantVideoRendererMap.filter { (streamID, _) ->
|
||||
streamID !in uniqueIDStreamList
|
||||
}
|
||||
|
||||
removedRemoteStreams.values.forEach { videoStream ->
|
||||
destroyVideoRenderer(videoStream)
|
||||
|
@ -218,7 +225,10 @@ internal class VideoViewManager(
|
|||
}
|
||||
}
|
||||
|
||||
private fun generateUniqueKey(userIdentifier: String, videoStreamId: String): String {
|
||||
private fun generateUniqueKey(
|
||||
userIdentifier: String,
|
||||
videoStreamId: String,
|
||||
): String {
|
||||
return "$userIdentifier:$videoStreamId"
|
||||
}
|
||||
|
||||
|
@ -255,18 +265,16 @@ internal class VideoStreamRendererFactoryImpl : VideoStreamRendererFactory {
|
|||
override fun getRemoteParticipantVideoStreamRenderer(
|
||||
stream: RemoteVideoStream,
|
||||
context: Context,
|
||||
) =
|
||||
VideoStreamRendererRemoteWrapper(
|
||||
stream.native as com.azure.android.communication.calling.RemoteVideoStream,
|
||||
context
|
||||
)
|
||||
) = VideoStreamRendererRemoteWrapper(
|
||||
stream.native as com.azure.android.communication.calling.RemoteVideoStream,
|
||||
context,
|
||||
)
|
||||
|
||||
override fun getLocalParticipantVideoStreamRenderer(
|
||||
stream: LocalVideoStream,
|
||||
context: Context,
|
||||
) =
|
||||
VideoStreamRendererLocalWrapper(
|
||||
stream.native as com.azure.android.communication.calling.LocalVideoStream,
|
||||
context
|
||||
)
|
||||
) = VideoStreamRendererLocalWrapper(
|
||||
stream.native as com.azure.android.communication.calling.LocalVideoStream,
|
||||
context,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -13,7 +13,6 @@ import kotlinx.coroutines.launch
|
|||
internal abstract class BaseViewModel constructor(
|
||||
protected val store: Store<ReduxState>,
|
||||
) {
|
||||
|
||||
open fun init(coroutineScope: CoroutineScope) {
|
||||
coroutineScope.launch {
|
||||
store.getStateFlow().collect {
|
||||
|
|
|
@ -18,26 +18,26 @@ import androidx.activity.addCallback
|
|||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.azure.android.communication.ui.calling.implementation.R
|
||||
import com.azure.android.communication.ui.calling.CallCompositeInstanceManager
|
||||
import com.azure.android.communication.ui.calling.implementation.R
|
||||
import com.azure.android.communication.ui.calling.presentation.DependencyInjectionContainerHolder
|
||||
import com.azure.android.communication.ui.calling.presentation.MultitaskingCallCompositeActivity
|
||||
import com.azure.android.communication.ui.calling.presentation.fragment.calling.banner.BannerView
|
||||
import com.azure.android.communication.ui.calling.presentation.fragment.calling.controlbar.ControlBarView
|
||||
import com.azure.android.communication.ui.calling.presentation.fragment.calling.controlbar.more.MoreCallOptionsListView
|
||||
import com.azure.android.communication.ui.calling.presentation.fragment.calling.hangup.LeaveConfirmView
|
||||
import com.azure.android.communication.ui.calling.presentation.fragment.calling.header.InfoHeaderView
|
||||
import com.azure.android.communication.ui.calling.presentation.fragment.calling.hold.OnHoldOverlayView
|
||||
import com.azure.android.communication.ui.calling.presentation.fragment.calling.lobby.ConnectingLobbyOverlayView
|
||||
import com.azure.android.communication.ui.calling.presentation.fragment.calling.lobby.LobbyErrorHeaderView
|
||||
import com.azure.android.communication.ui.calling.presentation.fragment.calling.lobby.LobbyHeaderView
|
||||
import com.azure.android.communication.ui.calling.presentation.fragment.calling.lobby.WaitingLobbyOverlayView
|
||||
import com.azure.android.communication.ui.calling.presentation.fragment.calling.localuser.LocalParticipantView
|
||||
import com.azure.android.communication.ui.calling.presentation.fragment.calling.notification.ToastNotificationView
|
||||
import com.azure.android.communication.ui.calling.presentation.fragment.calling.notification.UpperMessageBarNotificationLayoutView
|
||||
import com.azure.android.communication.ui.calling.presentation.fragment.calling.participant.grid.ParticipantGridView
|
||||
import com.azure.android.communication.ui.calling.presentation.fragment.calling.participantlist.ParticipantListView
|
||||
import com.azure.android.communication.ui.calling.presentation.fragment.common.audiodevicelist.AudioDeviceListView
|
||||
import com.azure.android.communication.ui.calling.presentation.fragment.calling.controlbar.more.MoreCallOptionsListView
|
||||
import com.azure.android.communication.ui.calling.presentation.fragment.calling.lobby.ConnectingLobbyOverlayView
|
||||
import com.azure.android.communication.ui.calling.presentation.fragment.calling.notification.ToastNotificationView
|
||||
import com.azure.android.communication.ui.calling.presentation.fragment.calling.notification.UpperMessageBarNotificationLayoutView
|
||||
import com.azure.android.communication.ui.calling.presentation.fragment.setup.components.ErrorInfoView
|
||||
|
||||
internal class CallingFragment :
|
||||
|
@ -78,7 +78,10 @@ internal class CallingFragment :
|
|||
private lateinit var lobbyHeaderView: LobbyHeaderView
|
||||
private lateinit var lobbyErrorHeaderView: LobbyErrorHeaderView
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
override fun onViewCreated(
|
||||
view: View,
|
||||
savedInstanceState: Bundle?,
|
||||
) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
viewModel.init(viewLifecycleOwner.lifecycleScope)
|
||||
|
||||
|
@ -87,7 +90,7 @@ internal class CallingFragment :
|
|||
confirmLeaveOverlayView.layoutDirection =
|
||||
activity?.window?.decorView?.layoutDirection ?: LayoutDirection.LOCALE
|
||||
confirmLeaveOverlayView.start(
|
||||
viewLifecycleOwner
|
||||
viewLifecycleOwner,
|
||||
)
|
||||
|
||||
controlBarView = view.findViewById(R.id.azure_communication_ui_call_call_buttons)
|
||||
|
@ -100,7 +103,7 @@ internal class CallingFragment :
|
|||
videoViewManager,
|
||||
viewLifecycleOwner,
|
||||
this::switchFloatingHeader,
|
||||
avatarViewManager
|
||||
avatarViewManager,
|
||||
)
|
||||
|
||||
connectingLobbyOverlay = view.findViewById(R.id.azure_communication_ui_call_connecting_lobby_overlay)
|
||||
|
@ -127,33 +130,33 @@ internal class CallingFragment :
|
|||
viewLifecycleOwner,
|
||||
viewModel.floatingHeaderViewModel,
|
||||
this::displayParticipantList,
|
||||
accessibilityManager.isEnabled
|
||||
accessibilityManager.isEnabled,
|
||||
)
|
||||
lobbyHeaderView = view.findViewById(R.id.azure_communication_ui_calling_lobby_header)
|
||||
lobbyHeaderView.start(
|
||||
viewLifecycleOwner,
|
||||
viewModel.lobbyHeaderViewModel,
|
||||
this::displayParticipantList
|
||||
this::displayParticipantList,
|
||||
)
|
||||
|
||||
lobbyErrorHeaderView = view.findViewById(R.id.azure_communication_ui_calling_lobby_error_header)
|
||||
lobbyErrorHeaderView.start(
|
||||
viewLifecycleOwner,
|
||||
viewModel.lobbyErrorHeaderViewModel
|
||||
viewModel.lobbyErrorHeaderViewModel,
|
||||
)
|
||||
|
||||
upperMessageBarNotificationLayoutView = view.findViewById(R.id.azure_communication_ui_calling_upper_message_bar_notifications_layout)
|
||||
upperMessageBarNotificationLayoutView.start(
|
||||
viewLifecycleOwner,
|
||||
viewModel.upperMessageBarNotificationLayoutViewModel,
|
||||
accessibilityManager.isEnabled
|
||||
accessibilityManager.isEnabled,
|
||||
)
|
||||
|
||||
toastNotificationView = view.findViewById(R.id.azure_communication_ui_calling_toast_notification)
|
||||
toastNotificationView.start(
|
||||
viewLifecycleOwner,
|
||||
viewModel.toastNotificationViewModel,
|
||||
accessibilityManager.isEnabled
|
||||
accessibilityManager.isEnabled,
|
||||
)
|
||||
|
||||
audioDeviceListView =
|
||||
|
@ -162,11 +165,12 @@ internal class CallingFragment :
|
|||
activity?.window?.decorView?.layoutDirection ?: LayoutDirection.LOCALE
|
||||
audioDeviceListView.start(viewLifecycleOwner)
|
||||
|
||||
participantListView = ParticipantListView(
|
||||
viewModel.participantListViewModel,
|
||||
this.requireContext(),
|
||||
avatarViewManager,
|
||||
)
|
||||
participantListView =
|
||||
ParticipantListView(
|
||||
viewModel.participantListViewModel,
|
||||
this.requireContext(),
|
||||
avatarViewManager,
|
||||
)
|
||||
participantListView.layoutDirection =
|
||||
activity?.window?.decorView?.layoutDirection ?: LayoutDirection.LOCALE
|
||||
participantListView.start(viewLifecycleOwner)
|
||||
|
@ -183,10 +187,11 @@ internal class CallingFragment :
|
|||
errorInfoView = ErrorInfoView(view)
|
||||
errorInfoView.start(viewLifecycleOwner, viewModel.errorInfoViewModel)
|
||||
|
||||
moreCallOptionsListView = MoreCallOptionsListView(
|
||||
this.requireContext(),
|
||||
viewModel.moreCallOptionsListViewModel
|
||||
)
|
||||
moreCallOptionsListView =
|
||||
MoreCallOptionsListView(
|
||||
this.requireContext(),
|
||||
viewModel.moreCallOptionsListViewModel,
|
||||
)
|
||||
moreCallOptionsListView.layoutDirection =
|
||||
activity?.window?.decorView?.layoutDirection ?: LayoutDirection.LOCALE
|
||||
moreCallOptionsListView.start(viewLifecycleOwner)
|
||||
|
@ -205,7 +210,6 @@ internal class CallingFragment :
|
|||
}
|
||||
|
||||
private fun onBackPressed() {
|
||||
|
||||
if (viewModel.multitaskingEnabled) {
|
||||
(activity as? MultitaskingCallCompositeActivity)?.hide()
|
||||
} else {
|
||||
|
@ -225,7 +229,7 @@ internal class CallingFragment :
|
|||
sensorManager.registerListener(
|
||||
this,
|
||||
sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY),
|
||||
SensorManager.SENSOR_DELAY_NORMAL
|
||||
SensorManager.SENSOR_DELAY_NORMAL,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -265,7 +269,10 @@ internal class CallingFragment :
|
|||
if (this::toastNotificationView.isInitialized) toastNotificationView.stop()
|
||||
}
|
||||
|
||||
override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {}
|
||||
override fun onAccuracyChanged(
|
||||
sensor: Sensor,
|
||||
accuracy: Int,
|
||||
) {}
|
||||
|
||||
override fun onSensorChanged(event: SensorEvent) {
|
||||
if (event.sensor.type == Sensor.TYPE_PROXIMITY) {
|
||||
|
@ -286,7 +293,7 @@ internal class CallingFragment :
|
|||
mapOf(
|
||||
LEAVE_CONFIRM_VIEW_KEY to viewModel.confirmLeaveOverlayViewModel.getShouldDisplayLeaveConfirmFlow(),
|
||||
AUDIO_DEVICE_LIST_VIEW_KEY to viewModel.audioDeviceListViewModel.displayAudioDeviceSelectionMenuStateFlow,
|
||||
PARTICIPANT_LIST_VIEW_KEY to viewModel.participantListViewModel.getDisplayParticipantListStateFlow()
|
||||
PARTICIPANT_LIST_VIEW_KEY to viewModel.participantListViewModel.getDisplayParticipantListStateFlow(),
|
||||
).forEach { (key, element) -> outState.putBoolean(key, element.value) }
|
||||
super.onSaveInstanceState(outState)
|
||||
}
|
||||
|
@ -298,7 +305,7 @@ internal class CallingFragment :
|
|||
mapOf(
|
||||
LEAVE_CONFIRM_VIEW_KEY to viewModel.confirmLeaveOverlayViewModel::requestExitConfirmation,
|
||||
AUDIO_DEVICE_LIST_VIEW_KEY to viewModel.audioDeviceListViewModel::displayAudioDeviceSelectionMenu,
|
||||
PARTICIPANT_LIST_VIEW_KEY to viewModel.participantListViewModel::displayParticipantList
|
||||
PARTICIPANT_LIST_VIEW_KEY to viewModel.participantListViewModel::displayParticipantList,
|
||||
).forEach { (key, showDialog) -> if (it.getBoolean(key)) showDialog() }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,6 @@ internal class CallingViewModel(
|
|||
val avMode: CallCompositeAudioVideoMode,
|
||||
) :
|
||||
BaseViewModel(store) {
|
||||
|
||||
val moreCallOptionsListViewModel = callingViewModelProvider.moreCallOptionsListViewModel
|
||||
val participantGridViewModel = callingViewModelProvider.participantGridViewModel
|
||||
val controlBarViewModel = callingViewModelProvider.controlBarViewModel
|
||||
|
@ -80,7 +79,7 @@ internal class CallingViewModel(
|
|||
state.localParticipantState.cameraState.device,
|
||||
state.localParticipantState.cameraState.camerasCount,
|
||||
state.pipState.status,
|
||||
avMode
|
||||
avMode,
|
||||
)
|
||||
|
||||
floatingHeaderViewModel.init(
|
||||
|
@ -90,16 +89,16 @@ internal class CallingViewModel(
|
|||
)
|
||||
|
||||
audioDeviceListViewModel.init(
|
||||
state.localParticipantState.audioState
|
||||
state.localParticipantState.audioState,
|
||||
)
|
||||
bannerViewModel.init(
|
||||
state.callState
|
||||
state.callState,
|
||||
)
|
||||
|
||||
participantListViewModel.init(
|
||||
state.remoteParticipantState.participantMap,
|
||||
state.localParticipantState,
|
||||
canShowLobby(state.localParticipantState.localParticipantRole)
|
||||
canShowLobby(state.localParticipantState.localParticipantRole),
|
||||
)
|
||||
|
||||
waitingLobbyOverlayViewModel.init(state.callState.callingStatus)
|
||||
|
@ -118,20 +117,19 @@ internal class CallingViewModel(
|
|||
lobbyHeaderViewModel.init(
|
||||
state.callState.callingStatus,
|
||||
getLobbyParticipantsForHeader(state),
|
||||
canShowLobby(state.localParticipantState.localParticipantRole)
|
||||
canShowLobby(state.localParticipantState.localParticipantRole),
|
||||
)
|
||||
|
||||
lobbyErrorHeaderViewModel.init(
|
||||
state.callState.callingStatus,
|
||||
state.remoteParticipantState.lobbyErrorCode,
|
||||
canShowLobby(state.localParticipantState.localParticipantRole)
|
||||
canShowLobby(state.localParticipantState.localParticipantRole),
|
||||
)
|
||||
|
||||
super.init(coroutineScope)
|
||||
}
|
||||
|
||||
override suspend fun onStateChange(state: ReduxState) {
|
||||
|
||||
if (!hasSetupCalled &&
|
||||
state.callState.operationStatus == OperationStatus.SKIP_SETUP_SCREEN &&
|
||||
state.permissionState.audioPermissionState == PermissionStatus.GRANTED
|
||||
|
@ -165,7 +163,7 @@ internal class CallingViewModel(
|
|||
state.localParticipantState.cameraState.device,
|
||||
state.localParticipantState.cameraState.camerasCount,
|
||||
state.pipState.status,
|
||||
avMode
|
||||
avMode,
|
||||
)
|
||||
|
||||
audioDeviceListViewModel.update(
|
||||
|
@ -204,7 +202,7 @@ internal class CallingViewModel(
|
|||
state.localParticipantState.cameraState.device,
|
||||
state.localParticipantState.cameraState.camerasCount,
|
||||
state.pipState.status,
|
||||
avMode
|
||||
avMode,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -218,33 +216,33 @@ internal class CallingViewModel(
|
|||
)
|
||||
|
||||
floatingHeaderViewModel.update(
|
||||
remoteParticipantsForGridView.count()
|
||||
remoteParticipantsForGridView.count(),
|
||||
)
|
||||
|
||||
lobbyHeaderViewModel.update(
|
||||
state.callState.callingStatus,
|
||||
getLobbyParticipantsForHeader(state),
|
||||
canShowLobby(state.localParticipantState.localParticipantRole)
|
||||
canShowLobby(state.localParticipantState.localParticipantRole),
|
||||
)
|
||||
|
||||
lobbyErrorHeaderViewModel.update(
|
||||
state.callState.callingStatus,
|
||||
state.remoteParticipantState.lobbyErrorCode,
|
||||
canShowLobby(state.localParticipantState.localParticipantRole)
|
||||
canShowLobby(state.localParticipantState.localParticipantRole),
|
||||
)
|
||||
|
||||
upperMessageBarNotificationLayoutViewModel.update(
|
||||
state.callDiagnosticsState
|
||||
state.callDiagnosticsState,
|
||||
)
|
||||
|
||||
toastNotificationViewModel.update(
|
||||
state.callDiagnosticsState
|
||||
state.callDiagnosticsState,
|
||||
)
|
||||
|
||||
participantListViewModel.update(
|
||||
state.remoteParticipantState.participantMap,
|
||||
state.localParticipantState,
|
||||
canShowLobby(state.localParticipantState.localParticipantRole)
|
||||
canShowLobby(state.localParticipantState.localParticipantRole),
|
||||
)
|
||||
|
||||
bannerViewModel.update(state.callState)
|
||||
|
@ -257,9 +255,11 @@ internal class CallingViewModel(
|
|||
}
|
||||
|
||||
private fun getLobbyParticipantsForHeader(state: ReduxState) =
|
||||
if (canShowLobby(state.localParticipantState.localParticipantRole))
|
||||
if (canShowLobby(state.localParticipantState.localParticipantRole)) {
|
||||
state.remoteParticipantState.participantMap.filter { it.value.participantStatus == ParticipantStatus.IN_LOBBY }
|
||||
else mapOf()
|
||||
} else {
|
||||
mapOf()
|
||||
}
|
||||
|
||||
private fun canShowLobby(role: CallCompositeInternalParticipantRole?): Boolean {
|
||||
role?.let {
|
||||
|
@ -276,8 +276,7 @@ internal class CallingViewModel(
|
|||
it.value.participantStatus != ParticipantStatus.IN_LOBBY
|
||||
}
|
||||
|
||||
private fun shouldUpdateRemoteParticipantsViewModels(state: ReduxState) =
|
||||
state.callState.callingStatus == CallingStatus.CONNECTED
|
||||
private fun shouldUpdateRemoteParticipantsViewModels(state: ReduxState) = state.callState.callingStatus == CallingStatus.CONNECTED
|
||||
|
||||
private fun updateOverlayDisplayedState(callingStatus: CallingStatus) {
|
||||
floatingHeaderViewModel.updateIsOverlayDisplayed(callingStatus)
|
||||
|
|
|
@ -16,7 +16,6 @@ import androidx.core.view.ViewCompat
|
|||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.azure.android.communication.ui.calling.implementation.R
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
internal class BannerView : ConstraintLayout {
|
||||
|
@ -69,19 +68,26 @@ internal class BannerView : ConstraintLayout {
|
|||
bannerText.text = getBannerInfo(bannerInfoType)
|
||||
bannerText.setOnClickListener(getBannerClickDestination(bannerInfoType))
|
||||
|
||||
val textToAnnounce = "${context.getString(R.string.azure_communication_ui_calling_alert_title)}: ${context.getString(R.string.azure_communication_ui_calling_view_button_close_button_full_accessibility_label)}, ${bannerText.text} ${context.getString(R.string.azure_communication_ui_calling_view_link)}"
|
||||
val textToAnnounce = "${context.getString(
|
||||
R.string.azure_communication_ui_calling_alert_title,
|
||||
)}: ${context.getString(
|
||||
R.string.azure_communication_ui_calling_view_button_close_button_full_accessibility_label,
|
||||
)}, ${bannerText.text} ${context.getString(R.string.azure_communication_ui_calling_view_link)}"
|
||||
announceForAccessibility(textToAnnounce)
|
||||
|
||||
bannerCloseButton.contentDescription = "${getBannerTitle(bannerText.text)}: ${context.getString(R.string.azure_communication_ui_calling_view_button_close_button_accessibility_label)}"
|
||||
}
|
||||
// Below code helps to display banner message on screen rotate. When recording and transcription being saved is displayed
|
||||
// and screen is rotated, blank banner is displayed.
|
||||
// We can not remove reset state in view model on stop as that cause incorrect message order
|
||||
else if (bannerText.text.isNullOrBlank() && viewModel.displayedBannerType != BannerInfoType.BLANK) {
|
||||
} else if (bannerText.text.isNullOrBlank() && viewModel.displayedBannerType != BannerInfoType.BLANK) {
|
||||
// Below code helps to display banner message on screen rotate. When recording and transcription being saved is displayed
|
||||
// and screen is rotated, blank banner is displayed.
|
||||
// We can not remove reset state in view model on stop as that cause incorrect message order
|
||||
bannerText.text = getBannerInfo(viewModel.displayedBannerType)
|
||||
bannerText.setOnClickListener(getBannerClickDestination(bannerInfoType))
|
||||
|
||||
val textToAnnounce = "${context.getString(R.string.azure_communication_ui_calling_alert_title)}: ${context.getString(R.string.azure_communication_ui_calling_view_button_close_button_full_accessibility_label)}, ${bannerText.text} ${context.getString(R.string.azure_communication_ui_calling_view_link)}"
|
||||
val textToAnnounce = "${context.getString(
|
||||
R.string.azure_communication_ui_calling_alert_title,
|
||||
)}: ${context.getString(
|
||||
R.string.azure_communication_ui_calling_view_button_close_button_full_accessibility_label,
|
||||
)}, ${bannerText.text} ${context.getString(R.string.azure_communication_ui_calling_view_link)}"
|
||||
announceForAccessibility(textToAnnounce)
|
||||
|
||||
bannerCloseButton.contentDescription = "${getBannerTitle(bannerText.text)}: ${context.getString(R.string.azure_communication_ui_calling_view_button_close_button_accessibility_label)}"
|
||||
|
@ -91,25 +97,26 @@ internal class BannerView : ConstraintLayout {
|
|||
private fun getBannerClickDestination(bannerInfoType: BannerInfoType): OnClickListener {
|
||||
return OnClickListener {
|
||||
bannerText.isEnabled = false
|
||||
val url = when (bannerInfoType) {
|
||||
BannerInfoType.RECORDING_AND_TRANSCRIPTION_STARTED,
|
||||
BannerInfoType.RECORDING_STARTED,
|
||||
BannerInfoType.TRANSCRIPTION_STARTED,
|
||||
BannerInfoType.RECORDING_STOPPED_STILL_TRANSCRIBING,
|
||||
BannerInfoType.TRANSCRIPTION_STOPPED_STILL_RECORDING,
|
||||
-> {
|
||||
context.getString(R.string.azure_communication_ui_calling_view_link_privacy_policy_url)
|
||||
val url =
|
||||
when (bannerInfoType) {
|
||||
BannerInfoType.RECORDING_AND_TRANSCRIPTION_STARTED,
|
||||
BannerInfoType.RECORDING_STARTED,
|
||||
BannerInfoType.TRANSCRIPTION_STARTED,
|
||||
BannerInfoType.RECORDING_STOPPED_STILL_TRANSCRIBING,
|
||||
BannerInfoType.TRANSCRIPTION_STOPPED_STILL_RECORDING,
|
||||
-> {
|
||||
context.getString(R.string.azure_communication_ui_calling_view_link_privacy_policy_url)
|
||||
}
|
||||
BannerInfoType.TRANSCRIPTION_STOPPED,
|
||||
BannerInfoType.RECORDING_STOPPED,
|
||||
BannerInfoType.RECORDING_AND_TRANSCRIPTION_STOPPED,
|
||||
-> {
|
||||
context.getString(R.string.azure_communication_ui_calling_view_link_learn_more_url)
|
||||
}
|
||||
else -> {
|
||||
""
|
||||
}
|
||||
}
|
||||
BannerInfoType.TRANSCRIPTION_STOPPED,
|
||||
BannerInfoType.RECORDING_STOPPED,
|
||||
BannerInfoType.RECORDING_AND_TRANSCRIPTION_STOPPED,
|
||||
-> {
|
||||
context.getString(R.string.azure_communication_ui_calling_view_link_learn_more_url)
|
||||
}
|
||||
else -> {
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
intent.data = Uri.parse(url)
|
||||
|
@ -118,7 +125,7 @@ internal class BannerView : ConstraintLayout {
|
|||
{
|
||||
bannerText.isEnabled = true
|
||||
},
|
||||
400
|
||||
400,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@ import com.azure.android.communication.ui.calling.redux.state.CallingStatus
|
|||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
internal class BannerViewModel {
|
||||
|
||||
private var _bannerInfoTypeStateFlow: MutableStateFlow<BannerInfoType> =
|
||||
MutableStateFlow(BannerInfoType.BLANK)
|
||||
var bannerInfoTypeStateFlow = _bannerInfoTypeStateFlow
|
||||
|
@ -28,12 +27,13 @@ internal class BannerViewModel {
|
|||
}
|
||||
|
||||
fun init(callingState: CallingState) {
|
||||
bannerInfoTypeStateFlow = MutableStateFlow(
|
||||
createBannerInfoType(
|
||||
callingState.isRecording,
|
||||
callingState.isTranscribing
|
||||
bannerInfoTypeStateFlow =
|
||||
MutableStateFlow(
|
||||
createBannerInfoType(
|
||||
callingState.isRecording,
|
||||
callingState.isTranscribing,
|
||||
),
|
||||
)
|
||||
)
|
||||
_isOverlayDisplayedFlow = MutableStateFlow(isOverlayDisplayed(callingState.callingStatus))
|
||||
}
|
||||
|
||||
|
@ -66,27 +66,29 @@ internal class BannerViewModel {
|
|||
isRecording: Boolean,
|
||||
isTranscribing: Boolean,
|
||||
): BannerInfoType {
|
||||
recordingState = when (isRecording) {
|
||||
true -> ComplianceState.ON
|
||||
false -> {
|
||||
if (recordingState == ComplianceState.ON) {
|
||||
ComplianceState.STOPPED
|
||||
} else {
|
||||
recordingState
|
||||
recordingState =
|
||||
when (isRecording) {
|
||||
true -> ComplianceState.ON
|
||||
false -> {
|
||||
if (recordingState == ComplianceState.ON) {
|
||||
ComplianceState.STOPPED
|
||||
} else {
|
||||
recordingState
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
transcriptionState = when (isTranscribing) {
|
||||
true -> ComplianceState.ON
|
||||
false -> {
|
||||
if (transcriptionState == ComplianceState.ON) {
|
||||
ComplianceState.STOPPED
|
||||
} else {
|
||||
transcriptionState
|
||||
transcriptionState =
|
||||
when (isTranscribing) {
|
||||
true -> ComplianceState.ON
|
||||
false -> {
|
||||
if (transcriptionState == ComplianceState.ON) {
|
||||
ComplianceState.STOPPED
|
||||
} else {
|
||||
transcriptionState
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((recordingState == ComplianceState.ON) &&
|
||||
(transcriptionState == ComplianceState.ON)
|
||||
|
@ -146,7 +148,7 @@ internal class BannerViewModel {
|
|||
internal enum class ComplianceState {
|
||||
ON,
|
||||
STOPPED,
|
||||
OFF
|
||||
OFF,
|
||||
}
|
||||
|
||||
internal enum class BannerInfoType {
|
||||
|
@ -158,5 +160,5 @@ internal enum class BannerInfoType {
|
|||
TRANSCRIPTION_STOPPED,
|
||||
RECORDING_STOPPED_STILL_TRANSCRIBING,
|
||||
RECORDING_STOPPED,
|
||||
RECORDING_AND_TRANSCRIPTION_STOPPED
|
||||
RECORDING_AND_TRANSCRIPTION_STOPPED,
|
||||
}
|
||||
|
|
|
@ -95,7 +95,7 @@ internal class ControlBarView : ConstraintLayout {
|
|||
}
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewModel.getCallStateFlow().collect() {
|
||||
viewModel.getCallStateFlow().collect {
|
||||
if (it == CallingStatus.NONE || it == CallingStatus.CONNECTING) {
|
||||
cameraToggle.isEnabled = false
|
||||
micToggle.isEnabled = false
|
||||
|
@ -121,18 +121,19 @@ internal class ControlBarView : ConstraintLayout {
|
|||
|
||||
private fun accessibilityNonSelectableViews() = setOf(micToggle, cameraToggle)
|
||||
|
||||
private val alwaysOffSelectedAccessibilityDelegate = object : AccessibilityDelegateCompat() {
|
||||
override fun onInitializeAccessibilityNodeInfo(
|
||||
host: View,
|
||||
info: AccessibilityNodeInfoCompat
|
||||
) {
|
||||
super.onInitializeAccessibilityNodeInfo(host, info)
|
||||
if (host in accessibilityNonSelectableViews()) {
|
||||
// From an accessibility standpoint these views are never "selected"
|
||||
info.isSelected = false
|
||||
private val alwaysOffSelectedAccessibilityDelegate =
|
||||
object : AccessibilityDelegateCompat() {
|
||||
override fun onInitializeAccessibilityNodeInfo(
|
||||
host: View,
|
||||
info: AccessibilityNodeInfoCompat,
|
||||
) {
|
||||
super.onInitializeAccessibilityNodeInfo(host, info)
|
||||
if (host in accessibilityNonSelectableViews()) {
|
||||
// From an accessibility standpoint these views are never "selected"
|
||||
info.isSelected = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupAccessibility() {
|
||||
ViewCompat.setAccessibilityDelegate(
|
||||
|
@ -141,7 +142,7 @@ internal class ControlBarView : ConstraintLayout {
|
|||
override fun onRequestSendAccessibilityEvent(
|
||||
host: ViewGroup,
|
||||
child: View,
|
||||
event: AccessibilityEvent
|
||||
event: AccessibilityEvent,
|
||||
): Boolean {
|
||||
if (child in accessibilityNonSelectableViews() && event.eventType == AccessibilityEvent.TYPE_VIEW_SELECTED) {
|
||||
// We don't want Accessibility TalkBock to read out the "Selected" status of
|
||||
|
@ -151,7 +152,7 @@ internal class ControlBarView : ConstraintLayout {
|
|||
}
|
||||
return super.onRequestSendAccessibilityEvent(host, child, event)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
ViewCompat.setAccessibilityDelegate(micToggle, alwaysOffSelectedAccessibilityDelegate)
|
||||
ViewCompat.setAccessibilityDelegate(cameraToggle, alwaysOffSelectedAccessibilityDelegate)
|
||||
|
@ -206,18 +207,18 @@ internal class ControlBarView : ConstraintLayout {
|
|||
when (audioDeviceSelectionStatus) {
|
||||
AudioDeviceSelectionStatus.SPEAKER_SELECTED -> {
|
||||
callAudioDeviceButton.setImageResource(
|
||||
R.drawable.azure_communication_ui_calling_speaker_speakerphone_selector
|
||||
R.drawable.azure_communication_ui_calling_speaker_speakerphone_selector,
|
||||
)
|
||||
}
|
||||
AudioDeviceSelectionStatus.RECEIVER_SELECTED -> {
|
||||
callAudioDeviceButton.setImageResource(
|
||||
R.drawable.azure_communication_ui_calling_speaker_receiver_selector
|
||||
R.drawable.azure_communication_ui_calling_speaker_receiver_selector,
|
||||
)
|
||||
}
|
||||
AudioDeviceSelectionStatus.BLUETOOTH_SCO_SELECTED -> {
|
||||
callAudioDeviceButton.setImageResource(
|
||||
// Needs an icon
|
||||
R.drawable.azure_communication_ui_calling_speaker_bluetooth_selector
|
||||
R.drawable.azure_communication_ui_calling_speaker_bluetooth_selector,
|
||||
)
|
||||
}
|
||||
else -> {}
|
||||
|
|
|
@ -5,10 +5,10 @@ package com.azure.android.communication.ui.calling.presentation.fragment.calling
|
|||
|
||||
import com.azure.android.communication.ui.calling.redux.action.Action
|
||||
import com.azure.android.communication.ui.calling.redux.action.LocalParticipantAction
|
||||
import com.azure.android.communication.ui.calling.redux.state.CallingState
|
||||
import com.azure.android.communication.ui.calling.redux.state.AudioDeviceSelectionStatus
|
||||
import com.azure.android.communication.ui.calling.redux.state.AudioOperationalStatus
|
||||
import com.azure.android.communication.ui.calling.redux.state.AudioState
|
||||
import com.azure.android.communication.ui.calling.redux.state.CallingState
|
||||
import com.azure.android.communication.ui.calling.redux.state.CallingStatus
|
||||
import com.azure.android.communication.ui.calling.redux.state.CameraState
|
||||
import com.azure.android.communication.ui.calling.redux.state.PermissionState
|
||||
|
|
|
@ -22,9 +22,8 @@ import kotlinx.coroutines.launch
|
|||
@SuppressLint("ViewConstructor")
|
||||
internal class MoreCallOptionsListView(
|
||||
context: Context,
|
||||
private val viewModel: MoreCallOptionsListViewModel
|
||||
private val viewModel: MoreCallOptionsListViewModel,
|
||||
) : RelativeLayout(context) {
|
||||
|
||||
private var recyclerView: RecyclerView
|
||||
private lateinit var menuDrawer: DrawerDialog
|
||||
private lateinit var bottomCellAdapter: BottomCellAdapter
|
||||
|
@ -67,41 +66,44 @@ internal class MoreCallOptionsListView(
|
|||
recyclerView.layoutManager = LinearLayoutManager(context)
|
||||
}
|
||||
|
||||
private val bottomCellItems get() = viewModel.listEntries.map { entry ->
|
||||
BottomCellItem(
|
||||
icon = ContextCompat.getDrawable(
|
||||
context,
|
||||
entry.icon ?: android.R.drawable.ic_dialog_alert
|
||||
),
|
||||
title = context.getString(entry.title),
|
||||
contentDescription = null,
|
||||
accessoryImage = null,
|
||||
accessoryColor = null,
|
||||
accessoryImageDescription = null,
|
||||
isChecked = false,
|
||||
participantViewData = null,
|
||||
isOnHold = false,
|
||||
onClickAction =
|
||||
{
|
||||
when (entry) {
|
||||
MoreCallOptionsListViewModel.Companion.Entries.SHARE_DIAGNOSTICS -> shareDiagnostics(context)
|
||||
MoreCallOptionsListViewModel.Companion.Entries.REPORT_ISSUE -> viewModel.requestReportIssueScreen()
|
||||
}
|
||||
menuDrawer.dismissDialog()
|
||||
}
|
||||
)
|
||||
}
|
||||
private val bottomCellItems get() =
|
||||
viewModel.listEntries.map { entry ->
|
||||
BottomCellItem(
|
||||
icon =
|
||||
ContextCompat.getDrawable(
|
||||
context,
|
||||
entry.icon ?: android.R.drawable.ic_dialog_alert,
|
||||
),
|
||||
title = context.getString(entry.title),
|
||||
contentDescription = null,
|
||||
accessoryImage = null,
|
||||
accessoryColor = null,
|
||||
accessoryImageDescription = null,
|
||||
isChecked = false,
|
||||
participantViewData = null,
|
||||
isOnHold = false,
|
||||
onClickAction =
|
||||
{
|
||||
when (entry) {
|
||||
MoreCallOptionsListViewModel.Companion.Entries.SHARE_DIAGNOSTICS -> shareDiagnostics(context)
|
||||
MoreCallOptionsListViewModel.Companion.Entries.REPORT_ISSUE -> viewModel.requestReportIssueScreen()
|
||||
}
|
||||
menuDrawer.dismissDialog()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun shareDiagnostics(context: Context) {
|
||||
val share = Intent.createChooser(
|
||||
Intent().apply {
|
||||
action = Intent.ACTION_SEND
|
||||
putExtra(Intent.EXTRA_TEXT, viewModel.callId)
|
||||
type = "text/plain"
|
||||
putExtra(Intent.EXTRA_TITLE, context.getString(R.string.azure_communication_ui_calling_view_share_diagnostics_title))
|
||||
},
|
||||
null
|
||||
)
|
||||
val share =
|
||||
Intent.createChooser(
|
||||
Intent().apply {
|
||||
action = Intent.ACTION_SEND
|
||||
putExtra(Intent.EXTRA_TEXT, viewModel.callId)
|
||||
type = "text/plain"
|
||||
putExtra(Intent.EXTRA_TITLE, context.getString(R.string.azure_communication_ui_calling_view_share_diagnostics_title))
|
||||
},
|
||||
null,
|
||||
)
|
||||
context.startActivity(share)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||
internal class MoreCallOptionsListViewModel(
|
||||
private val debugInfoManager: DebugInfoManager,
|
||||
private val showSupportFormOption: Boolean,
|
||||
private val dispatch: Dispatch
|
||||
private val dispatch: Dispatch,
|
||||
) {
|
||||
private val unknown = "UNKNOWN"
|
||||
val callId: String
|
||||
|
@ -23,12 +23,13 @@ internal class MoreCallOptionsListViewModel(
|
|||
|
||||
val displayStateFlow = MutableStateFlow(false)
|
||||
|
||||
val listEntries = mutableListOf<Entries>().apply {
|
||||
add(Entries.SHARE_DIAGNOSTICS)
|
||||
if (showSupportFormOption) {
|
||||
add(Entries.REPORT_ISSUE)
|
||||
val listEntries =
|
||||
mutableListOf<Entries>().apply {
|
||||
add(Entries.SHARE_DIAGNOSTICS)
|
||||
if (showSupportFormOption) {
|
||||
add(Entries.REPORT_ISSUE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun display() {
|
||||
displayStateFlow.value = true
|
||||
|
@ -43,14 +44,14 @@ internal class MoreCallOptionsListViewModel(
|
|||
}
|
||||
|
||||
companion object {
|
||||
enum class Entries(val title: Int, val icon: Int?,) {
|
||||
enum class Entries(val title: Int, val icon: Int?) {
|
||||
SHARE_DIAGNOSTICS(
|
||||
R.string.azure_communication_ui_calling_view_share_diagnostics,
|
||||
R.drawable.azure_communication_ui_calling_ic_fluent_share_android_24_regular
|
||||
R.drawable.azure_communication_ui_calling_ic_fluent_share_android_24_regular,
|
||||
),
|
||||
REPORT_ISSUE(
|
||||
R.string.azure_communication_ui_calling_report_issue_title,
|
||||
R.drawable.azure_communication_ui_calling_ic_fluent_person_feedback_24_regular
|
||||
R.drawable.azure_communication_ui_calling_ic_fluent_person_feedback_24_regular,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,6 @@ internal class LeaveConfirmView(
|
|||
private val viewModel: LeaveConfirmViewModel,
|
||||
context: Context,
|
||||
) : RelativeLayout(context) {
|
||||
|
||||
private var leaveConfirmMenuTable: RecyclerView
|
||||
private lateinit var leaveConfirmMenuDrawer: DrawerDialog
|
||||
private lateinit var bottomCellAdapter: BottomCellAdapter
|
||||
|
@ -47,9 +46,7 @@ internal class LeaveConfirmView(
|
|||
removeAllViews()
|
||||
}
|
||||
|
||||
fun start(
|
||||
viewLifecycleOwner: LifecycleOwner
|
||||
) {
|
||||
fun start(viewLifecycleOwner: LifecycleOwner) {
|
||||
bottomCellAdapter = BottomCellAdapter()
|
||||
bottomCellAdapter.setBottomCellItems(bottomCellItems)
|
||||
leaveConfirmMenuTable.adapter = bottomCellAdapter
|
||||
|
@ -85,66 +82,66 @@ internal class LeaveConfirmView(
|
|||
|
||||
private val bottomCellItems: List<BottomCellItem>
|
||||
get() {
|
||||
val bottomCellItems = mutableListOf(
|
||||
// Leave title
|
||||
BottomCellItem(
|
||||
null,
|
||||
context.getString(R.string.azure_communication_ui_calling_view_leave_call),
|
||||
context.getString(R.string.azure_communication_ui_calling_view_leave_confirm_menu),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
BottomCellItemType.BottomMenuTitle,
|
||||
null
|
||||
),
|
||||
|
||||
// Leave
|
||||
BottomCellItem(
|
||||
ContextCompat.getDrawable(
|
||||
context,
|
||||
R.drawable.azure_communication_ui_calling_leave_confirm_telephone_24
|
||||
val bottomCellItems =
|
||||
mutableListOf(
|
||||
// Leave title
|
||||
BottomCellItem(
|
||||
null,
|
||||
context.getString(R.string.azure_communication_ui_calling_view_leave_call),
|
||||
context.getString(R.string.azure_communication_ui_calling_view_leave_confirm_menu),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
BottomCellItemType.BottomMenuTitle,
|
||||
null,
|
||||
),
|
||||
context.getString(R.string.azure_communication_ui_calling_view_leave_call_button_text),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
onClickAction = {
|
||||
viewModel.confirm()
|
||||
}
|
||||
),
|
||||
|
||||
// Cancel
|
||||
BottomCellItem(
|
||||
ContextCompat.getDrawable(
|
||||
context,
|
||||
R.drawable.azure_communication_ui_calling_leave_confirm_dismiss_24
|
||||
// Leave
|
||||
BottomCellItem(
|
||||
ContextCompat.getDrawable(
|
||||
context,
|
||||
R.drawable.azure_communication_ui_calling_leave_confirm_telephone_24,
|
||||
),
|
||||
context.getString(R.string.azure_communication_ui_calling_view_leave_call_button_text),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
onClickAction = {
|
||||
viewModel.confirm()
|
||||
},
|
||||
),
|
||||
// Cancel
|
||||
BottomCellItem(
|
||||
ContextCompat.getDrawable(
|
||||
context,
|
||||
R.drawable.azure_communication_ui_calling_leave_confirm_dismiss_24,
|
||||
),
|
||||
context.getString(R.string.azure_communication_ui_calling_view_leave_call_cancel),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
onClickAction = {
|
||||
cancelLeaveConfirm()
|
||||
},
|
||||
),
|
||||
context.getString(R.string.azure_communication_ui_calling_view_leave_call_cancel),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
onClickAction = {
|
||||
cancelLeaveConfirm()
|
||||
},
|
||||
)
|
||||
)
|
||||
return bottomCellItems
|
||||
}
|
||||
|
||||
class AccessibilityManipulatingLinearLayoutManager(context: Context) : LinearLayoutManager(context) {
|
||||
override fun getRowCountForAccessibility(
|
||||
recycler: RecyclerView.Recycler,
|
||||
state: RecyclerView.State
|
||||
state: RecyclerView.State,
|
||||
): Int {
|
||||
return max(super.getRowCountForAccessibility(recycler, state) - 1, 0)
|
||||
}
|
||||
|
@ -153,12 +150,20 @@ internal class LeaveConfirmView(
|
|||
recycler: RecyclerView.Recycler,
|
||||
state: RecyclerView.State,
|
||||
host: View,
|
||||
info: AccessibilityNodeInfoCompat
|
||||
info: AccessibilityNodeInfoCompat,
|
||||
) {
|
||||
super.onInitializeAccessibilityNodeInfoForItem(recycler, state, host, info)
|
||||
try {
|
||||
info?.let {
|
||||
val itemInfo = AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(max(info.collectionItemInfo.rowIndex - 1, 0), info.collectionItemInfo.rowSpan, info.collectionItemInfo.columnIndex, info.collectionItemInfo.columnSpan, info.collectionItemInfo.isHeading, info.collectionItemInfo.isSelected)
|
||||
val itemInfo =
|
||||
AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(
|
||||
max(info.collectionItemInfo.rowIndex - 1, 0),
|
||||
info.collectionItemInfo.rowSpan,
|
||||
info.collectionItemInfo.columnIndex,
|
||||
info.collectionItemInfo.columnSpan,
|
||||
info.collectionItemInfo.isHeading,
|
||||
info.collectionItemInfo.isSelected,
|
||||
)
|
||||
if (info.collectionItemInfo.rowIndex == 0) {
|
||||
info.setCollectionItemInfo(null)
|
||||
} else {
|
||||
|
|
|
@ -57,7 +57,7 @@ internal class InfoHeaderView : ConstraintLayout {
|
|||
viewLifecycleOwner: LifecycleOwner,
|
||||
infoHeaderViewModel: InfoHeaderViewModel,
|
||||
displayParticipantList: () -> Unit,
|
||||
accessibilityEnabled: Boolean
|
||||
accessibilityEnabled: Boolean,
|
||||
) {
|
||||
this.infoHeaderViewModel = infoHeaderViewModel
|
||||
this.displayParticipantListCallback = displayParticipantList
|
||||
|
@ -78,16 +78,18 @@ internal class InfoHeaderView : ConstraintLayout {
|
|||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
infoHeaderViewModel.getNumberOfParticipantsFlow().collect {
|
||||
participantNumberText.text = when (it) {
|
||||
0 -> context.getString(R.string.azure_communication_ui_calling_view_info_header_waiting_for_others_to_join)
|
||||
participantNumberText.text =
|
||||
when (it) {
|
||||
0 -> context.getString(R.string.azure_communication_ui_calling_view_info_header_waiting_for_others_to_join)
|
||||
|
||||
1 -> context.getString(R.string.azure_communication_ui_calling_view_info_header_call_with_1_person)
|
||||
1 -> context.getString(R.string.azure_communication_ui_calling_view_info_header_call_with_1_person)
|
||||
|
||||
else -> resources.getString(
|
||||
R.string.azure_communication_ui_calling_view_info_header_call_with_n_people,
|
||||
it
|
||||
)
|
||||
}
|
||||
else ->
|
||||
resources.getString(
|
||||
R.string.azure_communication_ui_calling_view_info_header_call_with_n_people,
|
||||
it,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -27,9 +27,7 @@ internal class InfoHeaderViewModel(val multitaskingEnabled: Boolean) {
|
|||
return numberOfParticipantsFlow
|
||||
}
|
||||
|
||||
fun update(
|
||||
numberOfRemoteParticipants: Int,
|
||||
) {
|
||||
fun update(numberOfRemoteParticipants: Int) {
|
||||
numberOfParticipantsFlow.value = numberOfRemoteParticipants
|
||||
if (!displayedOnLaunch) {
|
||||
displayedOnLaunch = true
|
||||
|
@ -68,7 +66,7 @@ internal class InfoHeaderViewModel(val multitaskingEnabled: Boolean) {
|
|||
displayFloatingHeaderFlow.value = false
|
||||
}
|
||||
},
|
||||
3000
|
||||
3000,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -47,10 +47,11 @@ internal class OnHoldOverlayView : LinearLayout {
|
|||
findViewById(R.id.azure_communication_ui_call_hold_overlay_wait_for_host_image)
|
||||
overlayTitle = findViewById(R.id.azure_communication_ui_call_hold_overlay_title)
|
||||
resumeButton = findViewById(R.id.azure_communication_ui_call_hold_resume_button)
|
||||
resumeButton.background = ContextCompat.getDrawable(
|
||||
context,
|
||||
R.drawable.azure_communication_ui_calling_corner_radius_rectangle_4dp_primary_background
|
||||
)
|
||||
resumeButton.background =
|
||||
ContextCompat.getDrawable(
|
||||
context,
|
||||
R.drawable.azure_communication_ui_calling_corner_radius_rectangle_4dp_primary_background,
|
||||
)
|
||||
}
|
||||
|
||||
fun start(
|
||||
|
@ -83,7 +84,7 @@ internal class OnHoldOverlayView : LinearLayout {
|
|||
info.removeAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK)
|
||||
info.isClickable = false
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
initSnackBar()
|
||||
|
@ -124,45 +125,47 @@ internal class OnHoldOverlayView : LinearLayout {
|
|||
}
|
||||
|
||||
private fun initSnackBar() {
|
||||
snackBar = Snackbar.make(
|
||||
rootView,
|
||||
"",
|
||||
Snackbar.LENGTH_INDEFINITE,
|
||||
Snackbar.Style.REGULAR
|
||||
).apply {
|
||||
animationMode = BaseTransientBottomBar.ANIMATION_MODE_FADE
|
||||
setAction(rootView.context!!.getText(R.string.azure_communication_ui_calling_snack_bar_button_dismiss)) {
|
||||
}
|
||||
if (context.resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
|
||||
anchorView =
|
||||
rootView.findViewById(R.id.azure_communication_ui_call_call_buttons)
|
||||
}
|
||||
view.background.colorFilter = PorterDuffColorFilter(
|
||||
ContextCompat.getColor(
|
||||
rootView.context,
|
||||
R.color.azure_communication_ui_calling_color_snack_bar_background
|
||||
),
|
||||
PorterDuff.Mode.SRC_IN
|
||||
)
|
||||
snackBarTextView = view.findViewById(R.id.snackbar_text)
|
||||
snackBarTextView.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
rootView.context,
|
||||
R.color.azure_communication_ui_calling_color_snack_bar_text_color
|
||||
)
|
||||
)
|
||||
view.findViewById<AppCompatButton>(R.id.snackbar_action).apply {
|
||||
setTextColor(
|
||||
snackBar =
|
||||
Snackbar.make(
|
||||
rootView,
|
||||
"",
|
||||
Snackbar.LENGTH_INDEFINITE,
|
||||
Snackbar.Style.REGULAR,
|
||||
).apply {
|
||||
animationMode = BaseTransientBottomBar.ANIMATION_MODE_FADE
|
||||
setAction(rootView.context!!.getText(R.string.azure_communication_ui_calling_snack_bar_button_dismiss)) {
|
||||
}
|
||||
if (context.resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
|
||||
anchorView =
|
||||
rootView.findViewById(R.id.azure_communication_ui_call_call_buttons)
|
||||
}
|
||||
view.background.colorFilter =
|
||||
PorterDuffColorFilter(
|
||||
ContextCompat.getColor(
|
||||
rootView.context,
|
||||
R.color.azure_communication_ui_calling_color_snack_bar_background,
|
||||
),
|
||||
PorterDuff.Mode.SRC_IN,
|
||||
)
|
||||
snackBarTextView = view.findViewById(R.id.snackbar_text)
|
||||
snackBarTextView.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
rootView.context,
|
||||
R.color.azure_communication_ui_calling_color_snack_bar_text_color
|
||||
)
|
||||
R.color.azure_communication_ui_calling_color_snack_bar_text_color,
|
||||
),
|
||||
)
|
||||
isAllCaps = false
|
||||
contentDescription =
|
||||
rootView.context.getText(R.string.azure_communication_ui_calling_snack_bar_button_dismiss)
|
||||
view.findViewById<AppCompatButton>(R.id.snackbar_action).apply {
|
||||
setTextColor(
|
||||
ContextCompat.getColor(
|
||||
rootView.context,
|
||||
R.color.azure_communication_ui_calling_color_snack_bar_text_color,
|
||||
),
|
||||
)
|
||||
isAllCaps = false
|
||||
contentDescription =
|
||||
rootView.context.getText(R.string.azure_communication_ui_calling_snack_bar_button_dismiss)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupUi() {
|
||||
|
|
|
@ -15,6 +15,7 @@ internal class OnHoldOverlayViewModel(private val dispatch: (Action) -> Unit) {
|
|||
private lateinit var displayMicUsedToast: MutableStateFlow<Boolean>
|
||||
|
||||
fun getDisplayHoldOverlayFlow(): StateFlow<Boolean> = displayHoldOverlayFlow
|
||||
|
||||
fun getDisplayMicUsedToastStateFlow(): StateFlow<Boolean> = displayMicUsedToast
|
||||
|
||||
fun init(
|
||||
|
@ -39,6 +40,5 @@ internal class OnHoldOverlayViewModel(private val dispatch: (Action) -> Unit) {
|
|||
dispatch(AudioSessionAction.AudioFocusRequesting())
|
||||
}
|
||||
|
||||
private fun shouldDisplayHoldOverlay(callingStatus: CallingStatus) =
|
||||
callingStatus == CallingStatus.LOCAL_HOLD
|
||||
private fun shouldDisplayHoldOverlay(callingStatus: CallingStatus) = callingStatus == CallingStatus.LOCAL_HOLD
|
||||
}
|
||||
|
|
|
@ -54,12 +54,15 @@ internal class ConnectingLobbyOverlayView : LinearLayout {
|
|||
ViewCompat.setAccessibilityDelegate(
|
||||
this,
|
||||
object : AccessibilityDelegateCompat() {
|
||||
override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfoCompat) {
|
||||
override fun onInitializeAccessibilityNodeInfo(
|
||||
host: View,
|
||||
info: AccessibilityNodeInfoCompat,
|
||||
) {
|
||||
super.onInitializeAccessibilityNodeInfo(host, info)
|
||||
info.removeAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK)
|
||||
info.isClickable = false
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,8 +6,8 @@ package com.azure.android.communication.ui.calling.presentation.fragment.calling
|
|||
import com.azure.android.communication.ui.calling.error.ErrorCode
|
||||
import com.azure.android.communication.ui.calling.error.FatalError
|
||||
import com.azure.android.communication.ui.calling.presentation.manager.NetworkManager
|
||||
import com.azure.android.communication.ui.calling.redux.action.ErrorAction
|
||||
import com.azure.android.communication.ui.calling.redux.action.Action
|
||||
import com.azure.android.communication.ui.calling.redux.action.ErrorAction
|
||||
import com.azure.android.communication.ui.calling.redux.action.PermissionAction
|
||||
import com.azure.android.communication.ui.calling.redux.state.AudioOperationalStatus
|
||||
import com.azure.android.communication.ui.calling.redux.state.AudioState
|
||||
|
@ -22,7 +22,6 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
internal class ConnectingLobbyOverlayViewModel(private val dispatch: (Action) -> Unit) {
|
||||
|
||||
private lateinit var displayLobbyOverlayFlow: MutableStateFlow<Boolean>
|
||||
private lateinit var networkManager: NetworkManager
|
||||
|
||||
|
@ -76,12 +75,13 @@ internal class ConnectingLobbyOverlayViewModel(private val dispatch: (Action) ->
|
|||
|
||||
fun handleMicrophoneAccessFailed() {
|
||||
dispatchAction(
|
||||
action = ErrorAction.FatalErrorOccurred(
|
||||
FatalError(
|
||||
Throwable(),
|
||||
ErrorCode.MICROPHONE_NOT_AVAILABLE
|
||||
)
|
||||
)
|
||||
action =
|
||||
ErrorAction.FatalErrorOccurred(
|
||||
FatalError(
|
||||
Throwable(),
|
||||
ErrorCode.MICROPHONE_NOT_AVAILABLE,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -93,20 +93,23 @@ internal class ConnectingLobbyOverlayViewModel(private val dispatch: (Action) ->
|
|||
dispatch(action)
|
||||
}
|
||||
|
||||
private fun shouldDisplayLobbyOverlay(callingState: CallingState, permissionState: PermissionState) =
|
||||
((callingState.callingStatus == CallingStatus.NONE) || (callingState.callingStatus == CallingStatus.CONNECTING)) &&
|
||||
(permissionState.audioPermissionState != PermissionStatus.DENIED) &&
|
||||
(callingState.operationStatus == OperationStatus.SKIP_SETUP_SCREEN)
|
||||
private fun shouldDisplayLobbyOverlay(
|
||||
callingState: CallingState,
|
||||
permissionState: PermissionState,
|
||||
) = ((callingState.callingStatus == CallingStatus.NONE) || (callingState.callingStatus == CallingStatus.CONNECTING)) &&
|
||||
(permissionState.audioPermissionState != PermissionStatus.DENIED) &&
|
||||
(callingState.operationStatus == OperationStatus.SKIP_SETUP_SCREEN)
|
||||
|
||||
private fun handleOffline(networkManager: NetworkManager) {
|
||||
if (!networkManager.isNetworkConnectionAvailable()) {
|
||||
dispatchAction(
|
||||
action = ErrorAction.FatalErrorOccurred(
|
||||
FatalError(
|
||||
Throwable(),
|
||||
ErrorCode.INTERNET_NOT_AVAILABLE
|
||||
)
|
||||
)
|
||||
action =
|
||||
ErrorAction.FatalErrorOccurred(
|
||||
FatalError(
|
||||
Throwable(),
|
||||
ErrorCode.INTERNET_NOT_AVAILABLE,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -114,12 +117,13 @@ internal class ConnectingLobbyOverlayViewModel(private val dispatch: (Action) ->
|
|||
private fun handlePermissionDeniedEvent(permissionState: PermissionState) {
|
||||
if (permissionState.audioPermissionState == PermissionStatus.DENIED) {
|
||||
dispatchAction(
|
||||
action = ErrorAction.FatalErrorOccurred(
|
||||
FatalError(
|
||||
Throwable(),
|
||||
ErrorCode.MIC_PERMISSION_DENIED
|
||||
)
|
||||
)
|
||||
action =
|
||||
ErrorAction.FatalErrorOccurred(
|
||||
FatalError(
|
||||
Throwable(),
|
||||
ErrorCode.MIC_PERMISSION_DENIED,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ internal class LobbyErrorHeaderView : ConstraintLayout {
|
|||
|
||||
fun start(
|
||||
viewLifecycleOwner: LifecycleOwner,
|
||||
lobbyErrorHeaderViewModel: LobbyErrorHeaderViewModel
|
||||
lobbyErrorHeaderViewModel: LobbyErrorHeaderViewModel,
|
||||
) {
|
||||
this.lobbyErrorHeaderViewModel = lobbyErrorHeaderViewModel
|
||||
|
||||
|
@ -69,10 +69,22 @@ internal class LobbyErrorHeaderView : ConstraintLayout {
|
|||
|
||||
private fun getLobbyErrorMessage(it: CallCompositeLobbyErrorCode?): String {
|
||||
return when (it) {
|
||||
CallCompositeLobbyErrorCode.LOBBY_DISABLED_BY_CONFIGURATIONS -> context.getString(R.string.azure_communication_ui_calling_error_lobby_disabled_by_configuration)
|
||||
CallCompositeLobbyErrorCode.LOBBY_CONVERSATION_TYPE_NOT_SUPPORTED -> context.getString(R.string.azure_communication_ui_calling_error_lobby_conversation_type_not_supported)
|
||||
CallCompositeLobbyErrorCode.LOBBY_MEETING_ROLE_NOT_ALLOWED -> context.getString(R.string.azure_communication_ui_calling_error_lobby_meeting_role_not_allowded)
|
||||
CallCompositeLobbyErrorCode.REMOVE_PARTICIPANT_OPERATION_FAILURE -> context.getString(R.string.azure_communication_ui_calling_error_lobby_failed_to_remove_participant)
|
||||
CallCompositeLobbyErrorCode.LOBBY_DISABLED_BY_CONFIGURATIONS ->
|
||||
context.getString(
|
||||
R.string.azure_communication_ui_calling_error_lobby_disabled_by_configuration,
|
||||
)
|
||||
CallCompositeLobbyErrorCode.LOBBY_CONVERSATION_TYPE_NOT_SUPPORTED ->
|
||||
context.getString(
|
||||
R.string.azure_communication_ui_calling_error_lobby_conversation_type_not_supported,
|
||||
)
|
||||
CallCompositeLobbyErrorCode.LOBBY_MEETING_ROLE_NOT_ALLOWED ->
|
||||
context.getString(
|
||||
R.string.azure_communication_ui_calling_error_lobby_meeting_role_not_allowded,
|
||||
)
|
||||
CallCompositeLobbyErrorCode.REMOVE_PARTICIPANT_OPERATION_FAILURE ->
|
||||
context.getString(
|
||||
R.string.azure_communication_ui_calling_error_lobby_failed_to_remove_participant,
|
||||
)
|
||||
else -> context.getString(R.string.azure_communication_ui_calling_error_lobby_unknown)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ internal class LobbyErrorHeaderViewModel(private val dispatch: (Action) -> Unit)
|
|||
fun update(
|
||||
callingStatus: CallingStatus,
|
||||
error: CallCompositeLobbyErrorCode?,
|
||||
canShowLobby: Boolean
|
||||
canShowLobby: Boolean,
|
||||
) {
|
||||
displayLobbyErrorHeaderFlow.value = error != null &&
|
||||
callingStatus == CallingStatus.CONNECTED &&
|
||||
|
@ -32,13 +32,14 @@ internal class LobbyErrorHeaderViewModel(private val dispatch: (Action) -> Unit)
|
|||
fun init(
|
||||
callingStatus: CallingStatus,
|
||||
error: CallCompositeLobbyErrorCode?,
|
||||
canShowLobby: Boolean
|
||||
canShowLobby: Boolean,
|
||||
) {
|
||||
displayLobbyErrorHeaderFlow = MutableStateFlow(
|
||||
error != null &&
|
||||
callingStatus == CallingStatus.CONNECTED &&
|
||||
canShowLobby
|
||||
)
|
||||
displayLobbyErrorHeaderFlow =
|
||||
MutableStateFlow(
|
||||
error != null &&
|
||||
callingStatus == CallingStatus.CONNECTED &&
|
||||
canShowLobby,
|
||||
)
|
||||
lobbyErrorFlow = MutableStateFlow(error)
|
||||
}
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@ internal class LobbyHeaderView : ConstraintLayout {
|
|||
fun start(
|
||||
viewLifecycleOwner: LifecycleOwner,
|
||||
lobbyHeaderViewModel: LobbyHeaderViewModel,
|
||||
displayParticipantList: () -> Unit
|
||||
displayParticipantList: () -> Unit,
|
||||
) {
|
||||
this.lobbyHeaderViewModel = lobbyHeaderViewModel
|
||||
this.displayParticipantListCallback = displayParticipantList
|
||||
|
|
|
@ -31,16 +31,15 @@ internal class LobbyHeaderViewModel {
|
|||
canShowLobby: Boolean,
|
||||
) {
|
||||
var isNewLobbyParticipantAdded = isNewParticipantAdded(lobbyParticipants)
|
||||
displayLobbyHeaderFlow = MutableStateFlow(
|
||||
lobbyParticipants.isNotEmpty() &&
|
||||
(isNewLobbyParticipantAdded || displayLobbyHeaderFlow.value) &&
|
||||
callingStatus == CallingStatus.CONNECTED && canShowLobby
|
||||
)
|
||||
displayLobbyHeaderFlow =
|
||||
MutableStateFlow(
|
||||
lobbyParticipants.isNotEmpty() &&
|
||||
(isNewLobbyParticipantAdded || displayLobbyHeaderFlow.value) &&
|
||||
callingStatus == CallingStatus.CONNECTED && canShowLobby,
|
||||
)
|
||||
}
|
||||
|
||||
private fun isNewParticipantAdded(
|
||||
lobbyParticipants: Map<String, ParticipantInfoModel>
|
||||
): Boolean {
|
||||
private fun isNewParticipantAdded(lobbyParticipants: Map<String, ParticipantInfoModel>): Boolean {
|
||||
var isNewLobbyParticipantAdded = false
|
||||
if (lobbyParticipantsCache.size < lobbyParticipants.size) {
|
||||
isNewLobbyParticipantAdded = true
|
||||
|
|
|
@ -51,12 +51,15 @@ internal class WaitingLobbyOverlayView : LinearLayout {
|
|||
ViewCompat.setAccessibilityDelegate(
|
||||
this,
|
||||
object : AccessibilityDelegateCompat() {
|
||||
override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfoCompat) {
|
||||
override fun onInitializeAccessibilityNodeInfo(
|
||||
host: View,
|
||||
info: AccessibilityNodeInfoCompat,
|
||||
) {
|
||||
super.onInitializeAccessibilityNodeInfo(host, info)
|
||||
info.removeAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK)
|
||||
info.isClickable = false
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -12,20 +12,15 @@ internal class WaitingLobbyOverlayViewModel {
|
|||
|
||||
fun getDisplayLobbyOverlayFlow(): StateFlow<Boolean> = displayLobbyOverlayFlow
|
||||
|
||||
fun init(
|
||||
callingState: CallingStatus,
|
||||
) {
|
||||
fun init(callingState: CallingStatus) {
|
||||
val displayLobbyOverlay = shouldDisplayLobbyOverlay(callingState)
|
||||
displayLobbyOverlayFlow = MutableStateFlow(displayLobbyOverlay)
|
||||
}
|
||||
|
||||
fun update(
|
||||
callingState: CallingStatus,
|
||||
) {
|
||||
fun update(callingState: CallingStatus) {
|
||||
val displayLobbyOverlay = shouldDisplayLobbyOverlay(callingState)
|
||||
displayLobbyOverlayFlow.value = displayLobbyOverlay
|
||||
}
|
||||
|
||||
private fun shouldDisplayLobbyOverlay(callingStatus: CallingStatus) =
|
||||
callingStatus == CallingStatus.IN_LOBBY
|
||||
private fun shouldDisplayLobbyOverlay(callingStatus: CallingStatus) = callingStatus == CallingStatus.IN_LOBBY
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ import android.view.MotionEvent
|
|||
import android.view.View
|
||||
|
||||
internal class DragTouchListener internal constructor() : View.OnTouchListener {
|
||||
|
||||
private var curX = 0f
|
||||
private var curY = 0f
|
||||
private var startX = 0f
|
||||
|
@ -25,7 +24,10 @@ internal class DragTouchListener internal constructor() : View.OnTouchListener {
|
|||
view.translationY = 0F
|
||||
}
|
||||
|
||||
override fun onTouch(view: View, event: MotionEvent): Boolean {
|
||||
override fun onTouch(
|
||||
view: View,
|
||||
event: MotionEvent,
|
||||
): Boolean {
|
||||
when (event.action) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
startX = event.rawX
|
||||
|
|
|
@ -27,7 +27,6 @@ import kotlinx.coroutines.flow.collect
|
|||
import kotlinx.coroutines.launch
|
||||
|
||||
internal class LocalParticipantView : ConstraintLayout {
|
||||
|
||||
constructor(context: Context) : super(context)
|
||||
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
|
||||
|
||||
|
@ -96,7 +95,6 @@ internal class LocalParticipantView : ConstraintLayout {
|
|||
videoViewManager: VideoViewManager,
|
||||
avatarViewManager: AvatarViewManager,
|
||||
) {
|
||||
|
||||
this.viewModel = viewModel
|
||||
this.videoViewManager = videoViewManager
|
||||
|
||||
|
@ -166,12 +164,13 @@ internal class LocalParticipantView : ConstraintLayout {
|
|||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewModel.getCameraDeviceSelectionFlow().collect { cameraDeviceSelectionStatus ->
|
||||
listOf(switchCameraButton, pipSwitchCameraButton).forEach {
|
||||
it.contentDescription = context.getString(
|
||||
when (cameraDeviceSelectionStatus) {
|
||||
CameraDeviceSelectionStatus.FRONT -> R.string.azure_communication_ui_calling_switch_camera_button_front
|
||||
else -> R.string.azure_communication_ui_calling_switch_camera_button_back
|
||||
}
|
||||
)
|
||||
it.contentDescription =
|
||||
context.getString(
|
||||
when (cameraDeviceSelectionStatus) {
|
||||
CameraDeviceSelectionStatus.FRONT -> R.string.azure_communication_ui_calling_switch_camera_button_front
|
||||
else -> R.string.azure_communication_ui_calling_switch_camera_button_back
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -181,12 +180,12 @@ internal class LocalParticipantView : ConstraintLayout {
|
|||
if (it) {
|
||||
ViewCompat.setImportantForAccessibility(
|
||||
switchCameraButton,
|
||||
ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
|
||||
ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS,
|
||||
)
|
||||
} else {
|
||||
ViewCompat.setImportantForAccessibility(
|
||||
switchCameraButton,
|
||||
ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES
|
||||
ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -225,10 +224,11 @@ internal class LocalParticipantView : ConstraintLayout {
|
|||
localParticipantPip.visibility =
|
||||
if (model.viewMode == LocalParticipantViewMode.SELFIE_PIP) View.VISIBLE else View.GONE
|
||||
|
||||
val videoHolder = when (model.viewMode) {
|
||||
LocalParticipantViewMode.SELFIE_PIP -> localParticipantPipCameraHolder
|
||||
LocalParticipantViewMode.FULL_SCREEN -> localParticipantFullCameraHolder
|
||||
}
|
||||
val videoHolder =
|
||||
when (model.viewMode) {
|
||||
LocalParticipantViewMode.SELFIE_PIP -> localParticipantPipCameraHolder
|
||||
LocalParticipantViewMode.FULL_SCREEN -> localParticipantFullCameraHolder
|
||||
}
|
||||
|
||||
if (model.shouldDisplayVideo) {
|
||||
addVideoView(model.videoStreamID!!, videoHolder, model.viewMode)
|
||||
|
@ -238,20 +238,21 @@ internal class LocalParticipantView : ConstraintLayout {
|
|||
private fun addVideoView(
|
||||
videoStreamID: String,
|
||||
videoHolder: ConstraintLayout,
|
||||
viewMode: LocalParticipantViewMode
|
||||
viewMode: LocalParticipantViewMode,
|
||||
) {
|
||||
val scalingMode = if (isAndroidTV(context)) ScalingMode.FIT else ScalingMode.CROP
|
||||
|
||||
videoViewManager.getLocalVideoRenderer(
|
||||
videoStreamID,
|
||||
scalingMode
|
||||
scalingMode,
|
||||
)?.let { view ->
|
||||
view.background = this.context.let {
|
||||
ContextCompat.getDrawable(
|
||||
it,
|
||||
R.drawable.azure_communication_ui_calling_corner_radius_rectangle_4dp
|
||||
)
|
||||
}
|
||||
view.background =
|
||||
this.context.let {
|
||||
ContextCompat.getDrawable(
|
||||
it,
|
||||
R.drawable.azure_communication_ui_calling_corner_radius_rectangle_4dp,
|
||||
)
|
||||
}
|
||||
videoHolder.addView(view, 0)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,14 +34,20 @@ internal class LocalParticipantViewModel(
|
|||
private lateinit var isVisibleFlow: MutableStateFlow<Boolean>
|
||||
|
||||
fun getVideoStatusFlow(): StateFlow<VideoModel> = videoStatusFlow
|
||||
|
||||
fun getDisplayFullScreenAvatarFlow(): StateFlow<Boolean> = displayFullScreenAvatarFlow
|
||||
|
||||
fun getDisplayNameStateFlow(): StateFlow<String?> = displayNameStateFlow
|
||||
|
||||
fun getLocalUserMutedStateFlow(): StateFlow<Boolean> = localUserMutedStateFlow
|
||||
|
||||
fun getDisplaySwitchCameraButtonFlow(): StateFlow<Boolean> = displaySwitchCameraButtonFlow
|
||||
|
||||
fun getDisplayPipSwitchCameraButtonFlow(): StateFlow<Boolean> = displayPipSwitchCameraButtonFlow
|
||||
|
||||
fun getEnableCameraSwitchFlow(): StateFlow<Boolean> = enableCameraSwitchFlow
|
||||
fun getCameraDeviceSelectionFlow(): StateFlow<CameraDeviceSelectionStatus> =
|
||||
cameraDeviceSelectionFlow
|
||||
|
||||
fun getCameraDeviceSelectionFlow(): StateFlow<CameraDeviceSelectionStatus> = cameraDeviceSelectionFlow
|
||||
|
||||
fun getIsVisibleFlow(): StateFlow<Boolean> = isVisibleFlow
|
||||
|
||||
|
@ -85,7 +91,12 @@ internal class LocalParticipantViewModel(
|
|||
isVisibleFlow.value = isVisible(displayVideo, pipStatus, displayFullScreenAvatar, avMode)
|
||||
}
|
||||
|
||||
private fun isVisible(displayVideo: Boolean, pipStatus: PictureInPictureStatus, displayFullScreenAvatar: Boolean, avMode: CallCompositeAudioVideoMode): Boolean {
|
||||
private fun isVisible(
|
||||
displayVideo: Boolean,
|
||||
pipStatus: PictureInPictureStatus,
|
||||
displayFullScreenAvatar: Boolean,
|
||||
avMode: CallCompositeAudioVideoMode,
|
||||
): Boolean {
|
||||
if (avMode == CallCompositeAudioVideoMode.AUDIO_ONLY && !displayFullScreenAvatar) {
|
||||
return false
|
||||
}
|
||||
|
@ -112,7 +123,6 @@ internal class LocalParticipantViewModel(
|
|||
pipStatus: PictureInPictureStatus,
|
||||
avMode: CallCompositeAudioVideoMode,
|
||||
) {
|
||||
|
||||
val viewMode = getLocalParticipantViewMode(numberOfRemoteParticipants)
|
||||
val displayVideo = shouldDisplayVideo(videoStreamID)
|
||||
val displayLobbyOverlay = shouldDisplayLobbyOverlay(callingState)
|
||||
|
@ -128,13 +138,14 @@ internal class LocalParticipantViewModel(
|
|||
MutableStateFlow(
|
||||
displayVideo &&
|
||||
viewMode == LocalParticipantViewMode.FULL_SCREEN && camerasCount > 1 &&
|
||||
pipStatus == PictureInPictureStatus.VISIBLE
|
||||
pipStatus == PictureInPictureStatus.VISIBLE,
|
||||
)
|
||||
displayPipSwitchCameraButtonFlow =
|
||||
MutableStateFlow(displayVideo && viewMode == LocalParticipantViewMode.SELFIE_PIP && camerasCount > 1)
|
||||
enableCameraSwitchFlow = MutableStateFlow(
|
||||
cameraDeviceSelectionStatus != CameraDeviceSelectionStatus.SWITCHING
|
||||
)
|
||||
enableCameraSwitchFlow =
|
||||
MutableStateFlow(
|
||||
cameraDeviceSelectionStatus != CameraDeviceSelectionStatus.SWITCHING,
|
||||
)
|
||||
cameraDeviceSelectionFlow = MutableStateFlow(cameraDeviceSelectionStatus)
|
||||
isOverlayDisplayedFlow = MutableStateFlow(isOverlayDisplayed(callingState))
|
||||
numberOfRemoteParticipantsFlow = MutableStateFlow(numberOfRemoteParticipants)
|
||||
|
@ -157,15 +168,16 @@ internal class LocalParticipantViewModel(
|
|||
displayVideo: Boolean,
|
||||
displayLobbyOverlay: Boolean,
|
||||
viewMode: LocalParticipantViewMode,
|
||||
) =
|
||||
!displayVideo && viewMode == LocalParticipantViewMode.FULL_SCREEN && !displayLobbyOverlay
|
||||
) = !displayVideo && viewMode == LocalParticipantViewMode.FULL_SCREEN && !displayLobbyOverlay
|
||||
|
||||
private fun shouldDisplayLobbyOverlay(callingStatus: CallingStatus) =
|
||||
callingStatus == CallingStatus.IN_LOBBY
|
||||
private fun shouldDisplayLobbyOverlay(callingStatus: CallingStatus) = callingStatus == CallingStatus.IN_LOBBY
|
||||
|
||||
private fun getLocalParticipantViewMode(numberOfRemoteParticipants: Int): LocalParticipantViewMode {
|
||||
return if (numberOfRemoteParticipants > 0)
|
||||
LocalParticipantViewMode.SELFIE_PIP else LocalParticipantViewMode.FULL_SCREEN
|
||||
return if (numberOfRemoteParticipants > 0) {
|
||||
LocalParticipantViewMode.SELFIE_PIP
|
||||
} else {
|
||||
LocalParticipantViewMode.FULL_SCREEN
|
||||
}
|
||||
}
|
||||
|
||||
private fun isOverlayDisplayed(callingStatus: CallingStatus) =
|
||||
|
|
|
@ -41,7 +41,7 @@ internal class ToastNotificationView : ConstraintLayout {
|
|||
fun start(
|
||||
viewLifecycleOwner: LifecycleOwner,
|
||||
toastNotificationViewModel: ToastNotificationViewModel,
|
||||
accessibilityEnabled: Boolean
|
||||
accessibilityEnabled: Boolean,
|
||||
) {
|
||||
this.toastNotificationViewModel = toastNotificationViewModel
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
|
@ -62,8 +62,8 @@ internal class ToastNotificationView : ConstraintLayout {
|
|||
toastNotificationIconImageView.setImageDrawable(
|
||||
ContextCompat.getDrawable(
|
||||
context,
|
||||
it.notificationIconId
|
||||
)
|
||||
it.notificationIconId,
|
||||
),
|
||||
)
|
||||
toastNotificationMessageTextView.text = context.getString(it.notificationMessageId)
|
||||
}
|
||||
|
|
|
@ -28,8 +28,8 @@ internal class ToastNotificationViewModel(private val dispatch: (Action) -> Unit
|
|||
0,
|
||||
0,
|
||||
null,
|
||||
null
|
||||
)
|
||||
null,
|
||||
),
|
||||
)
|
||||
|
||||
private var timer: Timer = Timer()
|
||||
|
@ -45,16 +45,20 @@ internal class ToastNotificationViewModel(private val dispatch: (Action) -> Unit
|
|||
if (callDiagnosticsState.networkQualityCallDiagnostic.diagnosticValue == CallDiagnosticQuality.BAD ||
|
||||
callDiagnosticsState.networkQualityCallDiagnostic.diagnosticValue == CallDiagnosticQuality.POOR
|
||||
) {
|
||||
val toastNotificationModel = ToastNotificationModel(
|
||||
R.drawable.azure_communication_ui_calling_ic_fluent_wifi_warning_24_regular,
|
||||
R.string.azure_communication_ui_calling_diagnostics_network_quality_low,
|
||||
if (callDiagnosticsState.networkQualityCallDiagnostic?.diagnosticKind == NetworkCallDiagnostic.NETWORK_RECEIVE_QUALITY)
|
||||
NetworkCallDiagnostic.NETWORK_RECEIVE_QUALITY else NetworkCallDiagnostic.NETWORK_SEND_QUALITY,
|
||||
null
|
||||
)
|
||||
val toastNotificationModel =
|
||||
ToastNotificationModel(
|
||||
R.drawable.azure_communication_ui_calling_ic_fluent_wifi_warning_24_regular,
|
||||
R.string.azure_communication_ui_calling_diagnostics_network_quality_low,
|
||||
if (callDiagnosticsState.networkQualityCallDiagnostic?.diagnosticKind == NetworkCallDiagnostic.NETWORK_RECEIVE_QUALITY) {
|
||||
NetworkCallDiagnostic.NETWORK_RECEIVE_QUALITY
|
||||
} else {
|
||||
NetworkCallDiagnostic.NETWORK_SEND_QUALITY
|
||||
},
|
||||
null,
|
||||
)
|
||||
displayToastNotification(
|
||||
toastNotificationModel,
|
||||
false
|
||||
false,
|
||||
)
|
||||
} else {
|
||||
if (isPersistentNotificationDisplayed) {
|
||||
|
@ -68,15 +72,16 @@ internal class ToastNotificationViewModel(private val dispatch: (Action) -> Unit
|
|||
if (callDiagnosticsState.networkQualityCallDiagnostic.diagnosticValue == CallDiagnosticQuality.BAD ||
|
||||
callDiagnosticsState.networkQualityCallDiagnostic.diagnosticValue == CallDiagnosticQuality.POOR
|
||||
) {
|
||||
val toastNotificationModel = ToastNotificationModel(
|
||||
R.drawable.azure_communication_ui_calling_ic_fluent_wifi_warning_24_regular,
|
||||
R.string.azure_communication_ui_calling_diagnostics_network_reconnecting,
|
||||
NetworkCallDiagnostic.NETWORK_RECONNECTION_QUALITY,
|
||||
null
|
||||
)
|
||||
val toastNotificationModel =
|
||||
ToastNotificationModel(
|
||||
R.drawable.azure_communication_ui_calling_ic_fluent_wifi_warning_24_regular,
|
||||
R.string.azure_communication_ui_calling_diagnostics_network_reconnecting,
|
||||
NetworkCallDiagnostic.NETWORK_RECONNECTION_QUALITY,
|
||||
null,
|
||||
)
|
||||
displayToastNotification(
|
||||
toastNotificationModel,
|
||||
false
|
||||
false,
|
||||
)
|
||||
} else {
|
||||
if (isPersistentNotificationDisplayed) {
|
||||
|
@ -92,13 +97,17 @@ internal class ToastNotificationViewModel(private val dispatch: (Action) -> Unit
|
|||
when (callDiagnosticsState.networkCallDiagnostic?.diagnosticKind) {
|
||||
NetworkCallDiagnostic.NETWORK_UNAVAILABLE, NetworkCallDiagnostic.NETWORK_RELAYS_UNREACHABLE -> {
|
||||
if (callDiagnosticsState.networkCallDiagnostic.diagnosticValue) {
|
||||
val toastNotificationModel = ToastNotificationModel(
|
||||
R.drawable.azure_communication_ui_calling_ic_fluent_wifi_warning_24_regular,
|
||||
R.string.azure_communication_ui_calling_diagnostics_network_was_lost,
|
||||
if (callDiagnosticsState.networkQualityCallDiagnostic?.diagnosticKind == NetworkCallDiagnostic.NETWORK_UNAVAILABLE)
|
||||
NetworkCallDiagnostic.NETWORK_UNAVAILABLE else NetworkCallDiagnostic.NETWORK_RELAYS_UNREACHABLE,
|
||||
null
|
||||
)
|
||||
val toastNotificationModel =
|
||||
ToastNotificationModel(
|
||||
R.drawable.azure_communication_ui_calling_ic_fluent_wifi_warning_24_regular,
|
||||
R.string.azure_communication_ui_calling_diagnostics_network_was_lost,
|
||||
if (callDiagnosticsState.networkQualityCallDiagnostic?.diagnosticKind == NetworkCallDiagnostic.NETWORK_UNAVAILABLE) {
|
||||
NetworkCallDiagnostic.NETWORK_UNAVAILABLE
|
||||
} else {
|
||||
NetworkCallDiagnostic.NETWORK_RELAYS_UNREACHABLE
|
||||
},
|
||||
null,
|
||||
)
|
||||
displayToastNotification(toastNotificationModel)
|
||||
}
|
||||
}
|
||||
|
@ -108,12 +117,13 @@ internal class ToastNotificationViewModel(private val dispatch: (Action) -> Unit
|
|||
when (callDiagnosticsState.mediaCallDiagnostic?.diagnosticKind) {
|
||||
MediaCallDiagnostic.SPEAKING_WHILE_MICROPHONE_IS_MUTED -> {
|
||||
if (callDiagnosticsState.mediaCallDiagnostic.diagnosticValue) {
|
||||
val toastNotificationModel = ToastNotificationModel(
|
||||
R.drawable.azure_communication_ui_calling_ic_fluent_mic_off_24_filled,
|
||||
R.string.azure_communication_ui_calling_diagnostics_you_are_muted,
|
||||
null,
|
||||
MediaCallDiagnostic.SPEAKING_WHILE_MICROPHONE_IS_MUTED
|
||||
)
|
||||
val toastNotificationModel =
|
||||
ToastNotificationModel(
|
||||
R.drawable.azure_communication_ui_calling_ic_fluent_mic_off_24_filled,
|
||||
R.string.azure_communication_ui_calling_diagnostics_you_are_muted,
|
||||
null,
|
||||
MediaCallDiagnostic.SPEAKING_WHILE_MICROPHONE_IS_MUTED,
|
||||
)
|
||||
displayToastNotification(toastNotificationModel)
|
||||
} else if (!isPersistentNotificationDisplayed) {
|
||||
dismiss()
|
||||
|
@ -121,13 +131,17 @@ internal class ToastNotificationViewModel(private val dispatch: (Action) -> Unit
|
|||
}
|
||||
MediaCallDiagnostic.CAMERA_START_FAILED, MediaCallDiagnostic.CAMERA_START_TIMED_OUT -> {
|
||||
if (callDiagnosticsState.mediaCallDiagnostic.diagnosticValue) {
|
||||
val toastNotificationModel = ToastNotificationModel(
|
||||
R.drawable.azure_communication_ui_calling_ic_fluent_video_off_24_regular,
|
||||
R.string.azure_communication_ui_calling_diagnostics_unable_to_start_camera,
|
||||
null,
|
||||
if (callDiagnosticsState.mediaCallDiagnostic?.diagnosticKind == MediaCallDiagnostic.CAMERA_START_FAILED)
|
||||
MediaCallDiagnostic.CAMERA_START_FAILED else MediaCallDiagnostic.CAMERA_START_TIMED_OUT,
|
||||
)
|
||||
val toastNotificationModel =
|
||||
ToastNotificationModel(
|
||||
R.drawable.azure_communication_ui_calling_ic_fluent_video_off_24_regular,
|
||||
R.string.azure_communication_ui_calling_diagnostics_unable_to_start_camera,
|
||||
null,
|
||||
if (callDiagnosticsState.mediaCallDiagnostic?.diagnosticKind == MediaCallDiagnostic.CAMERA_START_FAILED) {
|
||||
MediaCallDiagnostic.CAMERA_START_FAILED
|
||||
} else {
|
||||
MediaCallDiagnostic.CAMERA_START_TIMED_OUT
|
||||
},
|
||||
)
|
||||
displayToastNotification(toastNotificationModel)
|
||||
}
|
||||
}
|
||||
|
@ -143,7 +157,10 @@ internal class ToastNotificationViewModel(private val dispatch: (Action) -> Unit
|
|||
}
|
||||
}
|
||||
|
||||
private fun displayToastNotification(toastNotificationModel: ToastNotificationModel, autoDismiss: Boolean = true) {
|
||||
private fun displayToastNotification(
|
||||
toastNotificationModel: ToastNotificationModel,
|
||||
autoDismiss: Boolean = true,
|
||||
) {
|
||||
if (!isPersistentNotificationDisplayed) {
|
||||
toastNotificationModelMessageFlow.value = toastNotificationModel
|
||||
displayToastNotificationFlow.value = true
|
||||
|
@ -157,16 +174,18 @@ internal class ToastNotificationViewModel(private val dispatch: (Action) -> Unit
|
|||
if (toastNotificationModel.networkCallDiagnostic == NetworkCallDiagnostic.NETWORK_UNAVAILABLE ||
|
||||
toastNotificationModel.networkCallDiagnostic == NetworkCallDiagnostic.NETWORK_RELAYS_UNREACHABLE
|
||||
) {
|
||||
val model = NetworkCallDiagnosticModel(
|
||||
toastNotificationModel.networkCallDiagnostic,
|
||||
false
|
||||
)
|
||||
val model =
|
||||
NetworkCallDiagnosticModel(
|
||||
toastNotificationModel.networkCallDiagnostic,
|
||||
false,
|
||||
)
|
||||
dispatch(CallDiagnosticsAction.NetworkCallDiagnosticsDismissed(model))
|
||||
} else {
|
||||
val model = NetworkQualityCallDiagnosticModel(
|
||||
toastNotificationModel.networkCallDiagnostic,
|
||||
CallDiagnosticQuality.UNKNOWN
|
||||
)
|
||||
val model =
|
||||
NetworkQualityCallDiagnosticModel(
|
||||
toastNotificationModel.networkCallDiagnostic,
|
||||
CallDiagnosticQuality.UNKNOWN,
|
||||
)
|
||||
dispatch(CallDiagnosticsAction.NetworkQualityCallDiagnosticsDismissed(model))
|
||||
}
|
||||
}
|
||||
|
@ -176,7 +195,7 @@ internal class ToastNotificationViewModel(private val dispatch: (Action) -> Unit
|
|||
}
|
||||
}
|
||||
},
|
||||
4000
|
||||
4000,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,23 +29,24 @@ internal class UpperMessageBarNotificationLayoutView : LinearLayout {
|
|||
fun start(
|
||||
viewLifecycleOwner: LifecycleOwner,
|
||||
upperMessageBarNotificationLayoutViewModel: UpperMessageBarNotificationLayoutViewModel,
|
||||
accessibilityEnabled: Boolean
|
||||
accessibilityEnabled: Boolean,
|
||||
) {
|
||||
this.upperMessageBarNotificationLayoutViewModel = upperMessageBarNotificationLayoutViewModel
|
||||
upperMessageBarNotificationLayout.visibility = View.VISIBLE
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
upperMessageBarNotificationLayoutViewModel.getNewUpperMessageBarNotificationFlow()?.collect() {
|
||||
upperMessageBarNotificationLayoutViewModel.getNewUpperMessageBarNotificationFlow()?.collect {
|
||||
if (!it.upperMessageBarNotificationModel.isEmpty()) {
|
||||
val upperMessageBarNotificationView: UpperMessageBarNotificationView = inflate(
|
||||
context,
|
||||
R.layout.azure_communication_ui_calling_upper_message_bar_notification,
|
||||
null
|
||||
) as UpperMessageBarNotificationView
|
||||
val upperMessageBarNotificationView: UpperMessageBarNotificationView =
|
||||
inflate(
|
||||
context,
|
||||
R.layout.azure_communication_ui_calling_upper_message_bar_notification,
|
||||
null,
|
||||
) as UpperMessageBarNotificationView
|
||||
upperMessageBarNotificationView.start(
|
||||
viewLifecycleOwner,
|
||||
it,
|
||||
accessibilityEnabled
|
||||
accessibilityEnabled,
|
||||
)
|
||||
|
||||
val layoutParams = LinearLayout.LayoutParams(upperMessageBarNotificationLayout.layoutParams)
|
||||
|
|
|
@ -12,33 +12,34 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
internal class UpperMessageBarNotificationLayoutViewModel(private val dispatch: (Action) -> Unit) {
|
||||
private var newUpperMessageBarNotificationFlow: MutableStateFlow<UpperMessageBarNotificationViewModel> = MutableStateFlow(
|
||||
UpperMessageBarNotificationViewModel(
|
||||
dispatch,
|
||||
UpperMessageBarNotificationModel(
|
||||
0,
|
||||
0,
|
||||
null
|
||||
)
|
||||
private var newUpperMessageBarNotificationFlow: MutableStateFlow<UpperMessageBarNotificationViewModel> =
|
||||
MutableStateFlow(
|
||||
UpperMessageBarNotificationViewModel(
|
||||
dispatch,
|
||||
UpperMessageBarNotificationModel(
|
||||
0,
|
||||
0,
|
||||
null,
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
private var mediaDiagnosticNotificationViewModels = hashMapOf<MediaCallDiagnostic, UpperMessageBarNotificationViewModel>()
|
||||
|
||||
fun getNewUpperMessageBarNotificationFlow(): StateFlow<UpperMessageBarNotificationViewModel> = newUpperMessageBarNotificationFlow
|
||||
|
||||
fun update(callDiagnosticsState: CallDiagnosticsState) {
|
||||
|
||||
when (callDiagnosticsState.mediaCallDiagnostic?.diagnosticKind) {
|
||||
MediaCallDiagnostic.NO_SPEAKER_DEVICES_AVAILABLE -> {
|
||||
if (mediaDiagnosticNotificationViewModels[MediaCallDiagnostic.NO_SPEAKER_DEVICES_AVAILABLE] == null &&
|
||||
callDiagnosticsState.mediaCallDiagnostic.diagnosticValue
|
||||
) {
|
||||
var upperMessageBarNotificationModel = UpperMessageBarNotificationModel(
|
||||
R.drawable.azure_communication_ui_calling_ic_fluent_speaker_mute_24_regular,
|
||||
R.string.azure_communication_ui_calling_diagnostics_unable_to_locate_speaker,
|
||||
MediaCallDiagnostic.NO_SPEAKER_DEVICES_AVAILABLE,
|
||||
)
|
||||
var upperMessageBarNotificationModel =
|
||||
UpperMessageBarNotificationModel(
|
||||
R.drawable.azure_communication_ui_calling_ic_fluent_speaker_mute_24_regular,
|
||||
R.string.azure_communication_ui_calling_diagnostics_unable_to_locate_speaker,
|
||||
MediaCallDiagnostic.NO_SPEAKER_DEVICES_AVAILABLE,
|
||||
)
|
||||
addNewNotification(upperMessageBarNotificationModel)
|
||||
} else if (mediaDiagnosticNotificationViewModels[MediaCallDiagnostic.NO_SPEAKER_DEVICES_AVAILABLE] != null &&
|
||||
!callDiagnosticsState.mediaCallDiagnostic.diagnosticValue
|
||||
|
@ -50,11 +51,12 @@ internal class UpperMessageBarNotificationLayoutViewModel(private val dispatch:
|
|||
if (mediaDiagnosticNotificationViewModels[MediaCallDiagnostic.NO_MICROPHONE_DEVICES_AVAILABLE] == null &&
|
||||
callDiagnosticsState.mediaCallDiagnostic.diagnosticValue
|
||||
) {
|
||||
var upperMessageBarNotificationModel = UpperMessageBarNotificationModel(
|
||||
R.drawable.azure_communication_ui_calling_ic_fluent_mic_prohibited_24_regular,
|
||||
R.string.azure_communication_ui_calling_diagnostics_unable_to_locate_microphone,
|
||||
MediaCallDiagnostic.NO_MICROPHONE_DEVICES_AVAILABLE
|
||||
)
|
||||
var upperMessageBarNotificationModel =
|
||||
UpperMessageBarNotificationModel(
|
||||
R.drawable.azure_communication_ui_calling_ic_fluent_mic_prohibited_24_regular,
|
||||
R.string.azure_communication_ui_calling_diagnostics_unable_to_locate_microphone,
|
||||
MediaCallDiagnostic.NO_MICROPHONE_DEVICES_AVAILABLE,
|
||||
)
|
||||
addNewNotification(upperMessageBarNotificationModel)
|
||||
} else if (mediaDiagnosticNotificationViewModels[MediaCallDiagnostic.NO_MICROPHONE_DEVICES_AVAILABLE] != null &&
|
||||
!callDiagnosticsState.mediaCallDiagnostic.diagnosticValue
|
||||
|
@ -66,11 +68,12 @@ internal class UpperMessageBarNotificationLayoutViewModel(private val dispatch:
|
|||
if (mediaDiagnosticNotificationViewModels[MediaCallDiagnostic.MICROPHONE_NOT_FUNCTIONING] == null &&
|
||||
callDiagnosticsState.mediaCallDiagnostic.diagnosticValue
|
||||
) {
|
||||
var upperMessageBarNotificationModel = UpperMessageBarNotificationModel(
|
||||
R.drawable.azure_communication_ui_calling_ic_fluent_mic_prohibited_24_regular,
|
||||
R.string.azure_communication_ui_calling_diagnostics_microphone_not_working_as_expected,
|
||||
MediaCallDiagnostic.MICROPHONE_NOT_FUNCTIONING
|
||||
)
|
||||
var upperMessageBarNotificationModel =
|
||||
UpperMessageBarNotificationModel(
|
||||
R.drawable.azure_communication_ui_calling_ic_fluent_mic_prohibited_24_regular,
|
||||
R.string.azure_communication_ui_calling_diagnostics_microphone_not_working_as_expected,
|
||||
MediaCallDiagnostic.MICROPHONE_NOT_FUNCTIONING,
|
||||
)
|
||||
addNewNotification(upperMessageBarNotificationModel)
|
||||
} else if (mediaDiagnosticNotificationViewModels[MediaCallDiagnostic.MICROPHONE_NOT_FUNCTIONING] != null &&
|
||||
!callDiagnosticsState.mediaCallDiagnostic.diagnosticValue
|
||||
|
@ -82,11 +85,12 @@ internal class UpperMessageBarNotificationLayoutViewModel(private val dispatch:
|
|||
if (mediaDiagnosticNotificationViewModels[MediaCallDiagnostic.SPEAKER_NOT_FUNCTIONING] == null &&
|
||||
callDiagnosticsState.mediaCallDiagnostic.diagnosticValue
|
||||
) {
|
||||
var upperMessageBarNotificationModel = UpperMessageBarNotificationModel(
|
||||
R.drawable.azure_communication_ui_calling_ic_fluent_speaker_mute_24_regular,
|
||||
R.string.azure_communication_ui_calling_diagnostics_speaker_not_working_as_expected,
|
||||
MediaCallDiagnostic.SPEAKER_NOT_FUNCTIONING
|
||||
)
|
||||
var upperMessageBarNotificationModel =
|
||||
UpperMessageBarNotificationModel(
|
||||
R.drawable.azure_communication_ui_calling_ic_fluent_speaker_mute_24_regular,
|
||||
R.string.azure_communication_ui_calling_diagnostics_speaker_not_working_as_expected,
|
||||
MediaCallDiagnostic.SPEAKER_NOT_FUNCTIONING,
|
||||
)
|
||||
addNewNotification(upperMessageBarNotificationModel)
|
||||
} else if (mediaDiagnosticNotificationViewModels[MediaCallDiagnostic.SPEAKER_NOT_FUNCTIONING] != null &&
|
||||
!callDiagnosticsState.mediaCallDiagnostic.diagnosticValue
|
||||
|
@ -100,10 +104,11 @@ internal class UpperMessageBarNotificationLayoutViewModel(private val dispatch:
|
|||
|
||||
private fun addNewNotification(upperMessageBarNotificationModel: UpperMessageBarNotificationModel) {
|
||||
upperMessageBarNotificationModel.mediaCallDiagnostic?.let {
|
||||
val upperMessageNotificationViewModel = UpperMessageBarNotificationViewModel(
|
||||
dispatch,
|
||||
upperMessageBarNotificationModel
|
||||
)
|
||||
val upperMessageNotificationViewModel =
|
||||
UpperMessageBarNotificationViewModel(
|
||||
dispatch,
|
||||
upperMessageBarNotificationModel,
|
||||
)
|
||||
mediaDiagnosticNotificationViewModels[upperMessageBarNotificationModel.mediaCallDiagnostic] =
|
||||
upperMessageNotificationViewModel
|
||||
newUpperMessageBarNotificationFlow.value = upperMessageNotificationViewModel
|
||||
|
|
|
@ -47,7 +47,7 @@ internal class UpperMessageBarNotificationView : ConstraintLayout {
|
|||
fun start(
|
||||
viewLifecycleOwner: LifecycleOwner,
|
||||
upperMessageBarNotificationViewModel: UpperMessageBarNotificationViewModel,
|
||||
accessibilityEnabled: Boolean
|
||||
accessibilityEnabled: Boolean,
|
||||
) {
|
||||
this.upperMessageBarNotificationViewModel = upperMessageBarNotificationViewModel
|
||||
setupAccessibility()
|
||||
|
@ -59,8 +59,8 @@ internal class UpperMessageBarNotificationView : ConstraintLayout {
|
|||
upperMessageBarNotificationIconImageView.setImageDrawable(
|
||||
ContextCompat.getDrawable(
|
||||
context,
|
||||
it.notificationIconId
|
||||
)
|
||||
it.notificationIconId,
|
||||
),
|
||||
)
|
||||
upperMessageBarNotificationMessage.text = context.getString(it.notificationMessageId)
|
||||
}
|
||||
|
@ -68,7 +68,7 @@ internal class UpperMessageBarNotificationView : ConstraintLayout {
|
|||
}
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
upperMessageBarNotificationViewModel.getDismissUpperMessageBarNotificationFlow()?.collect() {
|
||||
upperMessageBarNotificationViewModel.getDismissUpperMessageBarNotificationFlow()?.collect {
|
||||
if (it) {
|
||||
val viewGroup = upperMessageBarNotificationLayout.parent as ViewGroup
|
||||
viewGroup.removeView(upperMessageBarNotificationLayout)
|
||||
|
|
|
@ -12,9 +12,10 @@ import kotlinx.coroutines.flow.StateFlow
|
|||
|
||||
internal class UpperMessageBarNotificationViewModel(
|
||||
private val dispatch: (Action) -> Unit,
|
||||
val upperMessageBarNotificationModel: UpperMessageBarNotificationModel
|
||||
val upperMessageBarNotificationModel: UpperMessageBarNotificationModel,
|
||||
) {
|
||||
private var upperMessageBarNotificationModelFlow: MutableStateFlow<UpperMessageBarNotificationModel> = MutableStateFlow(upperMessageBarNotificationModel)
|
||||
private var upperMessageBarNotificationModelFlow: MutableStateFlow<UpperMessageBarNotificationModel> =
|
||||
MutableStateFlow(upperMessageBarNotificationModel)
|
||||
private var dismissUpperMessageBarNotificationFlow: MutableStateFlow<Boolean> = MutableStateFlow(false)
|
||||
|
||||
fun getDismissUpperMessageBarNotificationFlow(): StateFlow<Boolean> = dismissUpperMessageBarNotificationFlow
|
||||
|
|
|
@ -29,7 +29,6 @@ internal class ParticipantGridCellView(
|
|||
private val getScreenShareVideoStreamRendererCallback: () -> VideoStreamRenderer?,
|
||||
private val getParticipantViewDataCallback: (participantID: String) -> CallCompositeParticipantViewData?,
|
||||
) : RelativeLayout(context) {
|
||||
|
||||
private lateinit var avatarView: ParticipantGridCellAvatarView
|
||||
private lateinit var videoView: ParticipantGridCellVideoView
|
||||
|
||||
|
@ -70,18 +69,19 @@ internal class ParticipantGridCellView(
|
|||
val onHoldTextView: TextView =
|
||||
findViewById(R.id.azure_communication_ui_calling_participant_audio_view_on_hold)
|
||||
|
||||
avatarView = ParticipantGridCellAvatarView(
|
||||
avatarControl,
|
||||
participantAvatarSpeakingIndicator,
|
||||
participantAvatarContainer,
|
||||
displayNameAudioTextView,
|
||||
micIndicatorAudioImageView,
|
||||
getParticipantViewDataCallback,
|
||||
participantViewModel,
|
||||
onHoldTextView,
|
||||
context,
|
||||
lifecycleScope,
|
||||
)
|
||||
avatarView =
|
||||
ParticipantGridCellAvatarView(
|
||||
avatarControl,
|
||||
participantAvatarSpeakingIndicator,
|
||||
participantAvatarContainer,
|
||||
displayNameAudioTextView,
|
||||
micIndicatorAudioImageView,
|
||||
getParticipantViewDataCallback,
|
||||
participantViewModel,
|
||||
onHoldTextView,
|
||||
context,
|
||||
lifecycleScope,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createVideoView() {
|
||||
|
@ -100,19 +100,20 @@ internal class ParticipantGridCellView(
|
|||
val micIndicatorOnVideoImageView: ImageView =
|
||||
findViewById(R.id.azure_communication_ui_participant_view_on_video_mic_indicator)
|
||||
|
||||
videoView = ParticipantGridCellVideoView(
|
||||
context,
|
||||
lifecycleScope,
|
||||
participantVideoContainerFrameLayout,
|
||||
videoContainer,
|
||||
displayNameAndMicIndicatorViewContainer,
|
||||
displayNameOnVideoTextView,
|
||||
micIndicatorOnVideoImageView,
|
||||
participantViewModel,
|
||||
getVideoStreamCallback,
|
||||
showFloatingHeaderCallBack,
|
||||
getScreenShareVideoStreamRendererCallback,
|
||||
getParticipantViewDataCallback,
|
||||
)
|
||||
videoView =
|
||||
ParticipantGridCellVideoView(
|
||||
context,
|
||||
lifecycleScope,
|
||||
participantVideoContainerFrameLayout,
|
||||
videoContainer,
|
||||
displayNameAndMicIndicatorViewContainer,
|
||||
displayNameOnVideoTextView,
|
||||
micIndicatorOnVideoImageView,
|
||||
participantViewModel,
|
||||
getVideoStreamCallback,
|
||||
showFloatingHeaderCallBack,
|
||||
getScreenShareVideoStreamRendererCallback,
|
||||
getParticipantViewDataCallback,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,14 +25,15 @@ internal class ParticipantGridCellViewModel(
|
|||
private var isMutedStateFlow = MutableStateFlow(isMuted)
|
||||
private var isSpeakingStateFlow = MutableStateFlow(isSpeaking && !isMuted)
|
||||
private var isNameIndicatorVisibleStateFlow = MutableStateFlow(true)
|
||||
private var videoViewModelStateFlow = MutableStateFlow(
|
||||
getVideoStreamModel(
|
||||
createVideoViewModel(cameraVideoStreamModel),
|
||||
createVideoViewModel(screenShareVideoStreamModel),
|
||||
isOnHoldStateFlow.value,
|
||||
isCameraDisabled,
|
||||
private var videoViewModelStateFlow =
|
||||
MutableStateFlow(
|
||||
getVideoStreamModel(
|
||||
createVideoViewModel(cameraVideoStreamModel),
|
||||
createVideoViewModel(screenShareVideoStreamModel),
|
||||
isOnHoldStateFlow.value,
|
||||
isCameraDisabled,
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
private var participantModifiedTimestamp = modifiedTimestamp
|
||||
private var participantUserIdentifier = userIdentifier
|
||||
|
@ -69,9 +70,7 @@ internal class ParticipantGridCellViewModel(
|
|||
return isOnHoldStateFlow
|
||||
}
|
||||
|
||||
fun update(
|
||||
participant: ParticipantInfoModel,
|
||||
) {
|
||||
fun update(participant: ParticipantInfoModel) {
|
||||
this.participantUserIdentifier = participant.userIdentifier
|
||||
this.displayNameStateFlow.value = participant.displayName
|
||||
this.isMutedStateFlow.value = participant.isMuted
|
||||
|
@ -80,12 +79,13 @@ internal class ParticipantGridCellViewModel(
|
|||
this.isNameIndicatorVisibleStateFlow.value =
|
||||
!(participant.displayName.isBlank() && !participant.isMuted)
|
||||
|
||||
this.videoViewModelStateFlow.value = getVideoStreamModel(
|
||||
createVideoViewModel(participant.cameraVideoStreamModel),
|
||||
createVideoViewModel(participant.screenShareVideoStreamModel),
|
||||
this.isOnHoldStateFlow.value,
|
||||
participant.isCameraDisabled
|
||||
)
|
||||
this.videoViewModelStateFlow.value =
|
||||
getVideoStreamModel(
|
||||
createVideoViewModel(participant.cameraVideoStreamModel),
|
||||
createVideoViewModel(participant.screenShareVideoStreamModel),
|
||||
this.isOnHoldStateFlow.value,
|
||||
participant.isCameraDisabled,
|
||||
)
|
||||
|
||||
this.isSpeakingStateFlow.value = participant.isSpeaking && !participant.isMuted
|
||||
this.participantModifiedTimestamp = participant.modifiedTimestamp
|
||||
|
@ -102,7 +102,7 @@ internal class ParticipantGridCellViewModel(
|
|||
cameraVideoStreamModel: VideoViewModel?,
|
||||
screenShareVideoStreamModel: VideoViewModel?,
|
||||
isOnHold: Boolean,
|
||||
isCameraDisabled: Boolean
|
||||
isCameraDisabled: Boolean,
|
||||
): VideoViewModel? {
|
||||
if (isOnHold) return null
|
||||
if (screenShareVideoStreamModel != null) return screenShareVideoStreamModel
|
||||
|
@ -111,6 +111,5 @@ internal class ParticipantGridCellViewModel(
|
|||
return null
|
||||
}
|
||||
|
||||
private fun isOnHold(participantStatus: ParticipantStatus?) =
|
||||
participantStatus == ParticipantStatus.HOLD
|
||||
private fun isOnHold(participantStatus: ParticipantStatus?) = participantStatus == ParticipantStatus.HOLD
|
||||
}
|
||||
|
|
|
@ -51,7 +51,12 @@ internal class ParticipantGridView : GridLayout {
|
|||
private lateinit var displayedRemoteParticipantsView: MutableList<ParticipantGridCellView>
|
||||
private lateinit var getParticipantViewDataCallback: (participantID: String) -> CallCompositeParticipantViewData?
|
||||
|
||||
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
||||
override fun onSizeChanged(
|
||||
w: Int,
|
||||
h: Int,
|
||||
oldw: Int,
|
||||
oldh: Int,
|
||||
) {
|
||||
super.onSizeChanged(w, h, oldw, oldh)
|
||||
post {
|
||||
updateGrid(participantGridViewModel.getRemoteParticipantsUpdateStateFlow().value)
|
||||
|
@ -85,7 +90,7 @@ internal class ParticipantGridView : GridLayout {
|
|||
info.removeAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK)
|
||||
info.isClickable = false
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -96,7 +101,7 @@ internal class ParticipantGridView : GridLayout {
|
|||
this.getVideoStreamCallback = { participantID: String, videoStreamID: String ->
|
||||
this.videoViewManager.getRemoteVideoStreamRenderer(
|
||||
participantID,
|
||||
videoStreamID
|
||||
videoStreamID,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -139,40 +144,51 @@ internal class ParticipantGridView : GridLayout {
|
|||
if (it) {
|
||||
ViewCompat.setImportantForAccessibility(
|
||||
gridView,
|
||||
ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
|
||||
ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS,
|
||||
)
|
||||
} else {
|
||||
ViewCompat.setImportantForAccessibility(
|
||||
gridView,
|
||||
ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES
|
||||
ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addOnLayoutChangeListener(object : OnLayoutChangeListener {
|
||||
override fun onLayoutChange(
|
||||
v: View,
|
||||
left: Int,
|
||||
top: Int,
|
||||
right: Int,
|
||||
bottom: Int,
|
||||
oldLeft: Int,
|
||||
oldTop: Int,
|
||||
oldRight: Int,
|
||||
oldBottom: Int,
|
||||
) {
|
||||
if (isLaidOut) {
|
||||
removeOnLayoutChangeListener(this)
|
||||
post {
|
||||
updateGrid(participantGridViewModel.getRemoteParticipantsUpdateStateFlow().value)
|
||||
addOnLayoutChangeListener(
|
||||
object : OnLayoutChangeListener {
|
||||
override fun onLayoutChange(
|
||||
v: View,
|
||||
left: Int,
|
||||
top: Int,
|
||||
right: Int,
|
||||
bottom: Int,
|
||||
oldLeft: Int,
|
||||
oldTop: Int,
|
||||
oldRight: Int,
|
||||
oldBottom: Int,
|
||||
) {
|
||||
if (isLaidOut) {
|
||||
removeOnLayoutChangeListener(this)
|
||||
post {
|
||||
updateGrid(participantGridViewModel.getRemoteParticipantsUpdateStateFlow().value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
addOnLayoutChangeListener { _, left, top, right, bottom,
|
||||
oldLeft, oldTop, oldRight, oldBottom ->
|
||||
addOnLayoutChangeListener {
|
||||
_,
|
||||
left,
|
||||
top,
|
||||
right,
|
||||
bottom,
|
||||
oldLeft,
|
||||
oldTop,
|
||||
oldRight,
|
||||
oldBottom,
|
||||
->
|
||||
if (left != oldLeft ||
|
||||
right != oldRight ||
|
||||
top != oldTop ||
|
||||
|
@ -190,9 +206,7 @@ internal class ParticipantGridView : GridLayout {
|
|||
removeAllViews()
|
||||
}
|
||||
|
||||
private fun updateGrid(
|
||||
displayedRemoteParticipantsViewModel: List<ParticipantGridCellViewModel>,
|
||||
) {
|
||||
private fun updateGrid(displayedRemoteParticipantsViewModel: List<ParticipantGridCellViewModel>) {
|
||||
videoViewManager.updateScalingForRemoteStream()
|
||||
removeAllViews()
|
||||
displayedRemoteParticipantsView = mutableListOf()
|
||||
|
@ -206,14 +220,12 @@ internal class ParticipantGridView : GridLayout {
|
|||
displayParticipants(displayedRemoteParticipantsView)
|
||||
}
|
||||
|
||||
private fun displayParticipants(
|
||||
displayedRemoteParticipantsView: List<ParticipantGridCellView>,
|
||||
) {
|
||||
private fun displayParticipants(displayedRemoteParticipantsView: List<ParticipantGridCellView>) {
|
||||
when (displayedRemoteParticipantsView.size) {
|
||||
SINGLE_PARTICIPANT, TWO_PARTICIPANTS, FOUR_PARTICIPANTS, SIX_PARTICIPANTS, NINE_PARTICIPANTS, -> {
|
||||
SINGLE_PARTICIPANT, TWO_PARTICIPANTS, FOUR_PARTICIPANTS, SIX_PARTICIPANTS, NINE_PARTICIPANTS -> {
|
||||
displayedRemoteParticipantsView.forEach {
|
||||
addParticipantToGrid(
|
||||
participantGridCellView = it
|
||||
participantGridCellView = it,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -222,19 +234,19 @@ internal class ParticipantGridView : GridLayout {
|
|||
if (context.resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
|
||||
addParticipantToGrid(
|
||||
columnSpan = 2,
|
||||
participantGridCellView = displayedRemoteParticipantsView[0]
|
||||
participantGridCellView = displayedRemoteParticipantsView[0],
|
||||
)
|
||||
} else {
|
||||
addParticipantToGrid(
|
||||
rowSpan = 2,
|
||||
participantGridCellView = displayedRemoteParticipantsView[0]
|
||||
participantGridCellView = displayedRemoteParticipantsView[0],
|
||||
)
|
||||
}
|
||||
|
||||
displayedRemoteParticipantsView.forEachIndexed { index, participantGridCellView ->
|
||||
if (index > 0) {
|
||||
addParticipantToGrid(
|
||||
participantGridCellView = participantGridCellView
|
||||
participantGridCellView = participantGridCellView,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -246,7 +258,7 @@ internal class ParticipantGridView : GridLayout {
|
|||
addParticipantToGrid(
|
||||
columnSpan = 2,
|
||||
rowSpan = 3,
|
||||
participantGridCellView = displayedRemoteParticipantsView[0]
|
||||
participantGridCellView = displayedRemoteParticipantsView[0],
|
||||
)
|
||||
|
||||
displayedRemoteParticipantsView.forEachIndexed { index, participantGridCellView ->
|
||||
|
@ -255,13 +267,13 @@ internal class ParticipantGridView : GridLayout {
|
|||
addParticipantToGrid(
|
||||
columnSpan = 2,
|
||||
rowSpan = 3,
|
||||
participantGridCellView = participantGridCellView
|
||||
participantGridCellView = participantGridCellView,
|
||||
)
|
||||
} else {
|
||||
addParticipantToGrid(
|
||||
columnSpan = 2,
|
||||
rowSpan = 2,
|
||||
participantGridCellView = participantGridCellView
|
||||
participantGridCellView = participantGridCellView,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -272,13 +284,13 @@ internal class ParticipantGridView : GridLayout {
|
|||
addParticipantToGrid(
|
||||
rowSpan = 2,
|
||||
columnSpan = 2,
|
||||
participantGridCellView = participantGridCellView
|
||||
participantGridCellView = participantGridCellView,
|
||||
)
|
||||
} else {
|
||||
addParticipantToGrid(
|
||||
columnSpan = 3,
|
||||
rowSpan = 2,
|
||||
participantGridCellView = participantGridCellView
|
||||
participantGridCellView = participantGridCellView,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -287,19 +299,19 @@ internal class ParticipantGridView : GridLayout {
|
|||
if (context.resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
|
||||
addParticipantToGrid(
|
||||
columnSpan = 2,
|
||||
participantGridCellView = displayedRemoteParticipantsView[0]
|
||||
participantGridCellView = displayedRemoteParticipantsView[0],
|
||||
)
|
||||
} else {
|
||||
addParticipantToGrid(
|
||||
rowSpan = 2,
|
||||
participantGridCellView = displayedRemoteParticipantsView[0]
|
||||
participantGridCellView = displayedRemoteParticipantsView[0],
|
||||
)
|
||||
}
|
||||
|
||||
displayedRemoteParticipantsView.forEachIndexed { index, participantGridCellView ->
|
||||
if (index > 0) {
|
||||
addParticipantToGrid(
|
||||
participantGridCellView = participantGridCellView
|
||||
participantGridCellView = participantGridCellView,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -309,23 +321,23 @@ internal class ParticipantGridView : GridLayout {
|
|||
if (context.resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
|
||||
addParticipantToGrid(
|
||||
rowSpan = 4,
|
||||
participantGridCellView = displayedRemoteParticipantsView[0]
|
||||
participantGridCellView = displayedRemoteParticipantsView[0],
|
||||
)
|
||||
addParticipantToGrid(
|
||||
rowSpan = 3,
|
||||
participantGridCellView = displayedRemoteParticipantsView[1]
|
||||
participantGridCellView = displayedRemoteParticipantsView[1],
|
||||
)
|
||||
displayedRemoteParticipantsView.forEachIndexed { index, participantGridCellView ->
|
||||
if (index > 1) {
|
||||
if (index % 2 == 0) {
|
||||
addParticipantToGrid(
|
||||
rowSpan = 3,
|
||||
participantGridCellView = participantGridCellView
|
||||
participantGridCellView = participantGridCellView,
|
||||
)
|
||||
} else {
|
||||
addParticipantToGrid(
|
||||
rowSpan = 4,
|
||||
participantGridCellView = participantGridCellView
|
||||
participantGridCellView = participantGridCellView,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -335,12 +347,12 @@ internal class ParticipantGridView : GridLayout {
|
|||
if (index < 4) {
|
||||
addParticipantToGrid(
|
||||
columnSpan = 3,
|
||||
participantGridCellView = participantGridCellView
|
||||
participantGridCellView = participantGridCellView,
|
||||
)
|
||||
} else {
|
||||
addParticipantToGrid(
|
||||
columnSpan = 4,
|
||||
participantGridCellView = participantGridCellView
|
||||
participantGridCellView = participantGridCellView,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -353,13 +365,13 @@ internal class ParticipantGridView : GridLayout {
|
|||
addParticipantToGrid(
|
||||
rowSpan = 2,
|
||||
columnSpan = 3,
|
||||
participantGridCellView = participantGridCellView
|
||||
participantGridCellView = participantGridCellView,
|
||||
)
|
||||
} else {
|
||||
addParticipantToGrid(
|
||||
rowSpan = 2,
|
||||
columnSpan = 2,
|
||||
participantGridCellView = participantGridCellView
|
||||
participantGridCellView = participantGridCellView,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -367,7 +379,7 @@ internal class ParticipantGridView : GridLayout {
|
|||
addParticipantToGrid(
|
||||
rowSpan = 3,
|
||||
columnSpan = 2,
|
||||
participantGridCellView = displayedRemoteParticipantsView[0]
|
||||
participantGridCellView = displayedRemoteParticipantsView[0],
|
||||
)
|
||||
|
||||
displayedRemoteParticipantsView.forEachIndexed { index, participantGridCellView ->
|
||||
|
@ -376,13 +388,13 @@ internal class ParticipantGridView : GridLayout {
|
|||
addParticipantToGrid(
|
||||
rowSpan = 3,
|
||||
columnSpan = 2,
|
||||
participantGridCellView = participantGridCellView
|
||||
participantGridCellView = participantGridCellView,
|
||||
)
|
||||
} else {
|
||||
addParticipantToGrid(
|
||||
rowSpan = 2,
|
||||
columnSpan = 2,
|
||||
participantGridCellView = participantGridCellView
|
||||
participantGridCellView = participantGridCellView,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -461,7 +473,10 @@ internal class ParticipantGridView : GridLayout {
|
|||
this.addView(participantGridCellView)
|
||||
}
|
||||
|
||||
private fun setGridRowsColumn(rows: Int, columns: Int) {
|
||||
private fun setGridRowsColumn(
|
||||
rows: Int,
|
||||
columns: Int,
|
||||
) {
|
||||
this.rowCount = rows
|
||||
this.columnCount = columns
|
||||
}
|
||||
|
@ -487,6 +502,6 @@ internal class ParticipantGridView : GridLayout {
|
|||
showFloatingHeaderCallBack,
|
||||
getVideoStreamCallback,
|
||||
getScreenShareVideoStreamRendererCallback,
|
||||
getParticipantViewDataCallback
|
||||
getParticipantViewDataCallback,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -13,9 +13,8 @@ import java.lang.Integer.min
|
|||
|
||||
internal class ParticipantGridViewModel(
|
||||
private val participantGridCellViewModelFactory: ParticipantGridCellViewModelFactory,
|
||||
private val maxRemoteParticipantSize: Int
|
||||
private val maxRemoteParticipantSize: Int,
|
||||
) {
|
||||
|
||||
private var remoteParticipantsUpdatedStateFlow: MutableStateFlow<List<ParticipantGridCellViewModel>> =
|
||||
MutableStateFlow(mutableListOf())
|
||||
|
||||
|
@ -28,9 +27,7 @@ internal class ParticipantGridViewModel(
|
|||
private var pipStatus: PictureInPictureStatus = PictureInPictureStatus.VISIBLE
|
||||
private lateinit var isLobbyOverlayDisplayedFlow: MutableStateFlow<Boolean>
|
||||
|
||||
fun init(
|
||||
callingStatus: CallingStatus,
|
||||
) {
|
||||
fun init(callingStatus: CallingStatus) {
|
||||
isLobbyOverlayDisplayedFlow = MutableStateFlow(isLobbyOverlayDisplayed(callingStatus))
|
||||
}
|
||||
|
||||
|
@ -50,8 +47,11 @@ internal class ParticipantGridViewModel(
|
|||
}
|
||||
|
||||
fun getMaxRemoteParticipantsSize(): Int {
|
||||
return if (pipStatus == PictureInPictureStatus.VISIBLE)
|
||||
maxRemoteParticipantSize else 1
|
||||
return if (pipStatus == PictureInPictureStatus.VISIBLE) {
|
||||
maxRemoteParticipantSize
|
||||
} else {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
fun getIsLobbyOverlayDisplayedFlow(): StateFlow<Boolean> = isLobbyOverlayDisplayedFlow
|
||||
|
@ -87,12 +87,13 @@ internal class ParticipantGridViewModel(
|
|||
sortRemoteParticipants(remoteParticipantsMap, dominantSpeakersInfo)
|
||||
}
|
||||
} else {
|
||||
remoteParticipantsMapSorted = mapOf(
|
||||
Pair(
|
||||
participantSharingScreen,
|
||||
remoteParticipantsMap[participantSharingScreen]!!
|
||||
remoteParticipantsMapSorted =
|
||||
mapOf(
|
||||
Pair(
|
||||
participantSharingScreen,
|
||||
remoteParticipantsMap[participantSharingScreen]!!,
|
||||
),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
updateRemoteParticipantsVideoStreams(remoteParticipantsMapSorted)
|
||||
|
@ -100,9 +101,7 @@ internal class ParticipantGridViewModel(
|
|||
updateDisplayedParticipants(remoteParticipantsMapSorted.toMutableMap())
|
||||
}
|
||||
|
||||
private fun getParticipantSharingScreen(
|
||||
remoteParticipantsMap: Map<String, ParticipantInfoModel>,
|
||||
): String? {
|
||||
private fun getParticipantSharingScreen(remoteParticipantsMap: Map<String, ParticipantInfoModel>): String? {
|
||||
remoteParticipantsMap.forEach { (id, participantInfoModel) ->
|
||||
if (participantInfoModel.screenShareVideoStreamModel != null) {
|
||||
return id
|
||||
|
@ -111,17 +110,16 @@ internal class ParticipantGridViewModel(
|
|||
return null
|
||||
}
|
||||
|
||||
private fun updateDisplayedParticipants(
|
||||
remoteParticipantsMapSorted: MutableMap<String, ParticipantInfoModel>,
|
||||
) {
|
||||
private fun updateDisplayedParticipants(remoteParticipantsMapSorted: MutableMap<String, ParticipantInfoModel>) {
|
||||
val alreadyDisplayedParticipants =
|
||||
displayedRemoteParticipantsViewModelMap.filter { (id, _) ->
|
||||
remoteParticipantsMapSorted.containsKey(id)
|
||||
}
|
||||
|
||||
val viewModelsThatCanBeRemoved = displayedRemoteParticipantsViewModelMap.keys.filter {
|
||||
!remoteParticipantsMapSorted.containsKey(it)
|
||||
}.toMutableList()
|
||||
val viewModelsThatCanBeRemoved =
|
||||
displayedRemoteParticipantsViewModelMap.keys.filter {
|
||||
!remoteParticipantsMapSorted.containsKey(it)
|
||||
}.toMutableList()
|
||||
|
||||
alreadyDisplayedParticipants.forEach { (id, participantViewModel) ->
|
||||
if (participantViewModel.getParticipantModifiedTimestamp()
|
||||
|
@ -179,75 +177,79 @@ internal class ParticipantGridViewModel(
|
|||
remoteParticipantsMap: Map<String, ParticipantInfoModel>,
|
||||
dominantSpeakersInfo: List<String>,
|
||||
): Map<String, ParticipantInfoModel> {
|
||||
|
||||
val dominantSpeakersOrder = mutableMapOf<String, Int>()
|
||||
|
||||
for (i in 0 until min(maxRemoteParticipantSize, dominantSpeakersInfo.count())) {
|
||||
dominantSpeakersOrder[dominantSpeakersInfo[i]] = i
|
||||
}
|
||||
|
||||
val lengthComparator = Comparator<Pair<String, ParticipantInfoModel>> { keyValuePair1, keyValuePair2 ->
|
||||
val participantId1 = keyValuePair1.first
|
||||
val participantId2 = keyValuePair2.first
|
||||
val participant1 = keyValuePair1.second
|
||||
val participant2 = keyValuePair2.second
|
||||
val lengthComparator =
|
||||
Comparator<Pair<String, ParticipantInfoModel>> { keyValuePair1, keyValuePair2 ->
|
||||
val participantId1 = keyValuePair1.first
|
||||
val participantId2 = keyValuePair2.first
|
||||
val participant1 = keyValuePair1.second
|
||||
val participant2 = keyValuePair2.second
|
||||
|
||||
if (dominantSpeakersOrder.containsKey(participantId1) &&
|
||||
dominantSpeakersOrder.containsKey(participantId2)
|
||||
) {
|
||||
val order1 = dominantSpeakersOrder.getValue(participantId1)
|
||||
val order2 = dominantSpeakersOrder.getValue(participantId2)
|
||||
return@Comparator if (order1 > order2)
|
||||
1 else -1
|
||||
if (dominantSpeakersOrder.containsKey(participantId1) &&
|
||||
dominantSpeakersOrder.containsKey(participantId2)
|
||||
) {
|
||||
val order1 = dominantSpeakersOrder.getValue(participantId1)
|
||||
val order2 = dominantSpeakersOrder.getValue(participantId2)
|
||||
return@Comparator if (order1 > order2) {
|
||||
1
|
||||
} else {
|
||||
-1
|
||||
}
|
||||
}
|
||||
|
||||
if (dominantSpeakersOrder.containsKey(participantId1)) {
|
||||
return@Comparator -1
|
||||
}
|
||||
|
||||
if (dominantSpeakersOrder.containsKey(participantId2)) {
|
||||
return@Comparator 1
|
||||
}
|
||||
|
||||
if ((participant1.cameraVideoStreamModel != null && participant2.cameraVideoStreamModel != null) ||
|
||||
(participant1.cameraVideoStreamModel == null && participant2.cameraVideoStreamModel == null)
|
||||
) {
|
||||
return@Comparator 0
|
||||
}
|
||||
|
||||
if (participant1.cameraVideoStreamModel != null) {
|
||||
return@Comparator -1
|
||||
} else {
|
||||
return@Comparator 1
|
||||
}
|
||||
}
|
||||
|
||||
if (dominantSpeakersOrder.containsKey(participantId1))
|
||||
return@Comparator -1
|
||||
|
||||
if (dominantSpeakersOrder.containsKey(participantId2))
|
||||
return@Comparator 1
|
||||
|
||||
if ((participant1.cameraVideoStreamModel != null && participant2.cameraVideoStreamModel != null) ||
|
||||
(participant1.cameraVideoStreamModel == null && participant2.cameraVideoStreamModel == null)
|
||||
)
|
||||
return@Comparator 0
|
||||
|
||||
if (participant1.cameraVideoStreamModel != null)
|
||||
return@Comparator -1
|
||||
else
|
||||
return@Comparator 1
|
||||
}
|
||||
|
||||
return remoteParticipantsMap.toList()
|
||||
.sortedWith(lengthComparator)
|
||||
.take(getMaxRemoteParticipantsSize()).toMap()
|
||||
}
|
||||
|
||||
private fun updateRemoteParticipantsVideoStreams(
|
||||
participantViewModelMap: Map<String, ParticipantInfoModel>,
|
||||
) {
|
||||
private fun updateRemoteParticipantsVideoStreams(participantViewModelMap: Map<String, ParticipantInfoModel>) {
|
||||
val usersVideoStream: MutableList<Pair<String, String>> = mutableListOf()
|
||||
participantViewModelMap.forEach { (participantId, participant) ->
|
||||
participant.cameraVideoStreamModel?.let {
|
||||
usersVideoStream.add(
|
||||
Pair(
|
||||
participantId,
|
||||
participant.cameraVideoStreamModel!!.videoStreamID
|
||||
)
|
||||
participant.cameraVideoStreamModel!!.videoStreamID,
|
||||
),
|
||||
)
|
||||
}
|
||||
participant.screenShareVideoStreamModel?.let {
|
||||
usersVideoStream.add(
|
||||
Pair(
|
||||
participantId,
|
||||
participant.screenShareVideoStreamModel!!.videoStreamID
|
||||
)
|
||||
participant.screenShareVideoStreamModel!!.videoStreamID,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
updateVideoStreamsCallback?.invoke(usersVideoStream)
|
||||
}
|
||||
|
||||
private fun isLobbyOverlayDisplayed(callingStatus: CallingStatus) =
|
||||
callingStatus == CallingStatus.IN_LOBBY
|
||||
private fun isLobbyOverlayDisplayed(callingStatus: CallingStatus) = callingStatus == CallingStatus.IN_LOBBY
|
||||
}
|
||||
|
|
|
@ -5,8 +5,8 @@ package com.azure.android.communication.ui.calling.presentation.fragment.calling
|
|||
|
||||
import android.content.Context
|
||||
import android.view.View.GONE
|
||||
import android.view.View.VISIBLE
|
||||
import android.view.View.INVISIBLE
|
||||
import android.view.View.VISIBLE
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
|
@ -53,11 +53,15 @@ internal class ParticipantGridCellAvatarView(
|
|||
if (it) {
|
||||
onHoldTextView.visibility = VISIBLE
|
||||
micIndicatorAudioImageView.visibility = GONE
|
||||
displayNameAudioTextView.setTextColor(ContextCompat.getColor(context, R.color.azure_communication_ui_calling_color_participant_list_mute_mic))
|
||||
displayNameAudioTextView.setTextColor(
|
||||
ContextCompat.getColor(context, R.color.azure_communication_ui_calling_color_participant_list_mute_mic),
|
||||
)
|
||||
} else {
|
||||
onHoldTextView.visibility = INVISIBLE
|
||||
setMicButtonVisibility(participantViewModel.getIsMutedStateFlow().value)
|
||||
displayNameAudioTextView.setTextColor(ContextCompat.getColor(context, R.color.azure_communication_ui_calling_color_on_background))
|
||||
displayNameAudioTextView.setTextColor(
|
||||
ContextCompat.getColor(context, R.color.azure_communication_ui_calling_color_on_background),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -100,14 +104,13 @@ internal class ParticipantGridCellAvatarView(
|
|||
}
|
||||
}
|
||||
|
||||
private fun setSpeakingIndicator(
|
||||
isSpeaking: Boolean,
|
||||
) {
|
||||
private fun setSpeakingIndicator(isSpeaking: Boolean) {
|
||||
if (isSpeaking) {
|
||||
participantAvatarSpeakingFrameLayout.background = ContextCompat.getDrawable(
|
||||
context,
|
||||
R.drawable.azure_communication_ui_calling_speaking_round_indicator
|
||||
)
|
||||
participantAvatarSpeakingFrameLayout.background =
|
||||
ContextCompat.getDrawable(
|
||||
context,
|
||||
R.drawable.azure_communication_ui_calling_speaking_round_indicator,
|
||||
)
|
||||
} else {
|
||||
participantAvatarSpeakingFrameLayout.setBackgroundResource(0)
|
||||
}
|
||||
|
|
|
@ -5,9 +5,9 @@ package com.azure.android.communication.ui.calling.presentation.fragment.calling
|
|||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import android.view.View.GONE
|
||||
import android.view.View.INVISIBLE
|
||||
import android.view.View.VISIBLE
|
||||
import android.view.View.GONE
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageView
|
||||
|
@ -16,8 +16,8 @@ import androidx.constraintlayout.widget.ConstraintLayout
|
|||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.LifecycleCoroutineScope
|
||||
import com.azure.android.communication.ui.calling.implementation.R
|
||||
import com.azure.android.communication.ui.calling.models.StreamType
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeParticipantViewData
|
||||
import com.azure.android.communication.ui.calling.models.StreamType
|
||||
import com.azure.android.communication.ui.calling.presentation.fragment.calling.participant.grid.ParticipantGridCellViewModel
|
||||
import com.azure.android.communication.ui.calling.presentation.fragment.calling.participant.grid.VideoViewModel
|
||||
import com.azure.android.communication.ui.calling.presentation.fragment.calling.participant.grid.screenshare.ScreenShareViewManager
|
||||
|
@ -94,9 +94,7 @@ internal class ParticipantGridCellVideoView(
|
|||
}
|
||||
}
|
||||
|
||||
private fun updateVideoStream(
|
||||
videoViewModel: VideoViewModel?,
|
||||
) {
|
||||
private fun updateVideoStream(videoViewModel: VideoViewModel?) {
|
||||
if (videoStream != null) {
|
||||
detachFromParentView(videoStream)
|
||||
videoStream = null
|
||||
|
@ -105,7 +103,7 @@ internal class ParticipantGridCellVideoView(
|
|||
if (videoViewModel != null) {
|
||||
getVideoStreamCallback(
|
||||
participantViewModel.getParticipantUserIdentifier(),
|
||||
videoViewModel.videoStreamID
|
||||
videoViewModel.videoStreamID,
|
||||
)?.let { view ->
|
||||
videoStream = view
|
||||
setRendererView(view, videoViewModel.streamType)
|
||||
|
@ -125,9 +123,7 @@ internal class ParticipantGridCellVideoView(
|
|||
}
|
||||
}
|
||||
|
||||
private fun setSpeakingIndicator(
|
||||
isSpeaking: Boolean,
|
||||
) {
|
||||
private fun setSpeakingIndicator(isSpeaking: Boolean) {
|
||||
if (isSpeaking) {
|
||||
participantVideoContainerSpeakingFrameLayout.visibility = VISIBLE
|
||||
} else {
|
||||
|
@ -135,44 +131,53 @@ internal class ParticipantGridCellVideoView(
|
|||
}
|
||||
}
|
||||
|
||||
private fun setRendererView(rendererView: View, streamType: StreamType) {
|
||||
private fun setRendererView(
|
||||
rendererView: View,
|
||||
streamType: StreamType,
|
||||
) {
|
||||
detachFromParentView(rendererView)
|
||||
|
||||
if (streamType == StreamType.SCREEN_SHARING) {
|
||||
removeScreenShareZoomView()
|
||||
val screenShareFactory = ScreenShareViewManager(
|
||||
context,
|
||||
videoContainer,
|
||||
getScreenShareVideoStreamRendererCallback,
|
||||
showFloatingHeaderCallBack
|
||||
)
|
||||
val screenShareFactory =
|
||||
ScreenShareViewManager(
|
||||
context,
|
||||
videoContainer,
|
||||
getScreenShareVideoStreamRendererCallback,
|
||||
showFloatingHeaderCallBack,
|
||||
)
|
||||
screenShareZoomFrameLayout = screenShareFactory.getScreenShareView(rendererView)
|
||||
videoContainer.addView(screenShareZoomFrameLayout, 0)
|
||||
// scaled transformed view round corners are not visible when scroll is not at end
|
||||
// to avoid content outside speaking rectangle removing round corners
|
||||
videoContainer.background = ContextCompat.getDrawable(
|
||||
context,
|
||||
R.color.azure_communication_ui_calling_color_surface
|
||||
)
|
||||
participantVideoContainerSpeakingFrameLayout.background = ContextCompat.getDrawable(
|
||||
context,
|
||||
R.drawable.azure_communication_ui_calling_speaking_rectangle_indicator_no_corner
|
||||
)
|
||||
videoContainer.background =
|
||||
ContextCompat.getDrawable(
|
||||
context,
|
||||
R.color.azure_communication_ui_calling_color_surface,
|
||||
)
|
||||
participantVideoContainerSpeakingFrameLayout.background =
|
||||
ContextCompat.getDrawable(
|
||||
context,
|
||||
R.drawable.azure_communication_ui_calling_speaking_rectangle_indicator_no_corner,
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
rendererView.background = ContextCompat.getDrawable(
|
||||
context,
|
||||
R.drawable.azure_communication_ui_calling_corner_radius_rectangle_4dp
|
||||
)
|
||||
videoContainer.background = ContextCompat.getDrawable(
|
||||
context,
|
||||
R.drawable.azure_communication_ui_calling_corner_radius_rectangle_4dp_surface
|
||||
)
|
||||
participantVideoContainerSpeakingFrameLayout.background = ContextCompat.getDrawable(
|
||||
context,
|
||||
R.drawable.azure_communication_ui_calling_speaking_rectangle_indicator
|
||||
)
|
||||
rendererView.background =
|
||||
ContextCompat.getDrawable(
|
||||
context,
|
||||
R.drawable.azure_communication_ui_calling_corner_radius_rectangle_4dp,
|
||||
)
|
||||
videoContainer.background =
|
||||
ContextCompat.getDrawable(
|
||||
context,
|
||||
R.drawable.azure_communication_ui_calling_corner_radius_rectangle_4dp_surface,
|
||||
)
|
||||
participantVideoContainerSpeakingFrameLayout.background =
|
||||
ContextCompat.getDrawable(
|
||||
context,
|
||||
R.drawable.azure_communication_ui_calling_speaking_rectangle_indicator,
|
||||
)
|
||||
videoContainer.addView(rendererView, 0)
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,6 @@ internal class GestureListener(
|
|||
) : ScaleGestureDetector.OnScaleGestureListener,
|
||||
GestureDetector.OnGestureListener,
|
||||
GestureDetector.OnDoubleTapListener {
|
||||
|
||||
companion object {
|
||||
private const val INVALID_POINTER = MotionEvent.INVALID_POINTER_ID
|
||||
}
|
||||
|
|
|
@ -7,7 +7,10 @@ import android.view.MotionEvent
|
|||
|
||||
internal interface GestureListenerEvents {
|
||||
fun onSingleClick()
|
||||
|
||||
fun onDoubleClick(motionEvent: MotionEvent)
|
||||
|
||||
fun initTransformation()
|
||||
|
||||
fun updateTransformation()
|
||||
}
|
||||
|
|
|
@ -37,13 +37,14 @@ internal class ScreenShareViewManager(
|
|||
screenShareZoomFrameLayout.layoutParams =
|
||||
FrameLayout.LayoutParams(
|
||||
FrameLayout.LayoutParams.MATCH_PARENT,
|
||||
FrameLayout.LayoutParams.MATCH_PARENT
|
||||
FrameLayout.LayoutParams.MATCH_PARENT,
|
||||
)
|
||||
|
||||
screenShareZoomFrameLayout.addView(rendererViewTransformationWrapper)
|
||||
screenShareZoomFrameLayout.setFloatingHeaderCallback(showFloatingHeaderCallBack)
|
||||
|
||||
screenShareZoomFrameLayout.viewTreeObserver.addOnGlobalLayoutListener(object :
|
||||
screenShareZoomFrameLayout.viewTreeObserver.addOnGlobalLayoutListener(
|
||||
object :
|
||||
ViewTreeObserver.OnGlobalLayoutListener {
|
||||
override fun onGlobalLayout() {
|
||||
screenShareZoomFrameLayout.viewTreeObserver.removeOnGlobalLayoutListener(this)
|
||||
|
@ -53,7 +54,8 @@ internal class ScreenShareViewManager(
|
|||
setScreenShareLayoutSize()
|
||||
}, STREAM_SIZE_RETRY_DURATION)
|
||||
}
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
return screenShareZoomFrameLayout
|
||||
}
|
||||
|
|
|
@ -102,8 +102,11 @@ internal class ScreenShareZoomFrameLayout :
|
|||
}
|
||||
|
||||
override fun onLayout(
|
||||
changed: Boolean, left: Int,
|
||||
top: Int, right: Int, bottom: Int,
|
||||
changed: Boolean,
|
||||
left: Int,
|
||||
top: Int,
|
||||
right: Int,
|
||||
bottom: Int,
|
||||
) {
|
||||
super.onLayout(changed, left, top, right, bottom)
|
||||
getScreenShareViewBounds(screenShareViewBounds)
|
||||
|
@ -182,20 +185,22 @@ internal class ScreenShareZoomFrameLayout :
|
|||
val rect = RectF()
|
||||
rect.set(screenShareViewBounds)
|
||||
transform.mapRect(rect)
|
||||
val offsetLeft = getOffset(
|
||||
rect.left,
|
||||
rect.right,
|
||||
zoomFrameViewBounds.left,
|
||||
zoomFrameViewBounds.right,
|
||||
screenShareViewBounds.centerX()
|
||||
)
|
||||
val offsetTop = getOffset(
|
||||
rect.top,
|
||||
rect.bottom,
|
||||
zoomFrameViewBounds.top,
|
||||
zoomFrameViewBounds.bottom,
|
||||
screenShareViewBounds.centerY()
|
||||
)
|
||||
val offsetLeft =
|
||||
getOffset(
|
||||
rect.left,
|
||||
rect.right,
|
||||
zoomFrameViewBounds.left,
|
||||
zoomFrameViewBounds.right,
|
||||
screenShareViewBounds.centerX(),
|
||||
)
|
||||
val offsetTop =
|
||||
getOffset(
|
||||
rect.top,
|
||||
rect.bottom,
|
||||
zoomFrameViewBounds.top,
|
||||
zoomFrameViewBounds.bottom,
|
||||
screenShareViewBounds.centerY(),
|
||||
)
|
||||
|
||||
if (offsetLeft != 0f || offsetTop != 0f) {
|
||||
transform.postTranslate(offsetLeft, offsetTop)
|
||||
|
@ -232,7 +237,9 @@ internal class ScreenShareZoomFrameLayout :
|
|||
}
|
||||
return if (viewEnd < limitEnd) {
|
||||
limitEnd - viewEnd
|
||||
} else 0f
|
||||
} else {
|
||||
0f
|
||||
}
|
||||
}
|
||||
|
||||
private fun onDoubleTapDisplay(motionEvent: MotionEvent): Boolean {
|
||||
|
@ -257,11 +264,15 @@ internal class ScreenShareZoomFrameLayout :
|
|||
} else {
|
||||
if (shouldZoomToMax()) {
|
||||
zoomToPoint(
|
||||
MAX_SCALE, screenSharePoint, zoomLayoutPoint
|
||||
MAX_SCALE,
|
||||
screenSharePoint,
|
||||
zoomLayoutPoint,
|
||||
)
|
||||
} else {
|
||||
zoomToPoint(
|
||||
MIN_SCALE, screenSharePoint, zoomLayoutPoint
|
||||
MIN_SCALE,
|
||||
screenSharePoint,
|
||||
zoomLayoutPoint,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -284,10 +295,11 @@ internal class ScreenShareZoomFrameLayout :
|
|||
}
|
||||
|
||||
private fun shouldStartDoubleTapScroll(viewPoint: PointF): Boolean {
|
||||
val dist = hypot(
|
||||
(viewPoint.x - doubleTapZoomLayoutPoint.x).toDouble(),
|
||||
(viewPoint.y - doubleTapZoomLayoutPoint.y).toDouble()
|
||||
)
|
||||
val dist =
|
||||
hypot(
|
||||
(viewPoint.x - doubleTapZoomLayoutPoint.x).toDouble(),
|
||||
(viewPoint.y - doubleTapZoomLayoutPoint.y).toDouble(),
|
||||
)
|
||||
return dist > 20
|
||||
}
|
||||
|
||||
|
@ -332,7 +344,11 @@ internal class ScreenShareZoomFrameLayout :
|
|||
}
|
||||
}
|
||||
|
||||
private fun zoomToPoint(scale: Float, imagePoint: PointF, viewPoint: PointF) {
|
||||
private fun zoomToPoint(
|
||||
scale: Float,
|
||||
imagePoint: PointF,
|
||||
viewPoint: PointF,
|
||||
) {
|
||||
val matrix = Matrix()
|
||||
applyZoomToPointTransform(matrix, scale, imagePoint, viewPoint)
|
||||
setTransformAnimated(matrix)
|
||||
|
@ -380,20 +396,22 @@ internal class ScreenShareZoomFrameLayout :
|
|||
activeTransform.set(animatedMatrix)
|
||||
onTransformChanged()
|
||||
}
|
||||
doubleTapZoomAnimator.addListener(object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationCancel(animation: Animator) {
|
||||
onAnimationStopped()
|
||||
}
|
||||
doubleTapZoomAnimator.addListener(
|
||||
object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationCancel(animation: Animator) {
|
||||
onAnimationStopped()
|
||||
}
|
||||
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
onAnimationStopped()
|
||||
}
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
onAnimationStopped()
|
||||
}
|
||||
|
||||
private fun onAnimationStopped() {
|
||||
gestureListener.resetPointers()
|
||||
gestureListener.doubleTapZoomAnimationEnded()
|
||||
}
|
||||
})
|
||||
private fun onAnimationStopped() {
|
||||
gestureListener.resetPointers()
|
||||
gestureListener.doubleTapZoomAnimationEnded()
|
||||
}
|
||||
},
|
||||
)
|
||||
gestureListener.doubleTapZoomAnimationStarted()
|
||||
doubleTapZoomAnimator.start()
|
||||
}
|
||||
|
|
|
@ -113,9 +113,7 @@ internal class ParticipantListView(
|
|||
participantTable.layoutManager = LinearLayoutManager(context)
|
||||
}
|
||||
|
||||
private fun updateRemoteParticipantListContent(
|
||||
participantListCellModelList: List<ParticipantListCellModel>,
|
||||
) {
|
||||
private fun updateRemoteParticipantListContent(participantListCellModelList: List<ParticipantListCellModel>) {
|
||||
if (this::bottomCellAdapter.isInitialized) {
|
||||
val bottomCellItems = generateBottomCellItems(participantListCellModelList)
|
||||
updateRemoteParticipantListContent(bottomCellItems.size)
|
||||
|
@ -128,10 +126,10 @@ internal class ParticipantListView(
|
|||
|
||||
private fun updateLocalParticipantCellContent() {
|
||||
if (this::bottomCellAdapter.isInitialized) {
|
||||
|
||||
val bottomCellItems = generateBottomCellItems(
|
||||
viewModel.getRemoteParticipantListCellStateFlow().value
|
||||
)
|
||||
val bottomCellItems =
|
||||
generateBottomCellItems(
|
||||
viewModel.getRemoteParticipantListCellStateFlow().value,
|
||||
)
|
||||
|
||||
with(bottomCellAdapter) {
|
||||
setBottomCellItems(bottomCellItems)
|
||||
|
@ -141,7 +139,6 @@ internal class ParticipantListView(
|
|||
}
|
||||
|
||||
private fun updateRemoteParticipantListContent(listSize: Int) {
|
||||
|
||||
// title for in call participants
|
||||
var titles = 1
|
||||
|
||||
|
@ -153,20 +150,19 @@ internal class ParticipantListView(
|
|||
// set the height of the list to be half of the screen height or 50dp per item, whichever is smaller
|
||||
participantTable.layoutParams.height =
|
||||
(((listSize - titles) * 50 * context.resources.displayMetrics.density + titles * 30 * context.resources.displayMetrics.density).toInt()).coerceAtMost(
|
||||
context.resources.displayMetrics.heightPixels / 2
|
||||
context.resources.displayMetrics.heightPixels / 2,
|
||||
)
|
||||
}
|
||||
|
||||
private fun generateBottomCellItems(
|
||||
remoteParticipantCellModels: List<ParticipantListCellModel>,
|
||||
): MutableList<BottomCellItem> {
|
||||
private fun generateBottomCellItems(remoteParticipantCellModels: List<ParticipantListCellModel>): MutableList<BottomCellItem> {
|
||||
val bottomCellItemsInCallParticipants = mutableListOf<BottomCellItem>()
|
||||
val bottomCellItemsInLobbyParticipants = mutableListOf<BottomCellItem>()
|
||||
// since we can not get resources from model class, we create the local participant list cell
|
||||
// with suffix in this way
|
||||
val localParticipant = viewModel.createLocalParticipantListCell(
|
||||
resources.getString(R.string.azure_communication_ui_calling_view_participant_drawer_local_participant)
|
||||
)
|
||||
val localParticipant =
|
||||
viewModel.createLocalParticipantListCell(
|
||||
resources.getString(R.string.azure_communication_ui_calling_view_participant_drawer_local_participant),
|
||||
)
|
||||
val localParticipantViewData =
|
||||
avatarViewManager.callCompositeLocalOptions?.participantViewData
|
||||
bottomCellItemsInCallParticipants
|
||||
|
@ -174,14 +170,14 @@ internal class ParticipantListView(
|
|||
generateBottomCellItem(
|
||||
getLocalParticipantNameToDisplay(
|
||||
localParticipantViewData,
|
||||
localParticipant.displayName
|
||||
localParticipant.displayName,
|
||||
),
|
||||
localParticipant.isMuted,
|
||||
localParticipantViewData,
|
||||
localParticipant.isOnHold,
|
||||
localParticipant.userIdentifier,
|
||||
localParticipant.status
|
||||
)
|
||||
localParticipant.status,
|
||||
),
|
||||
)
|
||||
|
||||
for (remoteParticipant in remoteParticipantCellModels) {
|
||||
|
@ -198,8 +194,8 @@ internal class ParticipantListView(
|
|||
remoteParticipantViewData,
|
||||
null,
|
||||
remoteParticipant.userIdentifier,
|
||||
remoteParticipant.status
|
||||
)
|
||||
remoteParticipant.status,
|
||||
),
|
||||
)
|
||||
} else if (remoteParticipant.status != ParticipantStatus.DISCONNECTED) {
|
||||
bottomCellItemsInCallParticipants.add(
|
||||
|
@ -209,8 +205,8 @@ internal class ParticipantListView(
|
|||
remoteParticipantViewData,
|
||||
remoteParticipant.isOnHold,
|
||||
remoteParticipant.userIdentifier,
|
||||
remoteParticipant.status
|
||||
)
|
||||
remoteParticipant.status,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -221,7 +217,7 @@ internal class ParticipantListView(
|
|||
null,
|
||||
context.getString(
|
||||
R.string.azure_communication_ui_calling_participant_list_in_call_n_people,
|
||||
bottomCellItemsInCallParticipants.size
|
||||
bottomCellItemsInCallParticipants.size,
|
||||
),
|
||||
"",
|
||||
null,
|
||||
|
@ -231,8 +227,8 @@ internal class ParticipantListView(
|
|||
null,
|
||||
false,
|
||||
BottomCellItemType.BottomMenuTitle,
|
||||
null
|
||||
)
|
||||
null,
|
||||
),
|
||||
)
|
||||
|
||||
bottomCellItemsInLobbyParticipants.sortWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.title!! })
|
||||
|
@ -243,7 +239,7 @@ internal class ParticipantListView(
|
|||
null,
|
||||
context.getString(
|
||||
R.string.azure_communication_ui_calling_participant_list_in_lobby_n_people,
|
||||
bottomCellItemsInLobbyParticipants.size
|
||||
bottomCellItemsInLobbyParticipants.size,
|
||||
),
|
||||
"",
|
||||
null,
|
||||
|
@ -257,8 +253,8 @@ internal class ParticipantListView(
|
|||
true,
|
||||
admitAllButtonAction = {
|
||||
admitAllLobbyParticipants()
|
||||
}
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
return (bottomCellItemsInLobbyParticipants + bottomCellItemsInCallParticipants).toMutableList()
|
||||
|
@ -288,27 +284,44 @@ internal class ParticipantListView(
|
|||
userIdentifier: String,
|
||||
status: ParticipantStatus?,
|
||||
): BottomCellItem {
|
||||
val micIcon = ContextCompat.getDrawable(
|
||||
context,
|
||||
if (isMuted == true) R.drawable.azure_communication_ui_calling_ic_fluent_mic_off_24_filled_composite_button_filled_grey
|
||||
else R.drawable.azure_communication_ui_calling_ic_fluent_mic_on_24_filled_composite_button_filled_grey
|
||||
)
|
||||
val micIcon =
|
||||
ContextCompat.getDrawable(
|
||||
context,
|
||||
if (isMuted == true) {
|
||||
R.drawable.azure_communication_ui_calling_ic_fluent_mic_off_24_filled_composite_button_filled_grey
|
||||
} else {
|
||||
R.drawable.azure_communication_ui_calling_ic_fluent_mic_on_24_filled_composite_button_filled_grey
|
||||
},
|
||||
)
|
||||
|
||||
val micAccessibilityAnnouncement = context.getString(
|
||||
if (isMuted == true) R.string.azure_communication_ui_calling_view_participant_list_muted_accessibility_label
|
||||
else R.string.azure_communication_ui_calling_view_participant_list_unmuted_accessibility_label
|
||||
)
|
||||
val micAccessibilityAnnouncement =
|
||||
context.getString(
|
||||
if (isMuted == true) {
|
||||
R.string.azure_communication_ui_calling_view_participant_list_muted_accessibility_label
|
||||
} else {
|
||||
R.string.azure_communication_ui_calling_view_participant_list_unmuted_accessibility_label
|
||||
},
|
||||
)
|
||||
|
||||
val onHoldAnnouncement: String = if (isOnHold == true) context.getString(R.string.azure_communication_ui_calling_remote_participant_on_hold) else ""
|
||||
val onHoldAnnouncement: String =
|
||||
if (isOnHold == true) {
|
||||
context.getString(
|
||||
R.string.azure_communication_ui_calling_remote_participant_on_hold,
|
||||
)
|
||||
} else {
|
||||
""
|
||||
}
|
||||
|
||||
return BottomCellItem(
|
||||
null,
|
||||
displayName,
|
||||
displayName +
|
||||
if (status == ParticipantStatus.IN_LOBBY)
|
||||
if (status == ParticipantStatus.IN_LOBBY) {
|
||||
context.getString(R.string.azure_communication_ui_calling_view_participant_list_dismiss_lobby_list)
|
||||
else context.getString(R.string.azure_communication_ui_calling_view_participant_list_dismiss_list) +
|
||||
onHoldAnnouncement,
|
||||
} else {
|
||||
context.getString(R.string.azure_communication_ui_calling_view_participant_list_dismiss_list) +
|
||||
onHoldAnnouncement
|
||||
},
|
||||
if (status != ParticipantStatus.IN_LOBBY) micIcon else null,
|
||||
if (status != ParticipantStatus.IN_LOBBY) R.color.azure_communication_ui_calling_color_participant_list_mute_mic else null,
|
||||
micAccessibilityAnnouncement,
|
||||
|
@ -321,30 +334,33 @@ internal class ParticipantListView(
|
|||
} else if (accessibilityManager.isEnabled) {
|
||||
participantListDrawer.dismiss()
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun showAdmitDialog(displayName: String?, userIdentifier: String) {
|
||||
private fun showAdmitDialog(
|
||||
displayName: String?,
|
||||
userIdentifier: String,
|
||||
) {
|
||||
val builder =
|
||||
AlertDialog.Builder(context, R.style.AzureCommunicationUICalling_AlertDialog_Theme)
|
||||
builder.setMessage(
|
||||
context.getString(
|
||||
R.string.azure_communication_ui_calling_admit_name,
|
||||
displayName
|
||||
)
|
||||
displayName,
|
||||
),
|
||||
)
|
||||
.setPositiveButton(
|
||||
context.getString(
|
||||
R.string.azure_communication_ui_calling_admit
|
||||
)
|
||||
R.string.azure_communication_ui_calling_admit,
|
||||
),
|
||||
) { _, _ ->
|
||||
viewModel.admitParticipant(userIdentifier)
|
||||
}
|
||||
.setNegativeButton(
|
||||
context.getString(
|
||||
R.string.azure_communication_ui_calling_decline
|
||||
)
|
||||
R.string.azure_communication_ui_calling_decline,
|
||||
),
|
||||
) { _, _ ->
|
||||
viewModel.declineParticipant(userIdentifier)
|
||||
}
|
||||
|
|
|
@ -29,14 +29,19 @@ internal class ParticipantListViewModel(private val dispatch: (Action) -> Unit)
|
|||
return displayParticipantListStateFlow
|
||||
}
|
||||
|
||||
fun createLocalParticipantListCell(suffix: String) = ParticipantListCellModel(
|
||||
(localParticipantListCellStateFlow.value.displayName.trim() + " " + suffix).trim(),
|
||||
localParticipantListCellStateFlow.value.isMuted,
|
||||
"",
|
||||
false
|
||||
)
|
||||
fun createLocalParticipantListCell(suffix: String) =
|
||||
ParticipantListCellModel(
|
||||
(localParticipantListCellStateFlow.value.displayName.trim() + " " + suffix).trim(),
|
||||
localParticipantListCellStateFlow.value.isMuted,
|
||||
"",
|
||||
false,
|
||||
)
|
||||
|
||||
fun init(participantMap: Map<String, ParticipantInfoModel>, localUserState: LocalUserState, canShowLobby: Boolean) {
|
||||
fun init(
|
||||
participantMap: Map<String, ParticipantInfoModel>,
|
||||
localUserState: LocalUserState,
|
||||
canShowLobby: Boolean,
|
||||
) {
|
||||
val remoteParticipantList: List<ParticipantListCellModel> =
|
||||
participantMap.values.map {
|
||||
getRemoteParticipantListCellModel(it)
|
||||
|
@ -47,7 +52,11 @@ internal class ParticipantListViewModel(private val dispatch: (Action) -> Unit)
|
|||
MutableStateFlow(getLocalParticipantListCellModel(localUserState))
|
||||
}
|
||||
|
||||
fun update(participantMap: Map<String, ParticipantInfoModel>, localUserState: LocalUserState, canShowLobby: Boolean) {
|
||||
fun update(
|
||||
participantMap: Map<String, ParticipantInfoModel>,
|
||||
localUserState: LocalUserState,
|
||||
canShowLobby: Boolean,
|
||||
) {
|
||||
val remoteParticipantList: MutableList<ParticipantListCellModel> =
|
||||
participantMap.values.map {
|
||||
getRemoteParticipantListCellModel(it)
|
||||
|
@ -63,7 +72,7 @@ internal class ParticipantListViewModel(private val dispatch: (Action) -> Unit)
|
|||
) = (
|
||||
it.status != ParticipantStatus.DISCONNECTED &&
|
||||
if (it.status == ParticipantStatus.IN_LOBBY) canShowLobby else true
|
||||
)
|
||||
)
|
||||
|
||||
fun displayParticipantList() {
|
||||
displayParticipantListStateFlow.value = true
|
||||
|
@ -91,13 +100,14 @@ internal class ParticipantListViewModel(private val dispatch: (Action) -> Unit)
|
|||
localUserDisplayName ?: "",
|
||||
localUserState.audioState.operation == AudioOperationalStatus.OFF,
|
||||
"",
|
||||
false
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
private fun getRemoteParticipantListCellModel(it: ParticipantInfoModel): ParticipantListCellModel {
|
||||
return ParticipantListCellModel(
|
||||
it.displayName.trim(), it.isMuted,
|
||||
it.displayName.trim(),
|
||||
it.isMuted,
|
||||
it.userIdentifier,
|
||||
it.participantStatus == ParticipantStatus.HOLD,
|
||||
it.participantStatus,
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the MIT License.
|
||||
|
||||
@file:OptIn(InternalCoroutinesApi::class)
|
||||
|
||||
package com.azure.android.communication.ui.calling.presentation.fragment.calling.support
|
||||
|
||||
import android.content.Context
|
||||
|
@ -27,7 +28,6 @@ import kotlinx.coroutines.launch
|
|||
* It is displayed when the user clicks on the support button.
|
||||
*/
|
||||
internal class SupportView : ConstraintLayout {
|
||||
|
||||
private val sendButton: Button by lazy { findViewById(R.id.azure_communication_ui_send_button) }
|
||||
private val cancelButton: Button by lazy { findViewById(R.id.azure_communication_ui_cancel_button) }
|
||||
private val editText: EditText by lazy { findViewById(R.id.azure_communication_ui_user_message_edit_text) }
|
||||
|
@ -53,7 +53,10 @@ internal class SupportView : ConstraintLayout {
|
|||
inflate(context, R.layout.azure_communication_ui_calling_support_view, this)
|
||||
}
|
||||
|
||||
fun start(viewModel: SupportViewModel, viewLifecycleOwner: LifecycleOwner) {
|
||||
fun start(
|
||||
viewModel: SupportViewModel,
|
||||
viewLifecycleOwner: LifecycleOwner,
|
||||
) {
|
||||
// Text Changed, Submit, Cancel Buttons
|
||||
bindViewInputs(viewModel)
|
||||
|
||||
|
@ -88,7 +91,7 @@ internal class SupportView : ConstraintLayout {
|
|||
|
||||
private fun bindViewOutputs(
|
||||
viewLifecycleOwner: LifecycleOwner,
|
||||
viewModel: SupportViewModel
|
||||
viewModel: SupportViewModel,
|
||||
) {
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewModel.isVisibleStateFlow.collect {
|
||||
|
|
|
@ -10,7 +10,6 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
internal class SupportViewModel(private val dispatch: Dispatch, private val onSubmit: (String) -> Unit) {
|
||||
|
||||
private var _isVisibleStateFlow: MutableStateFlow<Boolean> = MutableStateFlow(false)
|
||||
private val _isSubmitEnabledStateFlow: MutableStateFlow<Boolean> = MutableStateFlow(false)
|
||||
|
||||
|
|
|
@ -24,7 +24,6 @@ internal class AudioDeviceListView(
|
|||
private val viewModel: AudioDeviceListViewModel,
|
||||
context: Context,
|
||||
) : RelativeLayout(context) {
|
||||
|
||||
private var deviceTable: RecyclerView
|
||||
private lateinit var audioDeviceDrawer: DrawerDialog
|
||||
private lateinit var bottomCellAdapter: BottomCellAdapter
|
||||
|
@ -97,7 +96,7 @@ internal class AudioDeviceListView(
|
|||
BottomCellItem(
|
||||
ContextCompat.getDrawable(
|
||||
context,
|
||||
R.drawable.azure_communication_ui_calling_ic_fluent_speaker_2_24_regular_composite_button_filled
|
||||
R.drawable.azure_communication_ui_calling_ic_fluent_speaker_2_24_regular_composite_button_filled,
|
||||
),
|
||||
when (viewModel.audioStateFlow.value.isHeadphonePlugged) {
|
||||
true -> context.getString(R.string.azure_communication_ui_calling_audio_device_drawer_headphone)
|
||||
|
@ -106,7 +105,7 @@ internal class AudioDeviceListView(
|
|||
null,
|
||||
ContextCompat.getDrawable(
|
||||
context,
|
||||
R.drawable.ms_ic_checkmark_24_filled
|
||||
R.drawable.ms_ic_checkmark_24_filled,
|
||||
),
|
||||
null,
|
||||
context.getString(R.string.azure_communication_ui_calling_setup_view_audio_device_selected_accessibility_label),
|
||||
|
@ -116,8 +115,8 @@ internal class AudioDeviceListView(
|
|||
onClickAction = {
|
||||
viewModel.switchAudioDevice(AudioDeviceSelectionStatus.RECEIVER_REQUESTED)
|
||||
audioDeviceDrawer.dismiss()
|
||||
}
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -125,13 +124,13 @@ internal class AudioDeviceListView(
|
|||
BottomCellItem(
|
||||
ContextCompat.getDrawable(
|
||||
context,
|
||||
R.drawable.azure_communication_ui_calling_ic_fluent_speaker_2_24_filled_composite_button_enabled
|
||||
R.drawable.azure_communication_ui_calling_ic_fluent_speaker_2_24_filled_composite_button_enabled,
|
||||
),
|
||||
context.getString(R.string.azure_communication_ui_calling_audio_device_drawer_speaker),
|
||||
null,
|
||||
ContextCompat.getDrawable(
|
||||
context,
|
||||
R.drawable.ms_ic_checkmark_24_filled
|
||||
R.drawable.ms_ic_checkmark_24_filled,
|
||||
),
|
||||
null,
|
||||
context.getString(R.string.azure_communication_ui_calling_setup_view_audio_device_selected_accessibility_label),
|
||||
|
@ -141,8 +140,8 @@ internal class AudioDeviceListView(
|
|||
onClickAction = {
|
||||
viewModel.switchAudioDevice(AudioDeviceSelectionStatus.SPEAKER_REQUESTED)
|
||||
audioDeviceDrawer.dismiss()
|
||||
}
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
if (viewModel.audioStateFlow.value.bluetoothState.available) {
|
||||
|
@ -152,15 +151,14 @@ internal class AudioDeviceListView(
|
|||
BottomCellItem(
|
||||
ContextCompat.getDrawable(
|
||||
context,
|
||||
R.drawable.azure_communication_ui_calling_ic_fluent_speaker_bluetooth_24_regular
|
||||
R.drawable.azure_communication_ui_calling_ic_fluent_speaker_bluetooth_24_regular,
|
||||
),
|
||||
viewModel.audioStateFlow.value.bluetoothState.deviceName,
|
||||
null,
|
||||
ContextCompat.getDrawable(
|
||||
context,
|
||||
R.drawable.ms_ic_checkmark_24_filled
|
||||
R.drawable.ms_ic_checkmark_24_filled,
|
||||
),
|
||||
|
||||
null,
|
||||
context.getString(R.string.azure_communication_ui_calling_setup_view_audio_device_selected_accessibility_label),
|
||||
isChecked = initialDevice == AudioDeviceSelectionStatus.BLUETOOTH_SCO_SELECTED,
|
||||
|
@ -169,8 +167,8 @@ internal class AudioDeviceListView(
|
|||
onClickAction = {
|
||||
viewModel.switchAudioDevice(AudioDeviceSelectionStatus.BLUETOOTH_SCO_REQUESTED)
|
||||
audioDeviceDrawer.dismiss()
|
||||
}
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
return bottomCellItems
|
||||
|
@ -182,8 +180,8 @@ internal class AudioDeviceListView(
|
|||
announceForAccessibility(
|
||||
context.getString(
|
||||
R.string.azure_communication_ui_calling_selected_audio_device_announcement,
|
||||
getDeviceTypeName(audioState)
|
||||
)
|
||||
getDeviceTypeName(audioState),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,6 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
internal class AudioDeviceListViewModel(private val dispatch: (Action) -> Unit) {
|
||||
|
||||
private val displayAudioDeviceSelectionMenuMutableStateFlow = MutableStateFlow(false)
|
||||
|
||||
private lateinit var audioStateMutableStateFlow: MutableStateFlow<AudioState>
|
||||
|
@ -29,8 +28,8 @@ internal class AudioDeviceListViewModel(private val dispatch: (Action) -> Unit)
|
|||
fun switchAudioDevice(audioDeviceSelectionStatus: AudioDeviceSelectionStatus) {
|
||||
dispatch(
|
||||
LocalParticipantAction.AudioDeviceChangeRequested(
|
||||
audioDeviceSelectionStatus
|
||||
)
|
||||
audioDeviceSelectionStatus,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -5,20 +5,20 @@ package com.azure.android.communication.ui.calling.presentation.fragment.factori
|
|||
|
||||
import com.azure.android.communication.ui.calling.presentation.fragment.calling.banner.BannerViewModel
|
||||
import com.azure.android.communication.ui.calling.presentation.fragment.calling.controlbar.ControlBarViewModel
|
||||
import com.azure.android.communication.ui.calling.presentation.fragment.calling.controlbar.more.MoreCallOptionsListViewModel
|
||||
import com.azure.android.communication.ui.calling.presentation.fragment.calling.hangup.LeaveConfirmViewModel
|
||||
import com.azure.android.communication.ui.calling.presentation.fragment.calling.header.InfoHeaderViewModel
|
||||
import com.azure.android.communication.ui.calling.presentation.fragment.calling.hold.OnHoldOverlayViewModel
|
||||
import com.azure.android.communication.ui.calling.presentation.fragment.calling.lobby.ConnectingLobbyOverlayViewModel
|
||||
import com.azure.android.communication.ui.calling.presentation.fragment.calling.lobby.LobbyErrorHeaderViewModel
|
||||
import com.azure.android.communication.ui.calling.presentation.fragment.calling.lobby.LobbyHeaderViewModel
|
||||
import com.azure.android.communication.ui.calling.presentation.fragment.calling.lobby.WaitingLobbyOverlayViewModel
|
||||
import com.azure.android.communication.ui.calling.presentation.fragment.calling.localuser.LocalParticipantViewModel
|
||||
import com.azure.android.communication.ui.calling.presentation.fragment.calling.notification.ToastNotificationViewModel
|
||||
import com.azure.android.communication.ui.calling.presentation.fragment.calling.notification.UpperMessageBarNotificationLayoutViewModel
|
||||
import com.azure.android.communication.ui.calling.presentation.fragment.calling.participant.grid.ParticipantGridViewModel
|
||||
import com.azure.android.communication.ui.calling.presentation.fragment.calling.participantlist.ParticipantListViewModel
|
||||
import com.azure.android.communication.ui.calling.presentation.fragment.common.audiodevicelist.AudioDeviceListViewModel
|
||||
import com.azure.android.communication.ui.calling.presentation.fragment.calling.controlbar.more.MoreCallOptionsListViewModel
|
||||
import com.azure.android.communication.ui.calling.presentation.fragment.calling.lobby.ConnectingLobbyOverlayViewModel
|
||||
import com.azure.android.communication.ui.calling.presentation.fragment.calling.notification.ToastNotificationViewModel
|
||||
import com.azure.android.communication.ui.calling.presentation.fragment.calling.notification.UpperMessageBarNotificationLayoutViewModel
|
||||
import com.azure.android.communication.ui.calling.presentation.manager.DebugInfoManager
|
||||
import com.azure.android.communication.ui.calling.redux.Store
|
||||
import com.azure.android.communication.ui.calling.redux.state.ReduxState
|
||||
|
@ -31,12 +31,11 @@ internal class CallingViewModelFactory(
|
|||
private val showSupportFormOption: Boolean = false,
|
||||
private val enableMultitasking: Boolean,
|
||||
) : BaseViewModelFactory(store) {
|
||||
|
||||
val moreCallOptionsListViewModel by lazy {
|
||||
MoreCallOptionsListViewModel(
|
||||
debugInfoManager,
|
||||
showSupportFormOption = showSupportFormOption,
|
||||
store::dispatch
|
||||
store::dispatch,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -7,9 +7,7 @@ import com.azure.android.communication.ui.calling.models.ParticipantInfoModel
|
|||
import com.azure.android.communication.ui.calling.presentation.fragment.calling.participant.grid.ParticipantGridCellViewModel
|
||||
|
||||
internal class ParticipantGridCellViewModelFactory {
|
||||
fun ParticipantGridCellViewModel(
|
||||
participantInfoModel: ParticipantInfoModel,
|
||||
): ParticipantGridCellViewModel =
|
||||
fun ParticipantGridCellViewModel(participantInfoModel: ParticipantInfoModel): ParticipantGridCellViewModel =
|
||||
ParticipantGridCellViewModel(
|
||||
participantInfoModel.userIdentifier,
|
||||
participantInfoModel.displayName,
|
||||
|
|
|
@ -18,7 +18,6 @@ internal class SetupViewModelFactory(
|
|||
private val store: Store<ReduxState>,
|
||||
private val context: Context,
|
||||
) : BaseViewModelFactory(store) {
|
||||
|
||||
val audioDeviceListViewModel by lazy {
|
||||
AudioDeviceListViewModel(store::dispatch)
|
||||
}
|
||||
|
@ -42,7 +41,7 @@ internal class SetupViewModelFactory(
|
|||
val joinCallButtonHolderViewModel by lazy {
|
||||
JoinCallButtonHolderViewModel(
|
||||
store::dispatch,
|
||||
context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||
context.getSystemService(Context.AUDIO_SERVICE) as AudioManager,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,6 @@ import com.azure.android.communication.ui.calling.presentation.fragment.setup.co
|
|||
|
||||
internal class SetupFragment :
|
||||
Fragment(R.layout.azure_communication_ui_calling_fragment_setup) {
|
||||
|
||||
// Get the DI Container, which gives us what we need for this fragment (dependencies)
|
||||
private val holder: DependencyInjectionContainerHolder by activityViewModels()
|
||||
|
||||
|
@ -49,7 +48,10 @@ internal class SetupFragment :
|
|||
private val networkManager get() = holder.container.networkManager
|
||||
private val viewModel get() = holder.setupViewModel
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
override fun onViewCreated(
|
||||
view: View,
|
||||
savedInstanceState: Bundle?,
|
||||
) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
viewModel.init(viewLifecycleOwner.lifecycleScope)
|
||||
|
||||
|
@ -62,7 +64,7 @@ internal class SetupFragment :
|
|||
view.findViewById(R.id.azure_communication_ui_setup_join_call_holder)
|
||||
setupJoinCallButtonHolderView.start(
|
||||
viewLifecycleOwner,
|
||||
viewModel.joinCallButtonHolderViewModel
|
||||
viewModel.joinCallButtonHolderViewModel,
|
||||
)
|
||||
|
||||
participantAvatarView = view.findViewById(R.id.azure_communication_ui_setup_default_avatar)
|
||||
|
@ -130,26 +132,30 @@ internal class SetupFragment :
|
|||
get() = (activity as AppCompatActivity)
|
||||
|
||||
private fun setActionBarTitle() {
|
||||
fun setActionbarTextColor(text: SpannableString, @ColorInt color: Int) {
|
||||
fun setActionbarTextColor(
|
||||
text: SpannableString,
|
||||
@ColorInt color: Int,
|
||||
) {
|
||||
text.setSpan(
|
||||
ForegroundColorSpan(
|
||||
ContextCompat.getColor(
|
||||
requireContext(),
|
||||
color
|
||||
)
|
||||
color,
|
||||
),
|
||||
),
|
||||
0,
|
||||
text.length,
|
||||
Spannable.SPAN_INCLUSIVE_INCLUSIVE
|
||||
Spannable.SPAN_INCLUSIVE_INCLUSIVE,
|
||||
)
|
||||
}
|
||||
|
||||
val localOptions = holder.container.configuration.callCompositeLocalOptions
|
||||
val titleSpan = if (!TextUtils.isEmpty(localOptions?.setupScreenViewData?.title)) {
|
||||
SpannableString(localOptions?.setupScreenViewData?.title)
|
||||
} else {
|
||||
SpannableString(getString(R.string.azure_communication_ui_calling_call_setup_action_bar_title))
|
||||
}
|
||||
val titleSpan =
|
||||
if (!TextUtils.isEmpty(localOptions?.setupScreenViewData?.title)) {
|
||||
SpannableString(localOptions?.setupScreenViewData?.title)
|
||||
} else {
|
||||
SpannableString(getString(R.string.azure_communication_ui_calling_call_setup_action_bar_title))
|
||||
}
|
||||
|
||||
setActionbarTextColor(titleSpan, R.color.azure_communication_ui_calling_color_action_bar_text)
|
||||
|
||||
|
@ -163,7 +169,7 @@ internal class SetupFragment :
|
|||
callCompositeActivity.supportActionBar?.subtitle = subtitleSpan
|
||||
} else {
|
||||
holder.container.logger.error(
|
||||
"Provided setupScreenViewData has subtitle, but no title provided. In this case subtitle is not displayed."
|
||||
"Provided setupScreenViewData has subtitle, but no title provided. In this case subtitle is not displayed.",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,6 @@ internal class SetupViewModel(
|
|||
private val networkManager: NetworkManager,
|
||||
) :
|
||||
BaseViewModel(store) {
|
||||
|
||||
val warningsViewModel = setupViewModelProvider.warningsViewModel
|
||||
val setupControlBarViewModel = setupViewModelProvider.setupControlBarViewModel
|
||||
val localParticipantRendererViewModel = setupViewModelProvider.previewAreaViewModel
|
||||
|
@ -68,7 +67,7 @@ internal class SetupViewModel(
|
|||
)
|
||||
setupGradientViewModel.init(
|
||||
state.localParticipantState.videoStreamID,
|
||||
state.localParticipantState.cameraState.operation
|
||||
state.localParticipantState.cameraState.operation,
|
||||
)
|
||||
participantAvatarViewModel.init(
|
||||
state.localParticipantState.displayName,
|
||||
|
@ -80,14 +79,13 @@ internal class SetupViewModel(
|
|||
state.permissionState.cameraPermissionState,
|
||||
state.localParticipantState.cameraState.operation,
|
||||
state.localParticipantState.cameraState.camerasCount,
|
||||
networkManager
|
||||
networkManager,
|
||||
)
|
||||
|
||||
super.init(coroutineScope)
|
||||
}
|
||||
|
||||
override suspend fun onStateChange(state: ReduxState) {
|
||||
|
||||
setupControlBarViewModel.update(
|
||||
state.permissionState,
|
||||
state.localParticipantState.cameraState,
|
||||
|
@ -99,29 +97,29 @@ internal class SetupViewModel(
|
|||
state.localParticipantState.videoStreamID,
|
||||
)
|
||||
audioDeviceListViewModel.update(
|
||||
state.localParticipantState.audioState
|
||||
state.localParticipantState.audioState,
|
||||
)
|
||||
errorInfoViewModel.updateCallStateError(state.errorState)
|
||||
errorInfoViewModel.updateAudioFocusRejectedState(
|
||||
state.audioSessionState.audioFocusStatus == AudioFocusStatus.REJECTED
|
||||
state.audioSessionState.audioFocusStatus == AudioFocusStatus.REJECTED,
|
||||
)
|
||||
state.localParticipantState.cameraState.error?.let {
|
||||
errorInfoViewModel.updateCallCompositeError(it)
|
||||
}
|
||||
setupGradientViewModel.update(
|
||||
state.localParticipantState.videoStreamID,
|
||||
state.localParticipantState.cameraState.operation
|
||||
state.localParticipantState.cameraState.operation,
|
||||
)
|
||||
participantAvatarViewModel.update(
|
||||
state.localParticipantState.videoStreamID,
|
||||
state.permissionState
|
||||
state.permissionState,
|
||||
)
|
||||
joinCallButtonHolderViewModel.update(
|
||||
state.permissionState.audioPermissionState,
|
||||
state.callState,
|
||||
state.permissionState.cameraPermissionState,
|
||||
state.localParticipantState.cameraState.operation,
|
||||
state.localParticipantState.cameraState.camerasCount
|
||||
state.localParticipantState.cameraState.camerasCount,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,9 +15,9 @@ import androidx.core.content.ContextCompat
|
|||
import androidx.core.view.ViewCompat
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.azure.android.communication.ui.calling.implementation.R
|
||||
import com.azure.android.communication.ui.calling.error.CallStateError
|
||||
import com.azure.android.communication.ui.calling.error.ErrorCode
|
||||
import com.azure.android.communication.ui.calling.implementation.R
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeEventCode
|
||||
import com.google.android.material.snackbar.BaseTransientBottomBar.ANIMATION_MODE_FADE
|
||||
import com.microsoft.fluentui.snackbar.Snackbar
|
||||
|
@ -28,7 +28,10 @@ internal class ErrorInfoView(private val rootView: View) {
|
|||
private lateinit var snackBar: Snackbar
|
||||
private lateinit var snackBarTextView: TextView
|
||||
|
||||
fun start(viewLifecycleOwner: LifecycleOwner, snackBarViewModel: ErrorInfoViewModel) {
|
||||
fun start(
|
||||
viewLifecycleOwner: LifecycleOwner,
|
||||
snackBarViewModel: ErrorInfoViewModel,
|
||||
) {
|
||||
initSnackBar()
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
snackBarViewModel.getCallStateErrorStateFlow().collect {
|
||||
|
@ -49,7 +52,7 @@ internal class ErrorInfoView(private val rootView: View) {
|
|||
displaySnackBar(
|
||||
null,
|
||||
rootView.context.getText(R.string.azure_communication_ui_calling_call_video_fails_error)
|
||||
.toString()
|
||||
.toString(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -66,12 +69,16 @@ internal class ErrorInfoView(private val rootView: View) {
|
|||
snackBar.anchorView = null
|
||||
}
|
||||
|
||||
private fun displaySnackBar(it: CallStateError?, message: String) {
|
||||
val errorMessage = if (it != null) {
|
||||
getErrorMessage(it)
|
||||
} else {
|
||||
message
|
||||
}
|
||||
private fun displaySnackBar(
|
||||
it: CallStateError?,
|
||||
message: String,
|
||||
) {
|
||||
val errorMessage =
|
||||
if (it != null) {
|
||||
getErrorMessage(it)
|
||||
} else {
|
||||
message
|
||||
}
|
||||
|
||||
if (errorMessage.isBlank()) return
|
||||
snackBarTextView.text = errorMessage
|
||||
|
@ -101,46 +108,48 @@ internal class ErrorInfoView(private val rootView: View) {
|
|||
}
|
||||
|
||||
private fun initSnackBar() {
|
||||
snackBar = Snackbar.make(
|
||||
rootView,
|
||||
"",
|
||||
Snackbar.LENGTH_INDEFINITE,
|
||||
Snackbar.Style.REGULAR
|
||||
).apply {
|
||||
animationMode = ANIMATION_MODE_FADE
|
||||
setAction(rootView.context!!.getText(R.string.azure_communication_ui_calling_snack_bar_button_dismiss)) {}
|
||||
anchorView =
|
||||
rootView.findViewById(R.id.azure_communication_ui_setup_join_call_button)
|
||||
view.background.colorFilter = PorterDuffColorFilter(
|
||||
ContextCompat.getColor(
|
||||
rootView.context,
|
||||
R.color.azure_communication_ui_calling_color_snack_bar_background
|
||||
),
|
||||
PorterDuff.Mode.SRC_IN
|
||||
)
|
||||
snackBarTextView = view.findViewById(R.id.snackbar_text)
|
||||
snackBarTextView.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
rootView.context,
|
||||
R.color.azure_communication_ui_calling_color_snack_bar_text_color
|
||||
)
|
||||
)
|
||||
view.findViewById<AppCompatButton>(R.id.snackbar_action).apply {
|
||||
setTextColor(
|
||||
snackBar =
|
||||
Snackbar.make(
|
||||
rootView,
|
||||
"",
|
||||
Snackbar.LENGTH_INDEFINITE,
|
||||
Snackbar.Style.REGULAR,
|
||||
).apply {
|
||||
animationMode = ANIMATION_MODE_FADE
|
||||
setAction(rootView.context!!.getText(R.string.azure_communication_ui_calling_snack_bar_button_dismiss)) {}
|
||||
anchorView =
|
||||
rootView.findViewById(R.id.azure_communication_ui_setup_join_call_button)
|
||||
view.background.colorFilter =
|
||||
PorterDuffColorFilter(
|
||||
ContextCompat.getColor(
|
||||
rootView.context,
|
||||
R.color.azure_communication_ui_calling_color_snack_bar_background,
|
||||
),
|
||||
PorterDuff.Mode.SRC_IN,
|
||||
)
|
||||
snackBarTextView = view.findViewById(R.id.snackbar_text)
|
||||
snackBarTextView.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
rootView.context,
|
||||
R.color.azure_communication_ui_calling_color_snack_bar_text_color
|
||||
)
|
||||
R.color.azure_communication_ui_calling_color_snack_bar_text_color,
|
||||
),
|
||||
)
|
||||
view.findViewById<AppCompatButton>(R.id.snackbar_action).apply {
|
||||
setTextColor(
|
||||
ContextCompat.getColor(
|
||||
rootView.context,
|
||||
R.color.azure_communication_ui_calling_color_snack_bar_text_color,
|
||||
),
|
||||
)
|
||||
isAllCaps = false
|
||||
contentDescription =
|
||||
rootView.context.getText(R.string.azure_communication_ui_calling_snack_bar_button_dismiss)
|
||||
}
|
||||
ViewCompat.setImportantForAccessibility(
|
||||
view,
|
||||
ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES,
|
||||
)
|
||||
isAllCaps = false
|
||||
contentDescription =
|
||||
rootView.context.getText(R.string.azure_communication_ui_calling_snack_bar_button_dismiss)
|
||||
}
|
||||
ViewCompat.setImportantForAccessibility(
|
||||
view,
|
||||
ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun View.accessibilityFocus(): View {
|
||||
|
|
|
@ -14,6 +14,7 @@ internal class ErrorInfoViewModel {
|
|||
private val _callCompositeErrorFlow: MutableStateFlow<CallCompositeError?> = MutableStateFlow(null)
|
||||
private val audioFocusRejectedFlow: MutableStateFlow<Boolean> = MutableStateFlow(false)
|
||||
var callCompositeErrorFlow = _callCompositeErrorFlow
|
||||
|
||||
fun updateCallStateError(errorState: ErrorState) {
|
||||
callStateErrorFlow.value = errorState.callStateError
|
||||
}
|
||||
|
|
|
@ -13,9 +13,9 @@ import androidx.constraintlayout.widget.ConstraintLayout
|
|||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.azure.android.communication.ui.calling.implementation.R
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
import com.azure.android.communication.ui.calling.implementation.R
|
||||
|
||||
internal class JoinCallButtonHolderView : ConstraintLayout {
|
||||
constructor(context: Context) : super(context)
|
||||
|
@ -36,15 +36,16 @@ internal class JoinCallButtonHolderView : ConstraintLayout {
|
|||
findViewById(R.id.azure_communication_ui_setup_start_call_button_text)
|
||||
progressBar = findViewById(R.id.azure_communication_ui_setup_start_call_progress_bar)
|
||||
joiningCallText = findViewById(R.id.azure_communication_ui_setup_start_call_joining_text)
|
||||
setupJoinCallButton.background = ContextCompat.getDrawable(
|
||||
context,
|
||||
R.drawable.azure_communication_ui_calling_corner_radius_rectangle_4dp_primary_background
|
||||
)
|
||||
setupJoinCallButton.background =
|
||||
ContextCompat.getDrawable(
|
||||
context,
|
||||
R.drawable.azure_communication_ui_calling_corner_radius_rectangle_4dp_primary_background,
|
||||
)
|
||||
}
|
||||
|
||||
fun start(
|
||||
viewLifecycleOwner: LifecycleOwner,
|
||||
viewModel: JoinCallButtonHolderViewModel
|
||||
viewModel: JoinCallButtonHolderViewModel,
|
||||
) {
|
||||
this.viewModel = viewModel
|
||||
setupJoinCallButtonText.text = context.getString(R.string.azure_communication_ui_calling_setup_view_button_join_call)
|
||||
|
|
|
@ -20,9 +20,8 @@ import kotlinx.coroutines.flow.StateFlow
|
|||
|
||||
internal class JoinCallButtonHolderViewModel(
|
||||
private val dispatch: (Action) -> Unit,
|
||||
private val audioManager: AudioManager
|
||||
private val audioManager: AudioManager,
|
||||
) {
|
||||
|
||||
private lateinit var joinCallButtonEnabledFlow: MutableStateFlow<Boolean>
|
||||
private var disableJoinCallButtonFlow = MutableStateFlow(false)
|
||||
private lateinit var networkManager: NetworkManager
|
||||
|
@ -57,7 +56,7 @@ internal class JoinCallButtonHolderViewModel(
|
|||
MutableStateFlow(
|
||||
audioPermissionState == PermissionStatus.GRANTED &&
|
||||
cameraPermissionState != PermissionStatus.UNKNOWN &&
|
||||
(camerasCount == 0 || cameraOperationalStatus != CameraOperationalStatus.PENDING)
|
||||
(camerasCount == 0 || cameraOperationalStatus != CameraOperationalStatus.PENDING),
|
||||
)
|
||||
disableJoinCallButtonFlow.value = false
|
||||
this.networkManager = networkManager
|
||||
|
|
|
@ -93,8 +93,8 @@ internal class PermissionWarningView : LinearLayout {
|
|||
setupMissingImage.setImageDrawable(
|
||||
ContextCompat.getDrawable(
|
||||
context,
|
||||
R.drawable.azure_communication_ui_calling_ic_fluent_mic_off_24_filled_composite_button_enabled
|
||||
)
|
||||
R.drawable.azure_communication_ui_calling_ic_fluent_mic_off_24_filled_composite_button_enabled,
|
||||
),
|
||||
)
|
||||
setupMissingText.setText(context.getString(R.string.azure_communication_ui_calling_setup_view_preview_area_audio_disabled))
|
||||
} else if (!cameraPermissionGranted) {
|
||||
|
@ -102,8 +102,8 @@ internal class PermissionWarningView : LinearLayout {
|
|||
setupMissingImage.setImageDrawable(
|
||||
ContextCompat.getDrawable(
|
||||
context,
|
||||
R.drawable.azure_communication_ui_calling_ic_fluent_video_off_24_filled_composite_button_enabled
|
||||
)
|
||||
R.drawable.azure_communication_ui_calling_ic_fluent_video_off_24_filled_composite_button_enabled,
|
||||
),
|
||||
)
|
||||
setupMissingText.setText(context.getString(R.string.azure_communication_ui_calling_setup_view_preview_area_camera_disabled))
|
||||
}
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче