Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,7 @@ fun CallScreen(
isShowingSettingMenu = !isShowingSettingMenu
},
)
Spacer(modifier = Modifier.size(StreamTokens.spacingMd))
Spacer(modifier = Modifier.size(StreamTokens.spacingXs))
if (isTablet()) {
ScreenShareToggleAction(
active = isScreenSharing,
Expand All @@ -421,7 +421,7 @@ fun CallScreen(
},
)
Spacer(
modifier = Modifier.size(StreamTokens.spacingMd),
modifier = Modifier.size(StreamTokens.spacingXs),
)
}
ToggleCameraAction(
Expand All @@ -432,7 +432,7 @@ fun CallScreen(
isCameraEnabled = isCameraEnabled,
onCallAction = { call.camera.setEnabled(it.isEnabled) },
)
Spacer(modifier = Modifier.size(StreamTokens.spacingMd))
Spacer(modifier = Modifier.size(StreamTokens.spacingXs))
ToggleMicrophoneAction(
modifier = Modifier
.testTag(
Expand All @@ -445,7 +445,7 @@ fun CallScreen(
)
},
)
Spacer(modifier = Modifier.size(StreamTokens.spacingMd))
Spacer(modifier = Modifier.size(StreamTokens.spacingXs))
}
Row {
StreamBadgeBox(
Expand All @@ -459,7 +459,7 @@ fun CallScreen(
showParticipants = !showParticipants
}
}
Spacer(modifier = Modifier.size(StreamTokens.spacingMd))
Spacer(modifier = Modifier.size(StreamTokens.spacingXs))
ChatDialogAction(
modifier = Modifier.testTag("Stream_ChatButton"),
messageCount = unreadCount,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,16 @@ import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.layout.requiredWidth
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import io.getstream.android.core.ui.design.StreamTokens
import io.getstream.video.android.compose.theme.VideoTheme
import io.getstream.video.android.compose.ui.components.base.StreamButton
import io.getstream.video.android.compose.ui.components.base.StreamButtonSize
import io.getstream.video.android.compose.ui.components.base.StreamButtonStyleDefaults
import io.getstream.video.android.compose.ui.components.base.StreamTextButton
Expand Down Expand Up @@ -120,25 +121,26 @@ private fun ReactionItem(
onEmojiSelected: (emoji: String) -> Unit,
) {
val mappedEmoji = reactionMapper.map(reaction.emojiCode)
val text = if (showText) {
"$mappedEmoji ${reaction.displayText}"
} else {
mappedEmoji
}
val modifier = if (showText) {
Modifier.fillMaxWidth()
val onClick = { onEmojiSelected(reaction.emojiCode) }
if (showText) {
StreamTextButton(
modifier = Modifier.fillMaxWidth(),
style = StreamButtonStyleDefaults.secondaryOutline,
size = StreamButtonSize.Small,
text = "$mappedEmoji ${reaction.displayText}",
onClick = onClick,
)
} else {
Modifier
.requiredWidth(StreamTokens.size48)
.requiredHeight(StreamTokens.size48)
// A plain StreamButton: the label padding of StreamTextButton leaves no room for the emoji.
StreamButton(
modifier = Modifier.requiredSize(StreamTokens.size48),
style = StreamButtonStyleDefaults.secondaryOutline,
size = StreamButtonSize.Small,
onClick = onClick,
) {
Text(text = mappedEmoji)
}
}
StreamTextButton(
modifier = modifier,
style = StreamButtonStyleDefaults.secondaryOutline,
size = StreamButtonSize.Small,
text = text,
onClick = { onEmojiSelected(reaction.emojiCode) },
)
}

private fun sendReaction(scope: CoroutineScope, call: Call, emoji: String, onDismiss: () -> Unit) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,19 @@ package io.getstream.video.android.ui.menu.base

import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyItemScope
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Icon
import androidx.compose.material.LinearProgressIndicator
import androidx.compose.material.LocalContentColor
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
Expand All @@ -40,12 +44,13 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTagsAsResourceId
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import io.getstream.android.core.ui.design.StreamTokens
import io.getstream.video.android.compose.theme.VideoTheme
import io.getstream.video.android.compose.ui.components.base.StreamButton
import io.getstream.video.android.compose.ui.components.base.StreamButtonStyleDefaults
import io.getstream.video.android.compose.ui.components.base.StreamIconButton
import io.getstream.video.android.compose.ui.components.base.StreamTextButton
import io.getstream.video.android.ui.closedcaptions.ClosedCaptionUiState
import io.getstream.video.android.ui.menu.AudioUsageVoiceCommunicationUiState
import io.getstream.video.android.ui.menu.TranscriptionAvailableUiState
Expand Down Expand Up @@ -237,7 +242,7 @@ private fun LazyListScope.menuItems(
items(items.size) { index ->
val item = items[index]
val highlight = item.highlight
StreamTextButton(
StreamButton(
modifier = Modifier.fillMaxWidth(),
onClick = {
val actionItem = item as? ActionMenuItem
Expand All @@ -247,10 +252,29 @@ private fun LazyListScope.menuItems(
onNewSubmenu(it)
}
},
text = item.title,
leadingIcon = item.icon.painter(),
style = if (highlight) StreamButtonStyleDefaults.primaryGhost else StreamButtonStyleDefaults.secondaryGhost,
)
style = StreamButtonStyleDefaults.secondaryGhost,
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = StreamTokens.spacingMd),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(StreamTokens.spacingSm),
) {
Icon(
painter = item.icon.painter(),
contentDescription = null,
modifier = Modifier.size(StreamTokens.iconSizeMd),
tint = if (highlight) VideoTheme.colors.accentPrimary else LocalContentColor.current,
)
Text(
text = item.title,
style = VideoTheme.typography.bodyDefault,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import io.getstream.video.android.compose.theme.VideoTheme
import io.getstream.video.android.compose.ui.components.call.controls.actions.FlipCameraAction
import io.getstream.video.android.mock.StreamPreviewDataUtils
import io.getstream.video.android.mock.previewCall

Expand All @@ -36,3 +37,46 @@ private fun CallAppBarRootPreview() {
internal fun CallAppBarPreview() {
CallAppBar(call = previewCall)
}

@Preview
@Composable
private fun CallAppBarUnevenSlotsRootPreview() {
StreamPreviewDataUtils.initializeStreamVideo(LocalContext.current)
VideoTheme {
CallAppBarUnevenSlotsPreview()
}
}

@Composable
internal fun CallAppBarUnevenSlotsPreview() {
CallAppBar(
call = previewCall,
leadingContent = {
DefaultCallAppBarLeadingContent(onBackButtonClicked = {})
FlipCameraAction(onCallAction = {})
},
)
}

@Preview
@Composable
private fun CallAppBarLongTitleRootPreview() {
StreamPreviewDataUtils.initializeStreamVideo(LocalContext.current)
VideoTheme {
CallAppBarLongTitlePreview()
}
}

@Composable
internal fun CallAppBarLongTitlePreview() {
CallAppBar(
call = previewCall,
centerContent = {
CalLCenterContent(
text = "Weekly design review with the whole video team",
isRecording = false,
isReconnecting = false,
)
},
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.ripple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.key
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
Expand All @@ -41,6 +42,7 @@ import com.skydoves.landscapist.coil.CoilImage
import com.skydoves.landscapist.components.rememberImageComponent
import com.skydoves.landscapist.placeholder.placeholder.PlaceholderPlugin
import io.getstream.android.core.ui.design.StreamTokens
import io.getstream.video.android.compose.pip.rememberIsInPipMode
import io.getstream.video.android.compose.theme.VideoTheme

/**
Expand Down Expand Up @@ -97,6 +99,8 @@ internal fun Avatar(
modifier
}

// The fade-in never finishes drawing in a Picture-in-Picture window and leaves the avatar empty.
val isInPictureInPicture = rememberIsInPipMode()
CoilImage(
modifier = clickableModifier.avatarBorder(shape).clip(shape),
imageModel = { imageUrl },
Expand All @@ -106,10 +110,15 @@ internal fun Avatar(
requestSize = imageRequestSize,
),
previewPlaceholder = painterResource(id = previewModePlaceholder),
component = rememberImageComponent {
+CrossfadePlugin()
loadingPlaceholder?.let {
+PlaceholderPlugin.Loading(painterResource(id = it))
// rememberImageComponent keeps its first instance, so the key rebuilds it when the mode changes.
component = key(isInPictureInPicture) {
rememberImageComponent {
if (!isInPictureInPicture) {
+CrossfadePlugin()
}
loadingPlaceholder?.let {
+PlaceholderPlugin.Loading(painterResource(id = it))
}
}
},
failure = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package io.getstream.video.android.compose.ui.components.avatar

import androidx.annotation.DrawableRes
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
Expand All @@ -39,7 +40,8 @@ import io.getstream.video.android.model.User
* @param userImage The URL of the image to be displayed. Usually [User.image].
* @param userName The name to be used for the initials fallback. Usually [User.name].
* @param shape The shape of the avatar. `CircleShape` by default.
* @param avatarSize The size of the avatar.
* @param avatarSize The size of the avatar. In a container smaller than twice this size, the avatar
* shrinks to half of the container's shorter side.
* @param imageScale The scale rule used for the image. `Crop` by default.
* @param imageDescription The image content description for accessibility. `Null` by default.
* @param imageRequestSize The image size to be requested. Original size by default.
Expand All @@ -63,10 +65,10 @@ public fun UserAvatarBackground(
@DrawableRes previewModePlaceholder: Int = LocalAvatarPreviewProvider.getLocalAvatarPreviewPlaceholder(),
textStyle: TextStyle? = null,
) {
Box(modifier = modifier.fillMaxSize()) {
BoxWithConstraints(modifier = modifier.fillMaxSize()) {
Box(
modifier = Modifier
.size(avatarSize)
.size(minOf(avatarSize, minOf(maxWidth, maxHeight) / 2))
.align(Alignment.Center),
) {
UserAvatar(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ package io.getstream.video.android.compose.ui.components.call

import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
Expand All @@ -35,6 +34,7 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment.Companion.CenterVertically
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
Expand All @@ -54,12 +54,14 @@ import io.getstream.video.android.core.Call
import io.getstream.video.android.core.call.state.CallAction
import io.getstream.video.android.core.recording.RecordingType
import io.getstream.video.android.ui.common.R
import kotlin.math.max

/**
* Represents the default AppBar that's shown in calls. Exposes handlers for the two default slot
* component implementations (leading and trailing).
*
* Exposes slots required to customize the look and feel.
* Exposes slots required to customize the look and feel. The leading and trailing content keep
* their size, and the center content is centered to the bar in the space left between them.
*
* @param call The call that contains all the participants state and tracks.
* @param modifier Modifier for styling.
Expand Down Expand Up @@ -106,16 +108,39 @@ public fun CallAppBar(
}
},
) {
Row(
Layout(
contents = listOf(
{ Row(verticalAlignment = CenterVertically) { leadingContent?.invoke(this) } },
{ Row(verticalAlignment = CenterVertically) { centerContent?.invoke(this) } },
{ Row(verticalAlignment = CenterVertically) { trailingContent?.invoke(this) } },
),
modifier = modifier
.fillMaxWidth()
.height(StreamTokens.size48),
verticalAlignment = CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
) {
leadingContent?.invoke(this)
centerContent?.invoke(this)
trailingContent?.invoke(this)
) { (leading, center, trailing), constraints ->
val width = constraints.maxWidth
val height = constraints.maxHeight
val slotConstraints = constraints.copy(minWidth = 0, minHeight = 0)
val leadingPlaceable = leading.single().measure(slotConstraints)
val trailingPlaceable = trailing.single().measure(
slotConstraints.copy(maxWidth = width - leadingPlaceable.width),
)
// The side slots keep their size, and the center gets the space that keeps it centered to the bar.
val sideWidth = max(leadingPlaceable.width, trailingPlaceable.width)
val centerPlaceable = center.single().measure(
slotConstraints.copy(maxWidth = (width - 2 * sideWidth).coerceAtLeast(0)),
)
layout(width, height) {
leadingPlaceable.placeRelative(0, (height - leadingPlaceable.height) / 2)
centerPlaceable.placeRelative(
(width - centerPlaceable.width) / 2,
(height - centerPlaceable.height) / 2,
)
trailingPlaceable.placeRelative(
width - trailingPlaceable.width,
(height - trailingPlaceable.height) / 2,
)
}
}
}

Expand Down
Loading
Loading