Add call participants overflow to calling v2 screen.
This commit is contained in:
parent
204fcc28c7
commit
3f71f90234
9 changed files with 370 additions and 90 deletions
|
@ -15,12 +15,12 @@ import org.thoughtcrime.securesms.events.CallParticipant;
|
|||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.webrtc.RendererCommon;
|
||||
|
||||
class WebRtcCallParticipantsRecyclerAdapter extends ListAdapter<CallParticipant, WebRtcCallParticipantsRecyclerAdapter.ViewHolder> {
|
||||
public class WebRtcCallParticipantsRecyclerAdapter extends ListAdapter<CallParticipant, WebRtcCallParticipantsRecyclerAdapter.ViewHolder> {
|
||||
|
||||
private static final int PARTICIPANT = 0;
|
||||
private static final int EMPTY = 1;
|
||||
|
||||
protected WebRtcCallParticipantsRecyclerAdapter() {
|
||||
public WebRtcCallParticipantsRecyclerAdapter() {
|
||||
super(new DiffCallback());
|
||||
}
|
||||
|
||||
|
@ -43,7 +43,7 @@ class WebRtcCallParticipantsRecyclerAdapter extends ListAdapter<CallParticipant,
|
|||
return getItem(position) == CallParticipant.EMPTY ? EMPTY : PARTICIPANT;
|
||||
}
|
||||
|
||||
static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
ViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
}
|
||||
|
|
|
@ -134,6 +134,7 @@ class CallActivity : BaseActivity(), CallControlsCallback {
|
|||
isRenderInPip = callParticipantsState.isInPipMode,
|
||||
hideAvatar = callParticipantsState.hideAvatar
|
||||
),
|
||||
overflowParticipants = callParticipantsState.listParticipants,
|
||||
localParticipant = callParticipantsState.localParticipant,
|
||||
localRenderState = callParticipantsState.localRenderState,
|
||||
callInfoView = {
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.components.webrtc.v2
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import org.thoughtcrime.securesms.components.webrtc.TextureViewRenderer
|
||||
import org.thoughtcrime.securesms.events.CallParticipant
|
||||
import org.thoughtcrime.securesms.ringrtc.CameraState
|
||||
import org.webrtc.RendererCommon
|
||||
|
||||
/**
|
||||
* Displays video for the given participant if attachVideoSink is true.
|
||||
*/
|
||||
@Composable
|
||||
fun CallParticipantVideoRenderer(
|
||||
callParticipant: CallParticipant,
|
||||
attachVideoSink: Boolean,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
AndroidView(
|
||||
factory = ::TextureViewRenderer,
|
||||
modifier = modifier,
|
||||
onRelease = { it.release() }
|
||||
) { renderer ->
|
||||
renderer.setMirror(callParticipant.cameraDirection == CameraState.Direction.FRONT)
|
||||
renderer.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL)
|
||||
|
||||
callParticipant.videoSink.lockableEglBase.performWithValidEglBase {
|
||||
renderer.init(it)
|
||||
}
|
||||
|
||||
if (attachVideoSink) {
|
||||
renderer.attachBroadcastVideoSink(callParticipant.videoSink)
|
||||
} else {
|
||||
renderer.attachBroadcastVideoSink(null)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.components.webrtc.v2
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.FrameLayout
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.webrtc.WebRtcCallParticipantsRecyclerAdapter
|
||||
import org.thoughtcrime.securesms.events.CallParticipant
|
||||
import org.thoughtcrime.securesms.util.visible
|
||||
|
||||
/**
|
||||
* Wrapper composable for the CallParticipants overflow recycler view.
|
||||
*
|
||||
* Displays a scrollable list of users that are in the call but are not displayed in the primary grid.
|
||||
*/
|
||||
@Composable
|
||||
fun CallParticipantsOverflow(
|
||||
overflowParticipants: List<CallParticipant>,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val adapter = remember { WebRtcCallParticipantsRecyclerAdapter() }
|
||||
|
||||
AndroidView(
|
||||
factory = {
|
||||
val view = LayoutInflater.from(it).inflate(R.layout.webrtc_call_participant_overflow_recycler, FrameLayout(it), false) as RecyclerView
|
||||
view.adapter = adapter
|
||||
view
|
||||
},
|
||||
modifier = modifier,
|
||||
update = {
|
||||
it.visible = true
|
||||
adapter.submitList(overflowParticipants)
|
||||
}
|
||||
)
|
||||
}
|
|
@ -15,14 +15,20 @@ import androidx.compose.animation.scaleIn
|
|||
import androidx.compose.animation.togetherWith
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxScope
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.statusBarsPadding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.BottomSheetScaffold
|
||||
import androidx.compose.material3.BottomSheetScaffoldState
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
|
@ -76,6 +82,7 @@ fun CallScreen(
|
|||
callControlsState: CallControlsState,
|
||||
callControlsCallback: CallControlsCallback = CallControlsCallback.Empty,
|
||||
callParticipantsPagerState: CallParticipantsPagerState,
|
||||
overflowParticipants: List<CallParticipant>,
|
||||
localParticipant: CallParticipant,
|
||||
localRenderState: WebRtcLocalRenderState,
|
||||
callInfoView: @Composable (Float) -> Unit,
|
||||
|
@ -159,6 +166,7 @@ fun CallScreen(
|
|||
localRenderState = localRenderState,
|
||||
webRtcCallState = webRtcCallState,
|
||||
callParticipantsPagerState = callParticipantsPagerState,
|
||||
overflowParticipants = overflowParticipants,
|
||||
scaffoldState = scaffoldState,
|
||||
callControlsState = callControlsState,
|
||||
onPipClick = onLocalPictureInPictureClicked
|
||||
|
@ -176,6 +184,7 @@ fun CallScreen(
|
|||
localRenderState = localRenderState,
|
||||
webRtcCallState = webRtcCallState,
|
||||
callParticipantsPagerState = callParticipantsPagerState,
|
||||
overflowParticipants = overflowParticipants,
|
||||
scaffoldState = scaffoldState,
|
||||
callControlsState = callControlsState,
|
||||
onPipClick = onLocalPictureInPictureClicked
|
||||
|
@ -229,44 +238,85 @@ fun CallScreen(
|
|||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun Viewport(
|
||||
private fun BoxScope.Viewport(
|
||||
localParticipant: CallParticipant,
|
||||
localRenderState: WebRtcLocalRenderState,
|
||||
webRtcCallState: WebRtcViewModel.State,
|
||||
callParticipantsPagerState: CallParticipantsPagerState,
|
||||
overflowParticipants: List<CallParticipant>,
|
||||
scaffoldState: BottomSheetScaffoldState,
|
||||
callControlsState: CallControlsState,
|
||||
onPipClick: () -> Unit
|
||||
) {
|
||||
LargeLocalVideoRenderer(
|
||||
localParticipant = localParticipant,
|
||||
localRenderState = localRenderState
|
||||
)
|
||||
|
||||
if (webRtcCallState.isPassedPreJoin) {
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
CallParticipantsPager(
|
||||
callParticipantsPagerState = callParticipantsPagerState,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.clip(MaterialTheme.shapes.extraLarge)
|
||||
.clickable(
|
||||
onClick = {
|
||||
scope.launch {
|
||||
if (scaffoldState.bottomSheetState.isVisible) {
|
||||
scaffoldState.bottomSheetState.hide()
|
||||
} else {
|
||||
scaffoldState.bottomSheetState.show()
|
||||
}
|
||||
}
|
||||
},
|
||||
enabled = !callControlsState.skipHiddenState
|
||||
)
|
||||
if (webRtcCallState.isPreJoinOrNetworkUnavailable) {
|
||||
LargeLocalVideoRenderer(
|
||||
localParticipant = localParticipant,
|
||||
localRenderState = localRenderState
|
||||
)
|
||||
}
|
||||
|
||||
if (webRtcCallState.inOngoingCall && localParticipant.isVideoEnabled) {
|
||||
val isLargeGroupCall = overflowParticipants.size > 1
|
||||
if (webRtcCallState.isPassedPreJoin) {
|
||||
val isPortrait = LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
Row {
|
||||
Column(
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
CallParticipantsPager(
|
||||
callParticipantsPagerState = callParticipantsPagerState,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f)
|
||||
.clip(MaterialTheme.shapes.extraLarge)
|
||||
.clickable(
|
||||
onClick = {
|
||||
scope.launch {
|
||||
if (scaffoldState.bottomSheetState.isVisible) {
|
||||
scaffoldState.bottomSheetState.hide()
|
||||
} else {
|
||||
scaffoldState.bottomSheetState.show()
|
||||
}
|
||||
}
|
||||
},
|
||||
enabled = !callControlsState.skipHiddenState
|
||||
)
|
||||
)
|
||||
|
||||
if (isPortrait && isLargeGroupCall) {
|
||||
CallParticipantsOverflow(
|
||||
overflowParticipants = overflowParticipants,
|
||||
modifier = Modifier
|
||||
.padding(16.dp)
|
||||
.height(72.dp)
|
||||
.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (!isPortrait && isLargeGroupCall) {
|
||||
CallParticipantsOverflow(
|
||||
overflowParticipants = overflowParticipants,
|
||||
modifier = Modifier
|
||||
.padding(16.dp)
|
||||
.width(72.dp)
|
||||
.fillMaxHeight()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (isLargeGroupCall) {
|
||||
TinyLocalVideoRenderer(
|
||||
localParticipant = localParticipant,
|
||||
localRenderState = localRenderState,
|
||||
modifier = Modifier.align(Alignment.BottomEnd),
|
||||
onClick = onPipClick
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (webRtcCallState.inOngoingCall && localParticipant.isVideoEnabled && !isLargeGroupCall) {
|
||||
val padBottom: Dp = if (scaffoldState.bottomSheetState.isVisible) {
|
||||
0.dp
|
||||
} else {
|
||||
|
@ -283,20 +333,60 @@ private fun Viewport(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Full-screen local video renderer displayed when the user is in pre-call state.
|
||||
*/
|
||||
@Composable
|
||||
private fun LargeLocalVideoRenderer(
|
||||
localParticipant: CallParticipant,
|
||||
localRenderState: WebRtcLocalRenderState
|
||||
) {
|
||||
CallParticipantVideoRenderer(
|
||||
callParticipant = localParticipant,
|
||||
attachVideoSink = localRenderState == WebRtcLocalRenderState.LARGE,
|
||||
LocalParticipantRenderer(
|
||||
localParticipant = localParticipant,
|
||||
localRenderState = localRenderState,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.clip(MaterialTheme.shapes.extraLarge)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Tiny expandable video renderer displayed when the user is in a large group call.
|
||||
*/
|
||||
@Composable
|
||||
private fun TinyLocalVideoRenderer(
|
||||
localParticipant: CallParticipant,
|
||||
localRenderState: WebRtcLocalRenderState,
|
||||
modifier: Modifier = Modifier,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
val isPortrait = LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT
|
||||
|
||||
val smallSize = remember(isPortrait) {
|
||||
if (isPortrait) DpSize(40.dp, 72.dp) else DpSize(72.dp, 40.dp)
|
||||
}
|
||||
val largeSize = remember(isPortrait) {
|
||||
if (isPortrait) DpSize(180.dp, 320.dp) else DpSize(320.dp, 180.dp)
|
||||
}
|
||||
|
||||
val width by animateDpAsState(label = "tiny-width", targetValue = if (localRenderState == WebRtcLocalRenderState.EXPANDED) largeSize.width else smallSize.width)
|
||||
val height by animateDpAsState(label = "tiny-height", targetValue = if (localRenderState == WebRtcLocalRenderState.EXPANDED) largeSize.height else smallSize.height)
|
||||
|
||||
LocalParticipantRenderer(
|
||||
localParticipant = localParticipant,
|
||||
localRenderState = localRenderState,
|
||||
modifier = modifier
|
||||
.padding(16.dp)
|
||||
.height(height)
|
||||
.width(width)
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.clickable(onClick = onClick)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Small moveable local video renderer that displays the user's video in a draggable and expandable view.
|
||||
*/
|
||||
@Composable
|
||||
private fun SmallMoveableLocalVideoRenderer(
|
||||
localParticipant: CallParticipant,
|
||||
|
@ -304,8 +394,14 @@ private fun SmallMoveableLocalVideoRenderer(
|
|||
extraPadBottom: Dp,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
val smallSize = DpSize(90.dp, 160.dp)
|
||||
val largeSize = DpSize(180.dp, 320.dp)
|
||||
val isPortrait = LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT
|
||||
|
||||
val smallSize = remember(isPortrait) {
|
||||
if (isPortrait) DpSize(90.dp, 160.dp) else DpSize(160.dp, 90.dp)
|
||||
}
|
||||
val largeSize = remember(isPortrait) {
|
||||
if (isPortrait) DpSize(180.dp, 320.dp) else DpSize(320.dp, 180.dp)
|
||||
}
|
||||
|
||||
val size = if (localRenderState == WebRtcLocalRenderState.SMALL_RECTANGLE) smallSize else largeSize
|
||||
|
||||
|
@ -321,19 +417,23 @@ private fun SmallMoveableLocalVideoRenderer(
|
|||
.statusBarsPadding()
|
||||
.padding(bottom = bottomPadding)
|
||||
) {
|
||||
CallParticipantVideoRenderer(
|
||||
callParticipant = localParticipant,
|
||||
attachVideoSink = localRenderState == WebRtcLocalRenderState.SMALL_RECTANGLE || localRenderState == WebRtcLocalRenderState.EXPANDED,
|
||||
LocalParticipantRenderer(
|
||||
localParticipant = localParticipant,
|
||||
localRenderState = localRenderState,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.clip(MaterialTheme.shapes.medium)
|
||||
.clickable {
|
||||
.clickable(onClick = {
|
||||
onClick()
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for a CallStateUpdate popup that animates its display on the screen, sliding up from either
|
||||
* above the controls or from the bottom of the screen if the controls are hidden.
|
||||
*/
|
||||
@Composable
|
||||
private fun AnimatedCallStateUpdate(
|
||||
callControlsChange: CallControlsChange?,
|
||||
|
@ -366,7 +466,7 @@ private fun AnimatedCallStateUpdate(
|
|||
private fun CallScreenPreview() {
|
||||
Previews.Preview {
|
||||
CallScreen(
|
||||
callRecipient = Recipient.UNKNOWN,
|
||||
callRecipient = Recipient(systemContactName = "Test User"),
|
||||
webRtcCallState = WebRtcViewModel.State.CALL_CONNECTED,
|
||||
callScreenState = CallScreenState(),
|
||||
callControlsState = CallControlsState(
|
||||
|
@ -376,14 +476,15 @@ private fun CallScreenPreview() {
|
|||
displayGroupRingingToggle = true,
|
||||
displayStartCallButton = true
|
||||
),
|
||||
callParticipantsPagerState = CallParticipantsPagerState(),
|
||||
localParticipant = CallParticipant(),
|
||||
localRenderState = WebRtcLocalRenderState.LARGE,
|
||||
callInfoView = {
|
||||
Text(text = "Call Info View Preview", modifier = Modifier.alpha(it))
|
||||
},
|
||||
localParticipant = CallParticipant(),
|
||||
localRenderState = WebRtcLocalRenderState.LARGE,
|
||||
callParticipantsPagerState = CallParticipantsPagerState(),
|
||||
onNavigationClick = {},
|
||||
onLocalPictureInPictureClicked = {}
|
||||
onLocalPictureInPictureClicked = {},
|
||||
overflowParticipants = (1..5).map { CallParticipant() }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.components.webrtc.v2
|
||||
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import org.thoughtcrime.securesms.avatar.fallback.FallbackAvatarDrawable
|
||||
import org.thoughtcrime.securesms.components.webrtc.TextureViewRenderer
|
||||
import org.thoughtcrime.securesms.components.webrtc.WebRtcLocalRenderState
|
||||
import org.thoughtcrime.securesms.compose.GlideImage
|
||||
import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto
|
||||
import org.thoughtcrime.securesms.events.CallParticipant
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.ringrtc.CameraState
|
||||
import org.webrtc.RendererCommon
|
||||
|
||||
/**
|
||||
* Displays video for the local participant or an appropriate avatar.
|
||||
*/
|
||||
@Composable
|
||||
fun LocalParticipantRenderer(
|
||||
localParticipant: CallParticipant,
|
||||
localRenderState: WebRtcLocalRenderState,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
BoxWithConstraints(
|
||||
modifier = modifier
|
||||
) {
|
||||
val maxWidth = constraints.maxWidth
|
||||
val maxHeight = constraints.maxHeight
|
||||
|
||||
val density = LocalDensity.current
|
||||
val size = with(density) {
|
||||
DpSize(
|
||||
width = maxWidth.toDp(),
|
||||
height = maxHeight.toDp()
|
||||
)
|
||||
}
|
||||
|
||||
val model = remember {
|
||||
ProfileContactPhoto(Recipient.self())
|
||||
}
|
||||
|
||||
val context = LocalContext.current
|
||||
val fallback = remember {
|
||||
FallbackAvatarDrawable(context, Recipient.self().getFallbackAvatar())
|
||||
}
|
||||
|
||||
GlideImage(
|
||||
model = model,
|
||||
imageSize = size,
|
||||
fallback = fallback,
|
||||
modifier = Modifier.fillMaxSize()
|
||||
)
|
||||
|
||||
if (localParticipant.isVideoEnabled) {
|
||||
AndroidView(
|
||||
factory = ::TextureViewRenderer,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
onRelease = { it.release() }
|
||||
) { renderer ->
|
||||
renderer.setMirror(localParticipant.cameraDirection == CameraState.Direction.FRONT)
|
||||
renderer.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL)
|
||||
|
||||
localParticipant.videoSink.lockableEglBase.performWithValidEglBase {
|
||||
renderer.init(it)
|
||||
}
|
||||
|
||||
renderer.attachBroadcastVideoSink(localParticipant.videoSink)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.compose
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.Drawable
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.DisposableEffectResult
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.ImageBitmap
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
import com.bumptech.glide.request.target.CustomTarget
|
||||
import com.bumptech.glide.request.transition.Transition
|
||||
|
||||
/**
|
||||
* Our very own GlideImage.
|
||||
*/
|
||||
@Composable
|
||||
fun <T> GlideImage(
|
||||
model: T?,
|
||||
modifier: Modifier = Modifier,
|
||||
imageSize: DpSize? = null,
|
||||
fallback: Drawable? = null,
|
||||
error: Drawable? = fallback,
|
||||
diskCacheStrategy: DiskCacheStrategy = DiskCacheStrategy.ALL
|
||||
) {
|
||||
var bitmap by remember {
|
||||
mutableStateOf<ImageBitmap?>(null)
|
||||
}
|
||||
|
||||
val target = remember {
|
||||
object : CustomTarget<Bitmap>() {
|
||||
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
|
||||
bitmap = resource.asImageBitmap()
|
||||
}
|
||||
|
||||
override fun onLoadCleared(placeholder: Drawable?) {
|
||||
bitmap = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val density = LocalDensity.current
|
||||
val context = LocalContext.current
|
||||
DisposableEffect(model, fallback, error, diskCacheStrategy, density, imageSize) {
|
||||
val builder = Glide.with(context)
|
||||
.asBitmap()
|
||||
.load(model)
|
||||
.fallback(fallback)
|
||||
.error(error)
|
||||
.diskCacheStrategy(diskCacheStrategy)
|
||||
.fitCenter()
|
||||
|
||||
if (imageSize != null) {
|
||||
with(density) {
|
||||
builder.override(imageSize.width.toPx().toInt(), imageSize.height.toPx().toInt()).into(target)
|
||||
}
|
||||
} else {
|
||||
builder.into(target)
|
||||
}
|
||||
|
||||
object : DisposableEffectResult {
|
||||
override fun dispose() {
|
||||
Glide.with(context).clear(target)
|
||||
bitmap = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val bm = bitmap
|
||||
if (bm != null) {
|
||||
Image(
|
||||
bitmap = bm,
|
||||
contentDescription = null,
|
||||
contentScale = if (model == null) ContentScale.Inside else ContentScale.Crop,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
}
|
|
@ -480,7 +480,7 @@ public class CommunicationActions {
|
|||
}
|
||||
|
||||
private static Class<? extends Activity> getCallActivityClass() {
|
||||
return RemoteConfig.useNewCallApi() ? CallActivity.class : WebRtcCallActivity.class;
|
||||
return RemoteConfig.newCallUi() ? CallActivity.class : WebRtcCallActivity.class;
|
||||
}
|
||||
|
||||
private interface CallContext {
|
||||
|
|
|
@ -1103,7 +1103,7 @@ object RemoteConfig {
|
|||
)
|
||||
|
||||
@JvmStatic
|
||||
@get:JvmName("useNewCallApi")
|
||||
@get:JvmName("newCallUi")
|
||||
val newCallUi: Boolean by remoteBoolean(
|
||||
key = "android.newCallUi",
|
||||
defaultValue = false,
|
||||
|
|
Loading…
Add table
Reference in a new issue