From 887c9b553e91a1679fe08a0dd77ce660e3e42484 Mon Sep 17 00:00:00 2001 From: Oussama Hassine Date: Mon, 9 Sep 2024 17:31:05 +0200 Subject: [PATCH] feat(calling): Adapt the floating self user view in PiP mode (WPB-10652) - Part 3 (#3410) --- .../participantsview/FloatingSelfUserTile.kt | 54 ++++++++++++++++--- .../participantsview/ParticipantTile.kt | 47 +++++++++------- .../participantsview/ParticipantsTiles.kt | 7 ++- .../gridview/CallingGridView.kt | 6 ++- 4 files changed, 85 insertions(+), 29 deletions(-) diff --git a/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/participantsview/FloatingSelfUserTile.kt b/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/participantsview/FloatingSelfUserTile.kt index 347a2d0fcb8..81dbf1c6143 100644 --- a/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/participantsview/FloatingSelfUserTile.kt +++ b/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/participantsview/FloatingSelfUserTile.kt @@ -28,6 +28,7 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Card import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -35,11 +36,15 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp +import androidx.core.app.PictureInPictureModeChangedInfo +import androidx.core.util.Consumer import com.wire.android.ui.calling.model.UICallParticipant +import com.wire.android.ui.calling.ongoing.OngoingCallActivity import com.wire.android.ui.common.colorsScheme import com.wire.android.ui.common.dimensions @@ -48,7 +53,9 @@ private const val STIFFNESS_MEDIUM_LOW = 300f private const val DEFAULT_OFFSETX_SELF_USER_TILE = -50f private const val DEFAULT_OFFSETY_SELF_USER_TILE = 80F private val SELF_VIDEO_TILE_HEIGHT = 250.dp +private val SELF_VIDEO_TILE_HEIGHT_IN_PIP = 60.dp private val SELF_VIDEO_TILE_WIDTH = 150.dp +private val SELF_VIDEO_TILE_WIDTH_IN_PIP = 40.dp @Composable fun FloatingSelfUserTile( @@ -59,9 +66,21 @@ fun FloatingSelfUserTile( modifier: Modifier = Modifier, onClearSelfUserVideoPreview: () -> Unit ) { + var selfVideoTileHeight by remember { + mutableStateOf(SELF_VIDEO_TILE_HEIGHT) + } + var selfVideoTileWidth by remember { + mutableStateOf(SELF_VIDEO_TILE_WIDTH) + } + val activity = LocalContext.current + val density = LocalDensity.current val contentHeightPx = density.run { (contentHeight).toPx() } + var isOnPiPMode by remember { + mutableStateOf(false) + } + var selfUserTileOffsetX by remember { mutableStateOf(DEFAULT_OFFSETX_SELF_USER_TILE) } @@ -77,12 +96,34 @@ fun FloatingSelfUserTile( label = "selfUserTileOffset" ) + DisposableEffect(activity) { + val observer = Consumer { info -> + if (info.isInPictureInPictureMode) { + selfVideoTileHeight = SELF_VIDEO_TILE_HEIGHT_IN_PIP + selfVideoTileWidth = SELF_VIDEO_TILE_WIDTH_IN_PIP + selfUserTileOffsetX = -10f + selfUserTileOffsetY = 10f + isOnPiPMode = true + } else { + selfVideoTileHeight = SELF_VIDEO_TILE_HEIGHT + selfVideoTileWidth = SELF_VIDEO_TILE_WIDTH + selfUserTileOffsetX = DEFAULT_OFFSETX_SELF_USER_TILE + selfUserTileOffsetY = DEFAULT_OFFSETY_SELF_USER_TILE + isOnPiPMode = false + } + } + (activity as OngoingCallActivity).addOnPictureInPictureModeChangedListener( + observer + ) + onDispose { activity.removeOnPictureInPictureModeChangedListener(observer) } + } + Card( border = BorderStroke(1.dp, colorsScheme().uncheckedColor), shape = RoundedCornerShape(dimensions().corner6x), modifier = modifier - .height(SELF_VIDEO_TILE_HEIGHT) - .width(SELF_VIDEO_TILE_WIDTH) + .height(selfVideoTileHeight) + .width(selfVideoTileWidth) .offset { IntOffset(selfUserTileOffset.x.toInt(), selfUserTileOffset.y.toInt()) } .pointerInput(Unit) { detectDragGestures( @@ -91,11 +132,11 @@ fun FloatingSelfUserTile( if (selfUserTileOffsetX - 150f > -(contentWidth / 2)) { DEFAULT_OFFSETX_SELF_USER_TILE } else { - -contentWidth + SELF_VIDEO_TILE_WIDTH.toPx() - DEFAULT_OFFSETX_SELF_USER_TILE + -contentWidth + selfVideoTileWidth.toPx() - DEFAULT_OFFSETX_SELF_USER_TILE } selfUserTileOffsetY = if (selfUserTileOffsetY + 250f > (contentHeightPx / 2)) { - contentHeightPx - SELF_VIDEO_TILE_HEIGHT.toPx() - DEFAULT_OFFSETY_SELF_USER_TILE + contentHeightPx - selfVideoTileHeight.toPx() - DEFAULT_OFFSETY_SELF_USER_TILE } else { DEFAULT_OFFSETY_SELF_USER_TILE } @@ -104,14 +145,14 @@ fun FloatingSelfUserTile( change.consume() val newOffsetX = (selfUserTileOffsetX + dragAmount.x) .coerceAtLeast( - -contentHeightPx - SELF_VIDEO_TILE_WIDTH.toPx() + -contentHeightPx - selfVideoTileWidth.toPx() ) .coerceAtMost(-50f) val newOffsetY = (selfUserTileOffsetY + dragAmount.y) .coerceAtLeast(50f) .coerceAtMost( - contentHeightPx - SELF_VIDEO_TILE_HEIGHT.toPx() + contentHeightPx - selfVideoTileHeight.toPx() ) selfUserTileOffsetX = newOffsetX @@ -122,6 +163,7 @@ fun FloatingSelfUserTile( ParticipantTile( participantTitleState = participant, isSelfUser = true, + isOnPiPMode = isOnPiPMode, shouldFillSelfUserCameraPreview = true, isSelfUserMuted = participant.isMuted, isSelfUserCameraOn = participant.isCameraOn, diff --git a/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/participantsview/ParticipantTile.kt b/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/participantsview/ParticipantTile.kt index 4446f7f13fa..451323b8cbb 100644 --- a/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/participantsview/ParticipantTile.kt +++ b/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/participantsview/ParticipantTile.kt @@ -93,6 +93,7 @@ fun ParticipantTile( isSelfUserCameraOn: Boolean, onSelfUserVideoPreviewCreated: (view: View) -> Unit, modifier: Modifier = Modifier, + isOnPiPMode: Boolean = false, shouldFillSelfUserCameraPreview: Boolean = false, shouldFillOthersVideoPreview: Boolean = true, isZoomingEnabled: Boolean = false, @@ -119,7 +120,11 @@ fun ParticipantTile( top.linkTo(parent.top) start.linkTo(parent.start) end.linkTo(parent.end) - bottom.linkTo(bottomRow.top) + if (isOnPiPMode) { + bottom.linkTo(parent.bottom) + } else { + bottom.linkTo(bottomRow.top) + } width = Dimension.fillToConstraints.atMost(maxAvatarSize) height = Dimension.fillToConstraints.atMost(maxAvatarSize + activeSpeakerBorderPadding) @@ -128,6 +133,7 @@ fun ParticipantTile( asset = participantTitleState.avatar, nameBasedAvatar = NameBasedAvatar(participantTitleState.name, participantTitleState.accentId) ), + isOnPiPMode = isOnPiPMode ) if (isSelfUser) { @@ -148,23 +154,25 @@ fun ParticipantTile( ) } - BottomRow( - participantTitleState = participantTitleState, - isSelfUser = isSelfUser, - isSelfUserMuted = isSelfUserMuted, - modifier = Modifier - .padding( - // move by the size of the active speaker border - start = dimensions().spacing6x, - end = dimensions().spacing6x, - bottom = dimensions().spacing6x, - ) - .constrainAs(bottomRow) { - bottom.linkTo(parent.bottom) - start.linkTo(parent.start) - end.linkTo(parent.end) - } - ) + if (!isOnPiPMode) { + BottomRow( + participantTitleState = participantTitleState, + isSelfUser = isSelfUser, + isSelfUserMuted = isSelfUserMuted, + modifier = Modifier + .padding( + // move by the size of the active speaker border + start = dimensions().spacing6x, + end = dimensions().spacing6x, + bottom = dimensions().spacing6x, + ) + .constrainAs(bottomRow) { + bottom.linkTo(parent.bottom) + start.linkTo(parent.start) + end.linkTo(parent.end) + } + ) + } } } } @@ -359,12 +367,13 @@ private fun OthersVideoRenderer( private fun AvatarTile( avatar: UserAvatarData, modifier: Modifier = Modifier, + isOnPiPMode: Boolean = false ) { BoxWithConstraints( modifier = modifier, contentAlignment = Alignment.Center, ) { - val size = min(maxWidth, maxHeight) + val size = if (isOnPiPMode) 20.dp else min(maxWidth, maxHeight) UserProfileAvatar( padding = dimensions().spacing0x, size = size, diff --git a/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/participantsview/ParticipantsTiles.kt b/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/participantsview/ParticipantsTiles.kt index 955d7a515a9..0e4dd59c546 100644 --- a/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/participantsview/ParticipantsTiles.kt +++ b/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/participantsview/ParticipantsTiles.kt @@ -41,6 +41,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.wire.android.BuildConfig +import com.wire.android.ui.LocalActivity import com.wire.android.ui.calling.model.UICallParticipant import com.wire.android.ui.calling.ongoing.buildPreviewParticipantsList import com.wire.android.ui.calling.ongoing.fullscreen.SelectedParticipant @@ -68,6 +69,8 @@ fun VerticalCallingPager( onDoubleTap: (selectedParticipant: SelectedParticipant) -> Unit, modifier: Modifier = Modifier, ) { + val activity = LocalActivity.current + Column( modifier = modifier .fillMaxWidth() @@ -128,8 +131,8 @@ fun VerticalCallingPager( } } } - // we don't need to display the indicator if we have one page - if (pagesCount(participants.size) > 1) { + // we don't need to display the indicator if we have one page and when it's in PiP mode + if (pagesCount(participants.size) > 1 && !activity.isInPictureInPictureMode) { Surface( shape = RoundedCornerShape(dimensions().corner16x), modifier = Modifier diff --git a/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/participantsview/gridview/CallingGridView.kt b/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/participantsview/gridview/CallingGridView.kt index 2a104d02e09..63d431a4d35 100644 --- a/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/participantsview/gridview/CallingGridView.kt +++ b/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/participantsview/gridview/CallingGridView.kt @@ -19,7 +19,6 @@ package com.wire.android.ui.calling.ongoing.participantsview.gridview import android.view.View -import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -36,6 +35,7 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.times import com.wire.android.BuildConfig +import com.wire.android.ui.LocalActivity import com.wire.android.ui.calling.model.UICallParticipant import com.wire.android.ui.calling.ongoing.buildPreviewParticipantsList import com.wire.android.ui.calling.ongoing.fullscreen.SelectedParticipant @@ -44,7 +44,6 @@ import com.wire.android.ui.common.dimensions import com.wire.android.ui.theme.WireTheme import com.wire.android.util.ui.PreviewMultipleThemes -@OptIn(ExperimentalFoundationApi::class) @Composable fun GroupCallGrid( participants: List, @@ -59,6 +58,8 @@ fun GroupCallGrid( contentPadding: Dp = dimensions().spacing4x, spacedBy: Dp = dimensions().spacing2x, ) { + val activity = LocalActivity.current + // We need the number of tiles rows needed to calculate their height val numberOfTilesRows = remember(participants.size) { tilesRowsCount(participants.size) @@ -105,6 +106,7 @@ fun GroupCallGrid( .height(tileHeight) .animateItem(), participantTitleState = participant, + isOnPiPMode = activity.isInPictureInPictureMode, isSelfUser = isSelfUser, isSelfUserMuted = isSelfUserMuted, isSelfUserCameraOn = isSelfUserCameraOn,